update statistics screen
This commit is contained in:
168
server/server.ts
168
server/server.ts
@@ -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>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user