HM-58. Добавлен новый тип логов client-logs (#21)
This commit is contained in:
@ -70,7 +70,7 @@
|
|||||||
"multiline-comment-style": ["warn", "starred-block"],
|
"multiline-comment-style": ["warn", "starred-block"],
|
||||||
"new-cap": "warn",
|
"new-cap": "warn",
|
||||||
"new-parens": "warn",
|
"new-parens": "warn",
|
||||||
"newline-per-chained-call": ["warn", {"ignoreChainWithDepth": 1}],
|
"newline-per-chained-call": ["warn", {"ignoreChainWithDepth": 3}],
|
||||||
"no-bitwise": "warn",
|
"no-bitwise": "warn",
|
||||||
"no-inline-comments": "warn",
|
"no-inline-comments": "warn",
|
||||||
"no-lonely-if": "warn",
|
"no-lonely-if": "warn",
|
||||||
|
|||||||
@ -2,10 +2,25 @@ import axios from 'axios';
|
|||||||
import {API_URL, ENDPOINTS} from './consts';
|
import {API_URL, ENDPOINTS} from './consts';
|
||||||
|
|
||||||
class StorageLogsApi {
|
class StorageLogsApi {
|
||||||
URL = `${API_URL}${ENDPOINTS.SERVER_LOGS}`;
|
URL = `${API_URL}`;
|
||||||
|
|
||||||
request = async () => {
|
requestServerLogs = async () => {
|
||||||
const {data} = await axios.get(this.URL);
|
const {data} = await axios.get(`${this.URL}${ENDPOINTS.SERVER_LOGS}`);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
requestClientLogs = async () => {
|
||||||
|
const {data} = await axios.get(`${this.URL}${ENDPOINTS.CLIENT_LOGS}`);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteAllServerLogs = async () => {
|
||||||
|
const {data} = await axios.delete(`${this.URL}${ENDPOINTS.SERVER_LOGS}`);
|
||||||
|
return data;
|
||||||
|
}
|
||||||
|
|
||||||
|
deleteAllClientLogs = async () => {
|
||||||
|
const {data} = await axios.delete(`${this.URL}${ENDPOINTS.CLIENT_LOGS}`);
|
||||||
return data;
|
return data;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
27
src/app.html
27
src/app.html
@ -76,7 +76,7 @@
|
|||||||
<tbody></tbody>
|
<tbody></tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
</template>
|
</template>
|
||||||
<!-- Шаблон колонки заголовка универсильной таблицы -->
|
<!-- Шаблон колонки заголовка универсильной таблицы -->
|
||||||
<template id="uni-table-th">
|
<template id="uni-table-th">
|
||||||
@ -94,15 +94,34 @@
|
|||||||
</nav>
|
</nav>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<!-- Шаблон фильтра страницы logs -->
|
<!-- Шаблон фильтра серверных логов страницы logs -->
|
||||||
<template id="logs-filters">
|
<template id="logs-filters">
|
||||||
<form class="row p-3 m-0">
|
<form class="row p-3 m-0">
|
||||||
<div class="col-md-auto">
|
<div class="col-md-auto">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<div class="mb-3">
|
<!-- Общий фильтр -->
|
||||||
<label for="logs-filter-message" class="form-label">Найти в сообщениях</label>
|
<div class="col mb-3">
|
||||||
|
<label for="logs-filter-type" class="form-label">Тип журнала</label>
|
||||||
|
<select class="form-select" id="logs-filter-type"></select>
|
||||||
|
</div>
|
||||||
|
<!-- Фильтры для таблицы серверных ошибок -->
|
||||||
|
<div class="col mb-3">
|
||||||
|
<label for="logs-filter-log-type" class="form-label">Тип записи</label>
|
||||||
|
<input type="text" class="form-control" id="logs-filter-log-type" placeholder="Введите текст">
|
||||||
|
</div>
|
||||||
|
<div class="col mb-3">
|
||||||
|
<label for="logs-filter-message" class="form-label">Сообщение</label>
|
||||||
<input type="text" class="form-control" id="logs-filter-message" placeholder="Введите текст">
|
<input type="text" class="form-control" id="logs-filter-message" placeholder="Введите текст">
|
||||||
</div>
|
</div>
|
||||||
|
<div class="col mb-3">
|
||||||
|
<label for="logs-filter-trace" class="form-label">Стек</label>
|
||||||
|
<input type="text" class="form-control" id="logs-filter-trace" placeholder="Введите текст">
|
||||||
|
</div>
|
||||||
|
<!-- Фильтры для таблицы клиентских запросов -->
|
||||||
|
<div class="col mb-3">
|
||||||
|
<label for="logs-filter-request-type" class="form-label">Результат запроса</label>
|
||||||
|
<input type="text" class="form-control" id="logs-filter-request-type" placeholder="Введите текст">
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="col d-flex align-items-end">
|
<div class="col d-flex align-items-end">
|
||||||
|
|||||||
@ -26,7 +26,9 @@ class Component extends EmitService {
|
|||||||
throw new Error(message);
|
throw new Error(message);
|
||||||
}
|
}
|
||||||
this.mainNode = content.firstElementChild.cloneNode(true);
|
this.mainNode = content.firstElementChild.cloneNode(true);
|
||||||
parentNode.appendChild(this.mainNode);
|
if (parentNode) {
|
||||||
|
parentNode.appendChild(this.mainNode);
|
||||||
|
}
|
||||||
this._listeners = [];
|
this._listeners = [];
|
||||||
this._events = {};
|
this._events = {};
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,24 +1,93 @@
|
|||||||
import Component from '../component/index';
|
import Component from '../component/index';
|
||||||
import routeService from '../../services/RouteService';
|
import routeService from '../../services/RouteService';
|
||||||
|
import {createElement} from '../../utils/elementUtils';
|
||||||
|
import {LOG_TYPE, LOG_LABELS} from '../../consts';
|
||||||
|
|
||||||
class LogsFilters extends Component {
|
class LogsFilters extends Component {
|
||||||
constructor (parentNode) {
|
constructor (parentNode) {
|
||||||
super('#logs-filters', parentNode);
|
super('#logs-filters', parentNode);
|
||||||
|
this.initFilter();
|
||||||
|
|
||||||
this.messageInput = this.mainNode.querySelector('#logs-filter-message');
|
this.addEventListener(this.mainNode, 'submit', this.submit);
|
||||||
|
this.addEventListener(this.typeInput, 'change', this.changeType);
|
||||||
|
}
|
||||||
|
|
||||||
const {message = ''} = routeService.getUrlData().query;
|
changeType = () => {
|
||||||
|
const tableType = this.typeInput.value;
|
||||||
|
routeService.pushQuery({tableType}, true);
|
||||||
|
const [showInputs, hideInputs] = (() => {
|
||||||
|
if (tableType === LOG_TYPE.SERVER) {
|
||||||
|
return [this.serverInputs, this.clientInputs];
|
||||||
|
}
|
||||||
|
return [this.clientInputs, this.serverInputs];
|
||||||
|
})();
|
||||||
|
hideInputs.forEach(({input, parentNode}) => {
|
||||||
|
input.value = '';
|
||||||
|
parentNode.style.display = 'none';
|
||||||
|
});
|
||||||
|
showInputs.forEach(({input, parentNode}) => {
|
||||||
|
input.value = '';
|
||||||
|
parentNode.style.display = 'block';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
this.messageInput.value = message;
|
submit = (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
const {query} = routeService.getUrlData();
|
||||||
|
|
||||||
this.addEventListener(this.mainNode, 'submit', (event) => {
|
const inputs = query.tableType === LOG_TYPE.SERVER
|
||||||
event.preventDefault();
|
? this.serverInputs
|
||||||
|
: this.clientInputs;
|
||||||
|
|
||||||
routeService.pushQuery({
|
routeService.pushQuery(inputs.reduce((memo, {name, input}) => ({
|
||||||
message: this.messageInput.value,
|
...memo,
|
||||||
|
[name]: input.value,
|
||||||
|
}), {}));
|
||||||
|
}
|
||||||
|
|
||||||
|
initInput = ({name, id}) => {
|
||||||
|
const input = this.mainNode.querySelector(`#${id}`);
|
||||||
|
return {
|
||||||
|
name,
|
||||||
|
input,
|
||||||
|
parentNode: input.parentNode,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
initFilter = () => {
|
||||||
|
this.initTypeSelect();
|
||||||
|
this.serverInputs = [
|
||||||
|
{name: 'type', id: 'logs-filter-log-type'},
|
||||||
|
{name: 'message', id: 'logs-filter-message'},
|
||||||
|
{name: 'trace', id: 'logs-filter-trace'},
|
||||||
|
].map(this.initInput);
|
||||||
|
this.clientInputs = [
|
||||||
|
{name: 'type', id: 'logs-filter-request-type'},
|
||||||
|
].map(this.initInput);
|
||||||
|
|
||||||
|
this.changeType();
|
||||||
|
}
|
||||||
|
|
||||||
|
initTypeSelect = () => {
|
||||||
|
const {tableType = LOG_TYPE.SERVER} = routeService.getUrlData().query;
|
||||||
|
this.typeInput = this.mainNode.querySelector('#logs-filter-type');
|
||||||
|
LOG_LABELS.forEach(({id, label}) => {
|
||||||
|
createElement({
|
||||||
|
tagName: 'option',
|
||||||
|
parentNode: this.typeInput,
|
||||||
|
options: {
|
||||||
|
textContent: label,
|
||||||
|
},
|
||||||
|
args: {
|
||||||
|
value: id,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
const {query} = routeService.getUrlData();
|
||||||
|
if (!query.type) {
|
||||||
|
routeService.pushQuery({tableType: LOG_TYPE.SERVER});
|
||||||
|
}
|
||||||
|
this.typeInput.value = tableType;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -5,14 +5,8 @@ import Pagination from '../pagination';
|
|||||||
import LogsFilters from '../logs-filters';
|
import LogsFilters from '../logs-filters';
|
||||||
import routeService from '../../services/RouteService';
|
import routeService from '../../services/RouteService';
|
||||||
import {prepareToNumber} from '../../utils/urlUtils';
|
import {prepareToNumber} from '../../utils/urlUtils';
|
||||||
|
import {LOG_TYPE, LOG_COLS} from '../../consts';
|
||||||
const COLS = [
|
import {createElement, markText} from '../../utils/elementUtils';
|
||||||
{id: '_id', label: 'id', width: '240px'},
|
|
||||||
{id: 'date', label: 'Дата', width: '150px'},
|
|
||||||
{id: 'type', label: 'Тип', width: '70px'},
|
|
||||||
{id: 'message', label: 'Сообщение', width: '200px'},
|
|
||||||
{id: 'trace', label: 'Стек', width: '200px'},
|
|
||||||
];
|
|
||||||
|
|
||||||
const ELEMENTS_ON_PAGE = 15;
|
const ELEMENTS_ON_PAGE = 15;
|
||||||
|
|
||||||
@ -20,63 +14,84 @@ class LogsPage extends Component {
|
|||||||
constructor (mainNodeSelector, parentNode) {
|
constructor (mainNodeSelector, parentNode) {
|
||||||
super(mainNodeSelector, parentNode);
|
super(mainNodeSelector, parentNode);
|
||||||
|
|
||||||
this.filters = new LogsFilters(this.mainNode);
|
this.header = createElement({
|
||||||
|
tagName: 'div',
|
||||||
|
parentNode: this.mainNode,
|
||||||
|
});
|
||||||
|
this.body = createElement({
|
||||||
|
tagName: 'div',
|
||||||
|
parentNode: this.mainNode,
|
||||||
|
});
|
||||||
|
this.footer = createElement({
|
||||||
|
tagName: 'div',
|
||||||
|
parentNode: this.mainNode,
|
||||||
|
});
|
||||||
|
|
||||||
this.table = new Table(this.mainNode, COLS);
|
this.filters = new LogsFilters(this.header);
|
||||||
|
|
||||||
this.pagination = new Pagination(this.mainNode);
|
this.tables = {
|
||||||
|
[LOG_TYPE.SERVER]: new Table(null, LOG_COLS[LOG_TYPE.SERVER]),
|
||||||
|
[LOG_TYPE.CLIENT]: new Table(null, LOG_COLS[LOG_TYPE.CLIENT]),
|
||||||
|
};
|
||||||
|
|
||||||
|
this.pagination = new Pagination(this.footer);
|
||||||
|
|
||||||
routeService.onChange(this.renderTable);
|
routeService.onChange(this.renderTable);
|
||||||
|
|
||||||
this.pagination.onPageChange((pageNumber) => {
|
this.initPage();
|
||||||
const start = (pageNumber - 1) * ELEMENTS_ON_PAGE;
|
|
||||||
const end = start + ELEMENTS_ON_PAGE;
|
|
||||||
const rows = this.logList.slice(start, end + 1);
|
|
||||||
|
|
||||||
this.table.render(rows);
|
|
||||||
});
|
|
||||||
|
|
||||||
this.loadLogList();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
loadLogList = async () => {
|
initPage = async () => {
|
||||||
this.logList = await storageLogsApi.request();
|
this.logList = {
|
||||||
|
[LOG_TYPE.SERVER]: await storageLogsApi.requestServerLogs(),
|
||||||
|
[LOG_TYPE.CLIENT]: await storageLogsApi.requestClientLogs(),
|
||||||
|
};
|
||||||
this.renderTable();
|
this.renderTable();
|
||||||
}
|
}
|
||||||
|
|
||||||
filterRows = (queryMessage) => {
|
filterRows = (rows, query) => {
|
||||||
return this.logList.reduce((memo, row) => {
|
const filteredRows = rows.filter((row) => {
|
||||||
const message = row.message.toLowerCase();
|
return Object.entries(query).every(([key, value]) => {
|
||||||
const searchMessage = (queryMessage || '').toLowerCase();
|
const rowValue = row[key];
|
||||||
if (searchMessage === '') {
|
if (!rowValue) {
|
||||||
memo.push(row);
|
return true;
|
||||||
return memo;
|
}
|
||||||
}
|
return rowValue.toLowerCase().includes(value.toLowerCase());
|
||||||
if (message.includes(searchMessage)) {
|
});
|
||||||
const replaceMessage = new RegExp(searchMessage, 'g');
|
});
|
||||||
const newText = message.replace(replaceMessage, `<span class="text-warning bg-dark">${searchMessage}</span>`);
|
|
||||||
memo.push({
|
return filteredRows.map((row) => {
|
||||||
...row,
|
return Object.entries(query).reduce((memo, [key, searchMessage]) => {
|
||||||
message: newText,
|
const rowValue = memo[key];
|
||||||
});
|
if (!rowValue) {
|
||||||
}
|
return memo;
|
||||||
return memo;
|
}
|
||||||
}, []);
|
return {
|
||||||
|
...memo,
|
||||||
|
[key]: markText(searchMessage, rowValue),
|
||||||
|
};
|
||||||
|
}, row);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
cutPagginationPage = (filteredRows, queryPageNumber) => {
|
||||||
|
const countPages = Math.ceil(filteredRows.length / ELEMENTS_ON_PAGE);
|
||||||
|
const pageNumber = prepareToNumber(queryPageNumber, countPages);
|
||||||
|
this.pagination.changeCountPages(countPages);
|
||||||
|
const start = (pageNumber - 1) * ELEMENTS_ON_PAGE;
|
||||||
|
const end = start + ELEMENTS_ON_PAGE;
|
||||||
|
return filteredRows.slice(start, end + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
renderTable = () => {
|
renderTable = () => {
|
||||||
const {query} = routeService.getUrlData();
|
const {query} = routeService.getUrlData();
|
||||||
const filterRows = this.filterRows(query.message);
|
const {tableType, ...omitQuery} = query;
|
||||||
const countPages = Math.ceil(filterRows.length / ELEMENTS_ON_PAGE);
|
this.table?.mainNode?.remove();
|
||||||
const pageNumber = prepareToNumber(query.pageNumber, countPages);
|
this.table = this.tables[tableType];
|
||||||
this.pagination.changeCountPages(countPages);
|
this.body.appendChild(this.table.mainNode);
|
||||||
|
const filteredRows = this.filterRows(this.logList[tableType], omitQuery);
|
||||||
const start = (pageNumber - 1) * ELEMENTS_ON_PAGE;
|
const rows = this.cutPagginationPage(filteredRows, query.pageNumber);
|
||||||
const end = start + ELEMENTS_ON_PAGE;
|
|
||||||
const rows = filterRows.slice(start, end + 1);
|
|
||||||
|
|
||||||
this.table.render(rows);
|
this.table.render(rows);
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
31
src/consts.js
Normal file
31
src/consts.js
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
export const LOG_TYPE = {
|
||||||
|
SERVER: 'server-logs',
|
||||||
|
CLIENT: 'client-logs',
|
||||||
|
};
|
||||||
|
|
||||||
|
export const LOG_LABELS = [
|
||||||
|
{id: LOG_TYPE.SERVER, label: 'Ошибки сервера'},
|
||||||
|
{id: LOG_TYPE.CLIENT, label: 'Запросы клиентов'},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const SERVER_COLS = [
|
||||||
|
{id: '_id', label: 'id', width: '240px'},
|
||||||
|
{id: 'date', label: 'Дата', width: '150px'},
|
||||||
|
{id: 'type', label: 'Тип записи', width: '120px'},
|
||||||
|
{id: 'message', label: 'Сообщение', width: '200px'},
|
||||||
|
{id: 'trace', label: 'Стек', width: '200px'},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const CLIENT_COLS = [
|
||||||
|
{id: '_id', label: 'id', width: '240px'},
|
||||||
|
{id: 'type', label: 'Результат запроса'},
|
||||||
|
{id: 'request', label: 'Запрос клиента', width: '240px'},
|
||||||
|
{id: 'response', label: 'Ответ сервера', width: '240px'},
|
||||||
|
{id: 'startTime', label: 'Начало запроса', width: '150px'},
|
||||||
|
{id: 'endTime', label: 'Окончание запроса', width: '150px'},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const LOG_COLS = {
|
||||||
|
[LOG_TYPE.SERVER]: SERVER_COLS,
|
||||||
|
[LOG_TYPE.CLIENT]: CLIENT_COLS,
|
||||||
|
};
|
||||||
@ -32,3 +32,17 @@ export const createElement = (createElementProps) => {
|
|||||||
parentNode.appendChild(element);
|
parentNode.appendChild(element);
|
||||||
return element;
|
return element;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Функция для поиска сообщений в тексте и их маркирование в этом тексте.
|
||||||
|
* @param {string} searchMessage - сообщение, которое нужно найти в тексте
|
||||||
|
* @param {string} text - исходный текст, в котором нужно выполнить поиск и промаркировать все сообщения
|
||||||
|
* @param {string} styleClassName - класс, которым нужно выделить сообщения. Выставлять не обязательно, тк есть
|
||||||
|
* дефолтное значение
|
||||||
|
*/
|
||||||
|
export const markText = (searchMessage, text, styleClassName = 'text-warning bg-dark') => {
|
||||||
|
const replaceMessage = new RegExp(searchMessage, 'gi');
|
||||||
|
return text.replace(replaceMessage, (match) => (
|
||||||
|
`<span class="${styleClassName}">${match}</span>`
|
||||||
|
));
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user