HM-71. Доработки таблицы логов. Универсальной таблице добавлена возможность переопределять рендер строк и заголовков (#31)
This commit is contained in:
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) {
|
||||
super();
|
||||
const content = document.querySelector(mainNodeSelector).content;
|
||||
if (content.children.length > 1) {
|
||||
const message = '<template> должен содержать только один элемент children';
|
||||
throw new Error(message);
|
||||
if (!mainNodeSelector && !parentNode) {
|
||||
throw new Error('Компонент должен содержать хотябы селектор или родительский Node');
|
||||
}
|
||||
this.mainNode = content.firstElementChild.cloneNode(true);
|
||||
if (parentNode) {
|
||||
parentNode.appendChild(this.mainNode);
|
||||
|
||||
if (mainNodeSelector) {
|
||||
const content = document.querySelector(mainNodeSelector).content;
|
||||
if (content.children.length > 1) {
|
||||
const message = '<template> должен содержать только один элемент children';
|
||||
throw new Error(message);
|
||||
}
|
||||
this.mainNode = content.firstElementChild.cloneNode(true);
|
||||
|
||||
if (parentNode) {
|
||||
parentNode.appendChild(this.mainNode);
|
||||
}
|
||||
} else {
|
||||
this.mainNode = parentNode;
|
||||
}
|
||||
|
||||
this._listeners = [];
|
||||
this._events = {};
|
||||
this._isAlive = true;
|
||||
|
||||
@ -1,12 +1,13 @@
|
||||
import Component from '../component/index';
|
||||
import Table from '../table';
|
||||
import storageLogsApi from '../../api/StorageLogsAPI';
|
||||
import Pagination from '../pagination';
|
||||
import LogsFilters from '../logs-filters';
|
||||
import routeService from '../../services/RouteService';
|
||||
import {prepareToNumber} from '../../utils/urlUtils';
|
||||
import {LOG_TYPE, LOG_COLS, EVENTS} from '../../consts';
|
||||
import {createElement, markText} from '../../utils/elementUtils';
|
||||
import {LOG_TYPE, EVENTS} from '../../consts';
|
||||
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;
|
||||
|
||||
@ -30,8 +31,8 @@ class LogsPage extends Component {
|
||||
this.filters = new LogsFilters(this.header);
|
||||
|
||||
this.tables = {
|
||||
[LOG_TYPE.SERVER]: new Table(null, LOG_COLS[LOG_TYPE.SERVER]),
|
||||
[LOG_TYPE.CLIENT]: new Table(null, LOG_COLS[LOG_TYPE.CLIENT]),
|
||||
[LOG_TYPE.SERVER]: new ServerLogsTable(),
|
||||
[LOG_TYPE.CLIENT]: new ClientLogsTable(),
|
||||
};
|
||||
|
||||
this.pagination = new Pagination(this.footer);
|
||||
@ -86,6 +87,23 @@ class LogsPage extends Component {
|
||||
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 = () => {
|
||||
this.render(() => {
|
||||
const {query} = routeService.getUrlData();
|
||||
@ -95,7 +113,8 @@ class LogsPage extends Component {
|
||||
this.body.appendChild(this.table.mainNode);
|
||||
const filteredRows = this.filterRows(this.logList[tableType], omitQuery);
|
||||
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);
|
||||
|
||||
this.id = col.id;
|
||||
this.mainNode.textContent = col.label;
|
||||
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 HeaderCol from './HeaderCol';
|
||||
import TableRow from './TableRow';
|
||||
|
||||
const ROW_CLICK = 'ROW_CLICK';
|
||||
import TableRowWrapper from './TableRowWrapper';
|
||||
import {EVENTS} from '../../consts';
|
||||
|
||||
/**
|
||||
* @interface Col
|
||||
@ -33,6 +33,8 @@ class Table extends Component {
|
||||
constructor (parentNode, cols) {
|
||||
super('#uni-table', parentNode);
|
||||
|
||||
this.mainNode.classList.add('Table');
|
||||
|
||||
this.theadTr = this.mainNode.querySelector('thead tr');
|
||||
this.tbody = this.mainNode.querySelector('tbody');
|
||||
|
||||
@ -40,6 +42,14 @@ class Table extends Component {
|
||||
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();
|
||||
});
|
||||
}
|
||||
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) => {
|
||||
if (this.rows) {
|
||||
this.rows.forEach((row) => {
|
||||
row.destroy();
|
||||
this.rows.forEach(({tr, rowComponent}) => {
|
||||
tr.destroy();
|
||||
rowComponent.destroy();
|
||||
});
|
||||
}
|
||||
|
||||
this.rows = rows.map((row) => {
|
||||
const rowComponent = new TableRow(this.tbody, this.cols, row);
|
||||
rowComponent.onClick((event) => {
|
||||
this.next(ROW_CLICK, event, row);
|
||||
const tr = new TableRowWrapper(this.tbody, row);
|
||||
this.addSubscribe(tr, EVENTS.ROW_CLICK, (event) => {
|
||||
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;
|
||||
|
||||
@ -1,30 +1,11 @@
|
||||
import Component from '../component/index';
|
||||
import {createElement} from '../../utils/elementUtils';
|
||||
|
||||
const CLICK = 'click';
|
||||
import RowCol from './RowCol';
|
||||
|
||||
class TableRow extends Component {
|
||||
constructor (parentNode, cols, row) {
|
||||
super('#uni-table-row', parentNode);
|
||||
super(null, parentNode);
|
||||
|
||||
cols.forEach((col) => {
|
||||
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);
|
||||
this.cols = cols.map((col) => new RowCol(this.mainNode, row[col.id]));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
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 = [
|
||||
{id: '_id', label: 'id', width: '240px'},
|
||||
{id: 'type', label: 'Результат запроса'},
|
||||
{id: 'type', label: 'Результат', width: '100px'},
|
||||
{id: 'request', label: 'Запрос клиента', width: '240px'},
|
||||
{id: 'response', label: 'Ответ сервера', width: '240px'},
|
||||
{id: 'startTime', label: 'Начало запроса', width: '150px'},
|
||||
{id: 'endTime', label: 'Окончание запроса', width: '150px'},
|
||||
{id: 'response', label: 'Ответ сервера'},
|
||||
{id: 'startTime', label: 'Начало', width: '150px'},
|
||||
{id: 'endTime', label: 'Окончание', width: '150px'},
|
||||
];
|
||||
|
||||
export const LOG_COLS = {
|
||||
@ -32,5 +32,6 @@ export const LOG_COLS = {
|
||||
|
||||
export const EVENTS = {
|
||||
ROUTE_CHANGE: 'routeChange',
|
||||
ROUTE_SEARCH_CHANGE: 'routeSearchChange'
|
||||
ROUTE_SEARCH_CHANGE: 'routeSearchChange',
|
||||
ROW_CLICK: 'rowClick',
|
||||
};
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
import moment from 'moment';
|
||||
|
||||
/**
|
||||
* @interface CreateElementProps
|
||||
* @type {Object}
|
||||
@ -46,3 +48,11 @@ export const markText = (searchMessage, text, styleClassName = 'text-warning bg-
|
||||
`<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