update statistics screen

This commit is contained in:
Primakov Alexandr Alexandrovich
2025-05-08 18:25:37 +03:00
parent 3c22354130
commit 2d0b97be44

View File

@@ -13,48 +13,110 @@ const folderPath = path.resolve(__dirname, './routers')
const getUrl = (url) => `${process.env.NODE_ENV === 'development' ? '' : '/ms'}${url}` const getUrl = (url) => `${process.env.NODE_ENV === 'development' ? '' : '/ms'}${url}`
// Определение типов // Определение типов
interface EndpointStats {
total: number;
mock: number;
real: number;
}
interface FileInfo { interface FileInfo {
path: string; path: string;
type: 'file'; type: 'file';
endpoints: number; endpoints: EndpointStats;
} }
interface DirectoryInfo { interface DirectoryInfo {
path: string; path: string;
type: 'directory'; type: 'directory';
endpoints: number; endpoints: EndpointStats;
children: (FileInfo | DirectoryInfo)[]; children: (FileInfo | DirectoryInfo)[];
} }
interface DirScanResult { interface DirScanResult {
items: (FileInfo | DirectoryInfo)[]; items: (FileInfo | DirectoryInfo)[];
totalEndpoints: number; totalEndpoints: EndpointStats;
} }
// Функция для поиска эндпоинтов в файлах // Функция для поиска эндпоинтов в файлах
function countEndpoints(filePath) { function countEndpoints(filePath) {
if (!fs.existsSync(filePath) || !filePath.endsWith('.js') && !filePath.endsWith('.ts')) { if (!fs.existsSync(filePath) || !filePath.endsWith('.js') && !filePath.endsWith('.ts')) {
return 0; return { total: 0, mock: 0, real: 0 };
} }
try { try {
const content = fs.readFileSync(filePath, 'utf8'); const content = fs.readFileSync(filePath, 'utf8');
const httpMethods = ['get', 'post', 'put', 'delete', 'patch']; const httpMethods = ['get', 'post', 'put', 'delete', 'patch'];
return httpMethods.reduce((count, method) => { const endpointMatches = [];
const regex = new RegExp(`router\\.${method}\\(`, 'gi'); let totalCount = 0;
const matches = content.match(regex) || [];
return count + matches.length; // Собираем все эндпоинты и их контекст
}, 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) { } catch (err) {
return 0; return { total: 0, mock: 0, real: 0 };
} }
} }
// Функция для рекурсивного обхода директорий // Функция для рекурсивного обхода директорий
function getAllDirs(dir, basePath = ''): DirScanResult { function getAllDirs(dir, basePath = ''): DirScanResult {
const items: (FileInfo | DirectoryInfo)[] = []; const items: (FileInfo | DirectoryInfo)[] = [];
let totalEndpoints = 0; let totalEndpoints = { total: 0, mock: 0, real: 0 };
try { try {
const dirItems = fs.readdirSync(dir); const dirItems = fs.readdirSync(dir);
@@ -66,7 +128,9 @@ function getAllDirs(dir, basePath = ''): DirScanResult {
if (stat.isDirectory()) { if (stat.isDirectory()) {
const dirResult = getAllDirs(itemPath, relativePath); const dirResult = getAllDirs(itemPath, relativePath);
totalEndpoints += dirResult.totalEndpoints; totalEndpoints.total += dirResult.totalEndpoints.total;
totalEndpoints.mock += dirResult.totalEndpoints.mock;
totalEndpoints.real += dirResult.totalEndpoints.real;
items.push({ items.push({
path: relativePath, path: relativePath,
@@ -76,7 +140,9 @@ function getAllDirs(dir, basePath = ''): DirScanResult {
}); });
} else { } else {
const fileEndpoints = countEndpoints(itemPath); const fileEndpoints = countEndpoints(itemPath);
totalEndpoints += fileEndpoints; totalEndpoints.total += fileEndpoints.total;
totalEndpoints.mock += fileEndpoints.mock;
totalEndpoints.real += fileEndpoints.real;
items.push({ items.push({
path: relativePath, path: relativePath,
@@ -103,9 +169,13 @@ function generateDirList(dirs: (FileInfo | DirectoryInfo)[], level = 0) {
return `<ul class="directory-list" style="padding-left: ${indent}px">${dirs.map(item => { return `<ul class="directory-list" style="padding-left: ${indent}px">${dirs.map(item => {
if (item.type === 'directory') { if (item.type === 'directory') {
const endpointClass = item.endpoints > 0 ? 'has-endpoints' : ''; const endpointClass = item.endpoints.total > 0 ? 'has-endpoints' : '';
const endpointInfo = item.endpoints > 0 const endpointInfo = item.endpoints.total > 0
? `<span class="endpoint-count" title="Количество эндпоинтов в директории">${item.endpoints}</span>` ? `<div class="endpoint-stats">
<span class="endpoint-count total" title="Всего эндпоинтов">${item.endpoints.total}</span>
<span class="endpoint-count mock" title="Моковые эндпоинты">${item.endpoints.mock}</span>
<span class="endpoint-count real" title="Реальные эндпоинты">${item.endpoints.real}</span>
</div>`
: ''; : '';
return ` return `
@@ -121,9 +191,13 @@ function generateDirList(dirs: (FileInfo | DirectoryInfo)[], level = 0) {
</li> </li>
`; `;
} else { } else {
const endpointClass = item.endpoints > 0 ? 'has-endpoints' : ''; const endpointClass = item.endpoints.total > 0 ? 'has-endpoints' : '';
const endpointInfo = item.endpoints > 0 const endpointInfo = item.endpoints.total > 0
? `<span class="endpoint-count" title="Количество эндпоинтов">${item.endpoints}</span>` ? `<div class="endpoint-stats">
<span class="endpoint-count total" title="Всего эндпоинтов">${item.endpoints.total}</span>
<span class="endpoint-count mock" title="Моковые эндпоинты">${item.endpoints.mock}</span>
<span class="endpoint-count real" title="Реальные эндпоинты">${item.endpoints.real}</span>
</div>`
: ''; : '';
return ` return `
@@ -200,7 +274,9 @@ router.get('/', async (req, res) => {
const routerFolders = fs.readdirSync(routersPath); const routerFolders = fs.readdirSync(routersPath);
const directoryResult = getAllDirs(projectRoot); const directoryResult = getAllDirs(projectRoot);
const totalEndpoints = directoryResult.totalEndpoints; const totalEndpoints = directoryResult.totalEndpoints.total;
const mockEndpoints = directoryResult.totalEndpoints.mock;
const realEndpoints = directoryResult.totalEndpoints.real;
// Получаем последние 10 ошибок // Получаем последние 10 ошибок
const latestErrors = await ErrorLog.find().sort({ createdAt: -1 }).limit(10); const latestErrors = await ErrorLog.find().sort({ createdAt: -1 }).limit(10);
@@ -376,6 +452,8 @@ router.get('/', async (req, res) => {
:root { :root {
--primary-color: #3f51b5; --primary-color: #3f51b5;
--secondary-color: #f50057; --secondary-color: #f50057;
--mock-color: #ff9800;
--real-color: #4caf50;
--bg-color: #f9f9f9; --bg-color: #f9f9f9;
--card-bg: #ffffff; --card-bg: #ffffff;
--text-color: #333333; --text-color: #333333;
@@ -501,16 +579,32 @@ router.get('/', async (req, res) => {
transform: rotate(90deg); transform: rotate(90deg);
} }
.endpoint-count { .endpoint-stats {
display: flex;
gap: 5px;
margin-left: 10px; margin-left: 10px;
}
.endpoint-count {
font-size: 0.75rem; font-size: 0.75rem;
color: #fff; color: #fff;
background-color: var(--secondary-color);
padding: 2px 8px; padding: 2px 8px;
border-radius: 12px; border-radius: 12px;
font-weight: 500; font-weight: 500;
} }
.endpoint-count.total {
background-color: var(--secondary-color);
}
.endpoint-count.mock {
background-color: var(--mock-color);
}
.endpoint-count.real {
background-color: var(--real-color);
}
.has-endpoints .dir-item { .has-endpoints .dir-item {
border-left: 3px solid var(--secondary-color); border-left: 3px solid var(--secondary-color);
} }
@@ -519,6 +613,20 @@ router.get('/', async (req, res) => {
border-left: 3px solid var(--secondary-color); border-left: 3px solid var(--secondary-color);
} }
.stats-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 15px;
}
.stats-card.mock {
background: linear-gradient(135deg, #ff9800, #f57c00);
}
.stats-card.real {
background: linear-gradient(135deg, #4caf50, #388e3c);
}
.section { .section {
margin-bottom: 30px; margin-bottom: 30px;
padding: 20px; padding: 20px;
@@ -848,9 +956,19 @@ router.get('/', async (req, res) => {
<div class="container"> <div class="container">
<div class="header"> <div class="header">
<h1>Multy Stub v${pkg.version}</h1> <h1>Multy Stub v${pkg.version}</h1>
<div class="stats-card"> <div class="stats-grid">
<div class="stats-value">${totalEndpoints}</div> <div class="stats-card">
<div class="stats-label">Всего эндпоинтов</div> <div class="stats-value">${totalEndpoints}</div>
<div class="stats-label">Всего эндпоинтов</div>
</div>
<div class="stats-card mock">
<div class="stats-value">${mockEndpoints}</div>
<div class="stats-label">Моковые эндпоинты</div>
</div>
<div class="stats-card real">
<div class="stats-value">${realEndpoints}</div>
<div class="stats-label">Реальные эндпоинты</div>
</div>
</div> </div>
</div> </div>