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