HM-71. Доработки таблицы логов. Универсальной таблице добавлена возможность переопределять рендер строк и заголовков (#31)
This commit is contained in:
@ -57,6 +57,7 @@
|
|||||||
"dependencies": {
|
"dependencies": {
|
||||||
"axios": "^0.19.2",
|
"axios": "^0.19.2",
|
||||||
"bootstrap": "^5.0.0-alpha1",
|
"bootstrap": "^5.0.0-alpha1",
|
||||||
|
"moment": "^2.27.0",
|
||||||
"popper.js": "^1.16.1",
|
"popper.js": "^1.16.1",
|
||||||
"query-string": "^6.13.1",
|
"query-string": "^6.13.1",
|
||||||
"uuid": "^8.2.0"
|
"uuid": "^8.2.0"
|
||||||
|
|||||||
15
src/components/client-logs-table/ClientLogsTable.js
Normal file
15
src/components/client-logs-table/ClientLogsTable.js
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import Table from '../table';
|
||||||
|
import {LOG_COLS, LOG_TYPE} from '../../consts';
|
||||||
|
import ClientLogsTableRow from './ClientLogsTableRow';
|
||||||
|
|
||||||
|
class ClientLogsTable extends Table {
|
||||||
|
constructor () {
|
||||||
|
super(null, LOG_COLS[LOG_TYPE.CLIENT]);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderRow = (parentNode, cols, row) => {
|
||||||
|
return new ClientLogsTableRow(parentNode, cols, row);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ClientLogsTable;
|
||||||
12
src/components/client-logs-table/ClientLogsTableRow.js
Normal file
12
src/components/client-logs-table/ClientLogsTableRow.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import Component from '../component';
|
||||||
|
import TableCellOverflow from '../table-cell-overflow';
|
||||||
|
|
||||||
|
class ClientLogsTableRow extends Component {
|
||||||
|
constructor (parentNode, cols, row) {
|
||||||
|
super(null, parentNode);
|
||||||
|
|
||||||
|
this.cols = cols.map((col) => new TableCellOverflow(this.mainNode, row[col.id]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ClientLogsTableRow;
|
||||||
3
src/components/client-logs-table/index.js
Normal file
3
src/components/client-logs-table/index.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import ClientLogsTable from './ClientLogsTable';
|
||||||
|
|
||||||
|
export default ClientLogsTable;
|
||||||
@ -26,15 +26,25 @@ class Component extends EmitService {
|
|||||||
|
|
||||||
constructor (mainNodeSelector, parentNode) {
|
constructor (mainNodeSelector, parentNode) {
|
||||||
super();
|
super();
|
||||||
|
if (!mainNodeSelector && !parentNode) {
|
||||||
|
throw new Error('Компонент должен содержать хотябы селектор или родительский Node');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mainNodeSelector) {
|
||||||
const content = document.querySelector(mainNodeSelector).content;
|
const content = document.querySelector(mainNodeSelector).content;
|
||||||
if (content.children.length > 1) {
|
if (content.children.length > 1) {
|
||||||
const message = '<template> должен содержать только один элемент children';
|
const message = '<template> должен содержать только один элемент children';
|
||||||
throw new Error(message);
|
throw new Error(message);
|
||||||
}
|
}
|
||||||
this.mainNode = content.firstElementChild.cloneNode(true);
|
this.mainNode = content.firstElementChild.cloneNode(true);
|
||||||
|
|
||||||
if (parentNode) {
|
if (parentNode) {
|
||||||
parentNode.appendChild(this.mainNode);
|
parentNode.appendChild(this.mainNode);
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
this.mainNode = parentNode;
|
||||||
|
}
|
||||||
|
|
||||||
this._listeners = [];
|
this._listeners = [];
|
||||||
this._events = {};
|
this._events = {};
|
||||||
this._isAlive = true;
|
this._isAlive = true;
|
||||||
|
|||||||
@ -1,12 +1,13 @@
|
|||||||
import Component from '../component/index';
|
import Component from '../component/index';
|
||||||
import Table from '../table';
|
|
||||||
import storageLogsApi from '../../api/StorageLogsAPI';
|
import storageLogsApi from '../../api/StorageLogsAPI';
|
||||||
import Pagination from '../pagination';
|
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, EVENTS} from '../../consts';
|
import {LOG_TYPE, EVENTS} from '../../consts';
|
||||||
import {createElement, markText} from '../../utils/elementUtils';
|
import {createElement, markText, prepareServerDate, prepareObjectToString} from '../../utils/elementUtils';
|
||||||
|
import ServerLogsTable from '../server-logs-table';
|
||||||
|
import ClientLogsTable from '../client-logs-table';
|
||||||
|
|
||||||
const ELEMENTS_ON_PAGE = 15;
|
const ELEMENTS_ON_PAGE = 15;
|
||||||
|
|
||||||
@ -30,8 +31,8 @@ class LogsPage extends Component {
|
|||||||
this.filters = new LogsFilters(this.header);
|
this.filters = new LogsFilters(this.header);
|
||||||
|
|
||||||
this.tables = {
|
this.tables = {
|
||||||
[LOG_TYPE.SERVER]: new Table(null, LOG_COLS[LOG_TYPE.SERVER]),
|
[LOG_TYPE.SERVER]: new ServerLogsTable(),
|
||||||
[LOG_TYPE.CLIENT]: new Table(null, LOG_COLS[LOG_TYPE.CLIENT]),
|
[LOG_TYPE.CLIENT]: new ClientLogsTable(),
|
||||||
};
|
};
|
||||||
|
|
||||||
this.pagination = new Pagination(this.footer);
|
this.pagination = new Pagination(this.footer);
|
||||||
@ -86,6 +87,23 @@ class LogsPage extends Component {
|
|||||||
return filteredRows.slice(start, end + 1);
|
return filteredRows.slice(start, end + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
prepareCells = (columns, prepareFunction) => {
|
||||||
|
return Object.entries(columns).reduce((memo, [colName, colValue]) => ({
|
||||||
|
...memo,
|
||||||
|
...(colValue ? {
|
||||||
|
[colName]: prepareFunction(colValue),
|
||||||
|
} : {}),
|
||||||
|
}), {});
|
||||||
|
}
|
||||||
|
|
||||||
|
prepareRows = (rows) => {
|
||||||
|
return rows.map(({startTime, endTime, date, request, response, ...omitProps}) => ({
|
||||||
|
...omitProps,
|
||||||
|
...this.prepareCells({startTime, endTime, date}, prepareServerDate),
|
||||||
|
...this.prepareCells({request, response}, prepareObjectToString),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
|
||||||
renderTable = () => {
|
renderTable = () => {
|
||||||
this.render(() => {
|
this.render(() => {
|
||||||
const {query} = routeService.getUrlData();
|
const {query} = routeService.getUrlData();
|
||||||
@ -95,7 +113,8 @@ class LogsPage extends Component {
|
|||||||
this.body.appendChild(this.table.mainNode);
|
this.body.appendChild(this.table.mainNode);
|
||||||
const filteredRows = this.filterRows(this.logList[tableType], omitQuery);
|
const filteredRows = this.filterRows(this.logList[tableType], omitQuery);
|
||||||
const rows = this.cutPagginationPage(filteredRows, query.pageNumber);
|
const rows = this.cutPagginationPage(filteredRows, query.pageNumber);
|
||||||
this.table.render(rows);
|
|
||||||
|
this.table.render(this.prepareRows(rows));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
16
src/components/server-logs-table/ServerLogsTable.js
Normal file
16
src/components/server-logs-table/ServerLogsTable.js
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
import Table from '../table';
|
||||||
|
import {LOG_TYPE, LOG_COLS} from '../../consts';
|
||||||
|
import ServerLogsTableRow from './ServerLogsTableRow';
|
||||||
|
|
||||||
|
class ServerLogsTable extends Table {
|
||||||
|
constructor () {
|
||||||
|
super(null, LOG_COLS[LOG_TYPE.SERVER]);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderRow = (parentNode, cols, row) => {
|
||||||
|
return new ServerLogsTableRow(parentNode, cols, row);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ServerLogsTable;
|
||||||
12
src/components/server-logs-table/ServerLogsTableRow.js
Normal file
12
src/components/server-logs-table/ServerLogsTableRow.js
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import Component from '../component';
|
||||||
|
import TableCellOverflow from '../table-cell-overflow';
|
||||||
|
|
||||||
|
class ServerLogsTableRow extends Component {
|
||||||
|
constructor (parentNode, cols, row) {
|
||||||
|
super(null, parentNode);
|
||||||
|
|
||||||
|
this.cols = cols.map((col) => new TableCellOverflow(this.mainNode, row[col.id]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default ServerLogsTableRow;
|
||||||
3
src/components/server-logs-table/index.js
Normal file
3
src/components/server-logs-table/index.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import ServerLogsTable from './ServerLogsTable';
|
||||||
|
|
||||||
|
export default ServerLogsTable;
|
||||||
14
src/components/table-cell-overflow/TableCellOverflow.css
Normal file
14
src/components/table-cell-overflow/TableCellOverflow.css
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
.TableCellOverflow__cellWrapper {
|
||||||
|
max-width: 300px;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
|
||||||
|
.TableCellOverflow__cell {
|
||||||
|
max-width: 100%;
|
||||||
|
min-width: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
33
src/components/table-cell-overflow/TableCellOverflow.js
Normal file
33
src/components/table-cell-overflow/TableCellOverflow.js
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
import Component from '../component';
|
||||||
|
import {createElement} from '../../utils/elementUtils';
|
||||||
|
import './TableCellOverflow.css';
|
||||||
|
|
||||||
|
class TableCellOverflow extends Component {
|
||||||
|
constructor (parentNode, text) {
|
||||||
|
super(null, parentNode);
|
||||||
|
|
||||||
|
const cell = createElement({
|
||||||
|
tagName: 'td',
|
||||||
|
parentNode: this.mainNode,
|
||||||
|
});
|
||||||
|
|
||||||
|
const div = createElement({
|
||||||
|
tagName: 'div',
|
||||||
|
parentNode: cell,
|
||||||
|
options: {
|
||||||
|
className: 'TableCellOverflow__cellWrapper'
|
||||||
|
}
|
||||||
|
});
|
||||||
|
const span = createElement({
|
||||||
|
tagName: 'span',
|
||||||
|
parentNode: div,
|
||||||
|
options: {
|
||||||
|
className: 'TableCellOverflow__cell',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
span.innerHTML = text;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TableCellOverflow;
|
||||||
3
src/components/table-cell-overflow/index.js
Normal file
3
src/components/table-cell-overflow/index.js
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
import TableCellOverflow from './TableCellOverflow';
|
||||||
|
|
||||||
|
export default TableCellOverflow;
|
||||||
@ -5,8 +5,8 @@ class HeaderCol extends Component {
|
|||||||
super('#uni-table-th', parentNode);
|
super('#uni-table-th', parentNode);
|
||||||
|
|
||||||
this.id = col.id;
|
this.id = col.id;
|
||||||
this.mainNode.textContent = col.label;
|
|
||||||
this.mainNode.style.minWidth = col.width ?? '';
|
this.mainNode.style.minWidth = col.width ?? '';
|
||||||
|
this.mainNode.textContent = col.label;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
18
src/components/table/RowCol.js
Normal file
18
src/components/table/RowCol.js
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import Component from '../component';
|
||||||
|
import {createElement} from '../../utils/elementUtils';
|
||||||
|
|
||||||
|
class RowCol extends Component {
|
||||||
|
constructor (parentNode, text) {
|
||||||
|
super(null, parentNode);
|
||||||
|
|
||||||
|
createElement({
|
||||||
|
tagName: 'td',
|
||||||
|
parentNode: this.mainNode,
|
||||||
|
options: {
|
||||||
|
innerHTML: text,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default RowCol;
|
||||||
@ -1,8 +1,8 @@
|
|||||||
import Component from '../component/index';
|
import Component from '../component/index';
|
||||||
import HeaderCol from './HeaderCol';
|
import HeaderCol from './HeaderCol';
|
||||||
import TableRow from './TableRow';
|
import TableRow from './TableRow';
|
||||||
|
import TableRowWrapper from './TableRowWrapper';
|
||||||
const ROW_CLICK = 'ROW_CLICK';
|
import {EVENTS} from '../../consts';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @interface Col
|
* @interface Col
|
||||||
@ -33,6 +33,8 @@ class Table extends Component {
|
|||||||
constructor (parentNode, cols) {
|
constructor (parentNode, cols) {
|
||||||
super('#uni-table', parentNode);
|
super('#uni-table', parentNode);
|
||||||
|
|
||||||
|
this.mainNode.classList.add('Table');
|
||||||
|
|
||||||
this.theadTr = this.mainNode.querySelector('thead tr');
|
this.theadTr = this.mainNode.querySelector('thead tr');
|
||||||
this.tbody = this.mainNode.querySelector('tbody');
|
this.tbody = this.mainNode.querySelector('tbody');
|
||||||
|
|
||||||
@ -40,6 +42,14 @@ class Table extends Component {
|
|||||||
this.renderHeader();
|
this.renderHeader();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
renderHeaderCol = (col) => {
|
||||||
|
return new HeaderCol(this.theadTr, col);
|
||||||
|
}
|
||||||
|
|
||||||
|
renderRow = (parentNode, cols, row) => {
|
||||||
|
return new TableRow(parentNode, cols, row);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Метод нужен для отрисовки шапки таблицы, вызывается конструктором самостоятельно
|
* Метод нужен для отрисовки шапки таблицы, вызывается конструктором самостоятельно
|
||||||
*/
|
*/
|
||||||
@ -49,7 +59,8 @@ class Table extends Component {
|
|||||||
col.destroy();
|
col.destroy();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
this.headerCols = this.cols.map((col) => new HeaderCol(this.theadTr, col));
|
|
||||||
|
this.headerCols = this.cols.map(this.renderHeaderCol);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -58,32 +69,24 @@ class Table extends Component {
|
|||||||
*/
|
*/
|
||||||
render = (rows) => {
|
render = (rows) => {
|
||||||
if (this.rows) {
|
if (this.rows) {
|
||||||
this.rows.forEach((row) => {
|
this.rows.forEach(({tr, rowComponent}) => {
|
||||||
row.destroy();
|
tr.destroy();
|
||||||
|
rowComponent.destroy();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
this.rows = rows.map((row) => {
|
this.rows = rows.map((row) => {
|
||||||
const rowComponent = new TableRow(this.tbody, this.cols, row);
|
const tr = new TableRowWrapper(this.tbody, row);
|
||||||
rowComponent.onClick((event) => {
|
this.addSubscribe(tr, EVENTS.ROW_CLICK, (event) => {
|
||||||
this.next(ROW_CLICK, event, row);
|
this.next(EVENTS.ROW_CLICK, event, row);
|
||||||
});
|
});
|
||||||
return rowComponent;
|
const rowComponent = this.renderRow(tr.mainNode, this.cols, row);
|
||||||
|
return {
|
||||||
|
tr,
|
||||||
|
rowComponent,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Метод для подписки на клики по строчкам, передает event и данные строчки
|
|
||||||
* @param {Function} listener - слушатель
|
|
||||||
*
|
|
||||||
* @example
|
|
||||||
* table.onRowClick((event, row) => {
|
|
||||||
* // Ваш код
|
|
||||||
* });
|
|
||||||
*/
|
|
||||||
onRowClick = (listener) => {
|
|
||||||
this.subscribe(ROW_CLICK, listener);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export default Table;
|
export default Table;
|
||||||
|
|||||||
@ -1,30 +1,11 @@
|
|||||||
import Component from '../component/index';
|
import Component from '../component/index';
|
||||||
import {createElement} from '../../utils/elementUtils';
|
import RowCol from './RowCol';
|
||||||
|
|
||||||
const CLICK = 'click';
|
|
||||||
|
|
||||||
class TableRow extends Component {
|
class TableRow extends Component {
|
||||||
constructor (parentNode, cols, row) {
|
constructor (parentNode, cols, row) {
|
||||||
super('#uni-table-row', parentNode);
|
super(null, parentNode);
|
||||||
|
|
||||||
cols.forEach((col) => {
|
this.cols = cols.map((col) => new RowCol(this.mainNode, row[col.id]));
|
||||||
const cell = createElement({
|
|
||||||
tagName: 'td',
|
|
||||||
parentNode: this.mainNode,
|
|
||||||
options: {
|
|
||||||
className: 'text-break'
|
|
||||||
},
|
|
||||||
});
|
|
||||||
cell.innerHTML = row[col.id];
|
|
||||||
});
|
|
||||||
|
|
||||||
this.mainNode.addEventListener(CLICK, (event) => {
|
|
||||||
this.next(CLICK, event);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
onClick = (listener) => {
|
|
||||||
this.subscribe(CLICK, listener);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
14
src/components/table/TableRowWrapper.js
Normal file
14
src/components/table/TableRowWrapper.js
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import Component from '../component';
|
||||||
|
import {EVENTS} from '../../consts';
|
||||||
|
|
||||||
|
class TableRowWrapper extends Component {
|
||||||
|
constructor (parentNode) {
|
||||||
|
super('#uni-table-row', parentNode);
|
||||||
|
|
||||||
|
this.addEventListener(this.mainNode, 'click', () => {
|
||||||
|
this.next(EVENTS.ROW_CLICK);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default TableRowWrapper;
|
||||||
@ -18,11 +18,11 @@ export const SERVER_COLS = [
|
|||||||
|
|
||||||
export const CLIENT_COLS = [
|
export const CLIENT_COLS = [
|
||||||
{id: '_id', label: 'id', width: '240px'},
|
{id: '_id', label: 'id', width: '240px'},
|
||||||
{id: 'type', label: 'Результат запроса'},
|
{id: 'type', label: 'Результат', width: '100px'},
|
||||||
{id: 'request', label: 'Запрос клиента', width: '240px'},
|
{id: 'request', label: 'Запрос клиента', width: '240px'},
|
||||||
{id: 'response', label: 'Ответ сервера', width: '240px'},
|
{id: 'response', label: 'Ответ сервера'},
|
||||||
{id: 'startTime', label: 'Начало запроса', width: '150px'},
|
{id: 'startTime', label: 'Начало', width: '150px'},
|
||||||
{id: 'endTime', label: 'Окончание запроса', width: '150px'},
|
{id: 'endTime', label: 'Окончание', width: '150px'},
|
||||||
];
|
];
|
||||||
|
|
||||||
export const LOG_COLS = {
|
export const LOG_COLS = {
|
||||||
@ -32,5 +32,6 @@ export const LOG_COLS = {
|
|||||||
|
|
||||||
export const EVENTS = {
|
export const EVENTS = {
|
||||||
ROUTE_CHANGE: 'routeChange',
|
ROUTE_CHANGE: 'routeChange',
|
||||||
ROUTE_SEARCH_CHANGE: 'routeSearchChange'
|
ROUTE_SEARCH_CHANGE: 'routeSearchChange',
|
||||||
|
ROW_CLICK: 'rowClick',
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,3 +1,5 @@
|
|||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @interface CreateElementProps
|
* @interface CreateElementProps
|
||||||
* @type {Object}
|
* @type {Object}
|
||||||
@ -46,3 +48,11 @@ export const markText = (searchMessage, text, styleClassName = 'text-warning bg-
|
|||||||
`<span class="${styleClassName}">${match}</span>`
|
`<span class="${styleClassName}">${match}</span>`
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const prepareServerDate = (stringDate) => {
|
||||||
|
return moment(stringDate).format('DD/MM/YYYY hh:mm:ss');
|
||||||
|
};
|
||||||
|
|
||||||
|
export const prepareObjectToString = (object) => {
|
||||||
|
return JSON.stringify(object);
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user