HM-74. Добавлена модалка-сайдбар. Реализована модалка для просмотра логов. (#32)
This commit is contained in:
@ -52,7 +52,6 @@
|
||||
"eol-last": ["warn", "always"],
|
||||
"func-call-spacing": ["warn", "never"],
|
||||
"func-style": ["warn", "expression"],
|
||||
"indent": ["warn", 4],
|
||||
"keyword-spacing": ["warn", {"before": true}],
|
||||
"line-comment-position": ["warn", {"position": "above"}],
|
||||
"lines-between-class-members": ["warn", "always"],
|
||||
|
||||
13
src/app.css
13
src/app.css
@ -1,4 +1,17 @@
|
||||
html, body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: none;
|
||||
}
|
||||
|
||||
.Logo {
|
||||
width: 50px;
|
||||
margin: 0 20px;
|
||||
}
|
||||
|
||||
.overflow-auto::-webkit-scrollbar {
|
||||
width: 0px;
|
||||
background: transparent;
|
||||
}
|
||||
|
||||
32
src/app.html
32
src/app.html
@ -5,6 +5,7 @@
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>Storage Service</title>
|
||||
<script src="https://kit.fontawesome.com/008e1ef4e8.js" crossorigin="anonymous"></script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
@ -32,7 +33,7 @@
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
</nav>
|
||||
|
||||
</template>
|
||||
<!-- Шаблон контейнера страниц-->
|
||||
@ -224,6 +225,35 @@
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Шаблоны форм для просмотры логов -->
|
||||
<template id="logs-view-form">
|
||||
<div class="h-100 overflow-auto">
|
||||
<p class="h2 mb-2 p-3 pr-5 sticky-top border-bottom bg-light"></p>
|
||||
<form class="p-3"></form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Шаблон FormControl компонента -->
|
||||
<template id="form-control">
|
||||
<div class="mb-3">
|
||||
<label class="form-label"></label>
|
||||
<div class="form-text"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
|
||||
<!-- Шаблон для Модального Сайдбара -->
|
||||
<template id="modal-sidebar">
|
||||
<div class="ModalSidebar row justify-content-end m-0">
|
||||
<div class="ModalSidebar__window col-12 col-md-9 col-lg-8 col-xl-7 p-0">
|
||||
<button class="ModalSidebar__close d-flex justify-center align-items-center" type="button">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Шаблон для создания апи -->
|
||||
<template id="create-api">
|
||||
<div class="card text-center Info__container">
|
||||
|
||||
@ -0,0 +1,7 @@
|
||||
.ClientLogsViewForm__headersInput {
|
||||
height: 200px;
|
||||
}
|
||||
|
||||
.ClientLogsViewForm__responseInput {
|
||||
height: 600px;
|
||||
}
|
||||
82
src/components/client-logs-view-form/ClientLogsViewForm.js
Normal file
82
src/components/client-logs-view-form/ClientLogsViewForm.js
Normal file
@ -0,0 +1,82 @@
|
||||
import Component from '../component';
|
||||
import FormControl from '../form-control';
|
||||
import {FORM_TYPES} from '../../consts';
|
||||
import ModalSidebar from '../modal-sidebar';
|
||||
import './ClientLogsViewForm.css';
|
||||
|
||||
class ClientLogsViewForm extends Component {
|
||||
constructor (parentNode) {
|
||||
super('#logs-view-form', parentNode);
|
||||
|
||||
this.sidebar = new ModalSidebar({
|
||||
content: this.mainNode,
|
||||
});
|
||||
|
||||
this.title = this.mainNode.querySelector('.h2');
|
||||
this.form = this.mainNode.querySelector('form');
|
||||
|
||||
this.title.textContent = 'Просмотр клиентского запроса';
|
||||
|
||||
const inputs = [
|
||||
this.idInput = new FormControl(this.form, {
|
||||
id: 'client-logs-view-form-id',
|
||||
label: 'id',
|
||||
}),
|
||||
this.resultInput = new FormControl(this.form, {
|
||||
id: 'client-logs-view-form-result',
|
||||
label: 'Результат',
|
||||
}),
|
||||
this.startTimeInput = new FormControl(this.form, {
|
||||
id: 'client-logs-view-form-startTime',
|
||||
label: 'Время запроса',
|
||||
}),
|
||||
this.headersInput = new FormControl(this.form, {
|
||||
id: 'client-logs-view-form-headers',
|
||||
label: 'Заголовки запроса',
|
||||
type: FORM_TYPES.TEXTAREA,
|
||||
className: 'ClientLogsViewForm__headersInput',
|
||||
}),
|
||||
this.urlInput = new FormControl(this.form, {
|
||||
id: 'client-logs-view-form-url',
|
||||
label: 'URL запроса',
|
||||
}),
|
||||
this.methodInput = new FormControl(this.form, {
|
||||
id: 'client-logs-view-form-method',
|
||||
label: 'Метод запроса',
|
||||
}),
|
||||
this.endTimeInput = new FormControl(this.form, {
|
||||
id: 'client-logs-view-form-endTime',
|
||||
label: 'Время ответа',
|
||||
}),
|
||||
this.responseInput = new FormControl(this.form, {
|
||||
id: 'client-logs-view-form-response',
|
||||
label: 'Ответ сервера',
|
||||
type: FORM_TYPES.TEXTAREA,
|
||||
className: 'ClientLogsViewForm__responseInput',
|
||||
}),
|
||||
];
|
||||
inputs.forEach((input) => {
|
||||
input.disabled(true);
|
||||
});
|
||||
}
|
||||
|
||||
prepareStringJSON = (string) => {
|
||||
const object = typeof string === 'string' ? JSON.parse(string) : string;
|
||||
return JSON.stringify(object, false, 4);
|
||||
}
|
||||
|
||||
setForm ({_id, type, request, response, startTime, endTime}) {
|
||||
const {headers, url, method} = JSON.parse(request) ?? {};
|
||||
this.idInput.setValue(_id);
|
||||
this.resultInput.setValue(type);
|
||||
this.startTimeInput.setValue(startTime);
|
||||
this.headersInput.setValue(this.prepareStringJSON(headers));
|
||||
this.urlInput.setValue(url);
|
||||
this.methodInput.setValue(method);
|
||||
this.endTimeInput.setValue(endTime);
|
||||
this.responseInput.setValue(this.prepareStringJSON(response));
|
||||
this.sidebar.show();
|
||||
}
|
||||
}
|
||||
|
||||
export default ClientLogsViewForm;
|
||||
3
src/components/client-logs-view-form/index.js
Normal file
3
src/components/client-logs-view-form/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
import ClientLogsViewForm from './ClientLogsViewForm';
|
||||
|
||||
export default ClientLogsViewForm;
|
||||
61
src/components/form-control/FormControl.js
Normal file
61
src/components/form-control/FormControl.js
Normal file
@ -0,0 +1,61 @@
|
||||
import Component from '../component';
|
||||
import {createElement} from '../../utils/elementUtils';
|
||||
import {FORM_TYPES} from '../../consts';
|
||||
|
||||
class FormControl extends Component {
|
||||
constructor (parentNode, {
|
||||
label,
|
||||
id,
|
||||
type = FORM_TYPES.TEXT,
|
||||
placeholder = '',
|
||||
initValue = '',
|
||||
className = '',
|
||||
} = {}) {
|
||||
super('#form-control', parentNode);
|
||||
|
||||
this.label = this.mainNode.querySelector('.form-label');
|
||||
this.errorText = this.mainNode.querySelector('.form-text');
|
||||
this.input = createElement({
|
||||
tagName: this.getInputTagName(type),
|
||||
options: {
|
||||
className: `form-control ${className}`,
|
||||
}
|
||||
});
|
||||
this.input.placeholder = placeholder;
|
||||
this.input.setAttribute('id', id);
|
||||
this.input.value = initValue;
|
||||
|
||||
this.label.insertAdjacentElement('afterend', this.input);
|
||||
this.label.textContent = label;
|
||||
this.label.setAttribute('for', id);
|
||||
}
|
||||
|
||||
disabled = (value) => {
|
||||
this.input.disabled = value;
|
||||
}
|
||||
|
||||
getValue = () => {
|
||||
return this.input.value;
|
||||
}
|
||||
|
||||
setValue = (value) => {
|
||||
this.input.value = value;
|
||||
}
|
||||
|
||||
getInputTagName (type) {
|
||||
switch (type) {
|
||||
case FORM_TYPES.TEXT:
|
||||
case FORM_TYPES.PASSWORD:
|
||||
return 'input';
|
||||
case FORM_TYPES.SELECT:
|
||||
return 'select';
|
||||
case FORM_TYPES.TEXTAREA:
|
||||
return 'textarea';
|
||||
default: {
|
||||
throw new Error(`Тип формы ${type} не поддерживается`);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export default FormControl;
|
||||
3
src/components/form-control/index.js
Normal file
3
src/components/form-control/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
import FormControl from './FormControl';
|
||||
|
||||
export default FormControl;
|
||||
@ -8,6 +8,8 @@ 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';
|
||||
import ClientLogsViewForm from '../client-logs-view-form';
|
||||
import ServerLogsViewForm from '../server-logs-view-form';
|
||||
|
||||
const ELEMENTS_ON_PAGE = 15;
|
||||
|
||||
@ -15,6 +17,9 @@ class LogsPage extends Component {
|
||||
constructor (mainNodeSelector, parentNode) {
|
||||
super(mainNodeSelector, parentNode);
|
||||
|
||||
this.clientLogsForm = new ClientLogsViewForm();
|
||||
this.serverLogsForm = new ServerLogsViewForm();
|
||||
|
||||
this.header = createElement({
|
||||
tagName: 'div',
|
||||
parentNode: this.mainNode,
|
||||
@ -104,17 +109,31 @@ class LogsPage extends Component {
|
||||
}));
|
||||
}
|
||||
|
||||
openRowForView = (tableType, row) => {
|
||||
if (tableType === LOG_TYPE.CLIENT) {
|
||||
this.clientLogsForm.setForm(row);
|
||||
} else if (tableType === LOG_TYPE.SERVER) {
|
||||
this.serverLogsForm.setForm(row);
|
||||
}
|
||||
}
|
||||
|
||||
renderTable = () => {
|
||||
this.render(() => {
|
||||
const {query} = routeService.getUrlData();
|
||||
const {tableType, ...omitQuery} = query;
|
||||
this.table?.mainNode?.remove();
|
||||
this.table?.destroy();
|
||||
this.table = this.tables[tableType];
|
||||
this.body.appendChild(this.table.mainNode);
|
||||
const filteredRows = this.filterRows(this.logList[tableType], omitQuery);
|
||||
const preparedRows = this.prepareRows(this.logList[tableType]);
|
||||
const filteredRows = this.filterRows(preparedRows, omitQuery);
|
||||
const rows = this.cutPagginationPage(filteredRows, query.pageNumber);
|
||||
|
||||
this.table.render(this.prepareRows(rows));
|
||||
this.table.render(rows);
|
||||
|
||||
this.addSubscribe(this.table, EVENTS.ROW_CLICK, (_, row) => {
|
||||
const findedRow = preparedRows.find((item) => item._id === row._id);
|
||||
this.openRowForView(tableType, findedRow);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
72
src/components/modal-sidebar/ModalSibebar.js
Normal file
72
src/components/modal-sidebar/ModalSibebar.js
Normal file
@ -0,0 +1,72 @@
|
||||
import Component from '../component';
|
||||
import './ModalSidebar.css';
|
||||
import {createElement} from '../../utils/elementUtils';
|
||||
|
||||
const SHOW_WINDOW_CLASS = 'ModalSidebar__window_show';
|
||||
const HIDE_WINDOW_CLASS = 'ModalSidebar__window_hide';
|
||||
const SHOW_SHADOW_CLASS = 'ModalSidebar__shadow_show';
|
||||
const HIDE_SHADOW_CLASS = 'ModalSidebar__shadow_hide';
|
||||
|
||||
class ModalSidebar extends Component {
|
||||
constructor ({
|
||||
content,
|
||||
disabledShadowClose,
|
||||
} = {}) {
|
||||
const parentNode = document.body;
|
||||
super('#modal-sidebar', parentNode);
|
||||
|
||||
this.shadow = createElement({
|
||||
tagName: 'div',
|
||||
parentNode,
|
||||
options: {
|
||||
className: 'ModalSidebar__shadow',
|
||||
},
|
||||
});
|
||||
|
||||
this.crossButton = this.mainNode.querySelector('.ModalSidebar__close');
|
||||
|
||||
this.window = this.mainNode.querySelector('.ModalSidebar__window');
|
||||
|
||||
this.window.appendChild(content);
|
||||
|
||||
if (!disabledShadowClose) {
|
||||
this.addEventListener(this.mainNode, 'click', (event) => {
|
||||
if (event.target === this.mainNode) {
|
||||
this.hide();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
this.addEventListener(this.crossButton, 'click', this.hide);
|
||||
}
|
||||
|
||||
isOpen = () => {
|
||||
return this.mainNode.classList.contains(SHOW_WINDOW_CLASS);
|
||||
}
|
||||
|
||||
toggle = () => {
|
||||
if (this.isOpen()) {
|
||||
this.hide();
|
||||
} else {
|
||||
this.show();
|
||||
}
|
||||
}
|
||||
|
||||
show = () => {
|
||||
this.mainNode.classList.add(SHOW_WINDOW_CLASS);
|
||||
this.shadow.classList.add(SHOW_SHADOW_CLASS);
|
||||
|
||||
this.mainNode.classList.remove(HIDE_WINDOW_CLASS);
|
||||
this.shadow.classList.remove(HIDE_SHADOW_CLASS);
|
||||
}
|
||||
|
||||
hide = () => {
|
||||
this.mainNode.classList.remove(SHOW_WINDOW_CLASS);
|
||||
this.shadow.classList.remove(SHOW_SHADOW_CLASS);
|
||||
|
||||
this.mainNode.classList.add(HIDE_WINDOW_CLASS);
|
||||
this.shadow.classList.add(HIDE_SHADOW_CLASS);
|
||||
}
|
||||
}
|
||||
|
||||
export default ModalSidebar;
|
||||
61
src/components/modal-sidebar/ModalSidebar.css
Normal file
61
src/components/modal-sidebar/ModalSidebar.css
Normal file
@ -0,0 +1,61 @@
|
||||
@import url('./ModalSidebarAnimation.css');
|
||||
|
||||
.ModalSidebar {
|
||||
position: fixed;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
right: -100%;
|
||||
z-index: 5;
|
||||
}
|
||||
|
||||
.ModalSidebar__shadow {
|
||||
position: fixed;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
top: 0;
|
||||
left: 0;
|
||||
background-color: rgba(0, 0, 0, 0.3);
|
||||
z-index: -1;
|
||||
opacity: 0;
|
||||
}
|
||||
|
||||
.ModalSidebar__shadow_show {
|
||||
opacity: 1;
|
||||
z-index: 4;
|
||||
}
|
||||
|
||||
.ModalSidebar__shadow_hide {
|
||||
opacity: 0;
|
||||
z-index: -1;
|
||||
}
|
||||
|
||||
.ModalSidebar__window {
|
||||
background-color: white;
|
||||
position: relative;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.ModalSidebar__window_hide {
|
||||
animation-name: ModalSidebarHide;
|
||||
animation-duration: 200ms;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
.ModalSidebar__window_show {
|
||||
animation-name: ModalSidebarShow;
|
||||
animation-duration: 200ms;
|
||||
animation-fill-mode: forwards;
|
||||
}
|
||||
|
||||
.ModalSidebar__close {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
position: absolute;
|
||||
top: 15px;
|
||||
right: 15px;
|
||||
z-index: 1030;
|
||||
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
}
|
||||
19
src/components/modal-sidebar/ModalSidebarAnimation.css
Normal file
19
src/components/modal-sidebar/ModalSidebarAnimation.css
Normal file
@ -0,0 +1,19 @@
|
||||
@keyframes ModalSidebarShow {
|
||||
from {
|
||||
right: -100%;
|
||||
}
|
||||
|
||||
to {
|
||||
right: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes ModalSidebarHide {
|
||||
from {
|
||||
right: 0;
|
||||
}
|
||||
|
||||
to {
|
||||
right: -100%;
|
||||
}
|
||||
}
|
||||
3
src/components/modal-sidebar/index.js
Normal file
3
src/components/modal-sidebar/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
import ModalSidebar from './ModalSibebar';
|
||||
|
||||
export default ModalSidebar;
|
||||
@ -0,0 +1,7 @@
|
||||
.ServerLogsViewForm__message {
|
||||
height: 100px;
|
||||
}
|
||||
|
||||
.ServerLogsViewForm__stack {
|
||||
height: 200px;
|
||||
}
|
||||
62
src/components/server-logs-view-form/ServerLogsViewForm.js
Normal file
62
src/components/server-logs-view-form/ServerLogsViewForm.js
Normal file
@ -0,0 +1,62 @@
|
||||
import Component from '../component';
|
||||
import ModalSidebar from '../modal-sidebar';
|
||||
import FormControl from '../form-control';
|
||||
import {FORM_TYPES} from '../../consts';
|
||||
import './ServerLogsViewForm.css';
|
||||
|
||||
class ServerLogsViewForm extends Component {
|
||||
constructor (parentNode) {
|
||||
super('#logs-view-form', parentNode);
|
||||
|
||||
this.sidebar = new ModalSidebar({
|
||||
content: this.mainNode,
|
||||
});
|
||||
|
||||
this.title = this.mainNode.querySelector('.h2');
|
||||
this.form = this.mainNode.querySelector('form');
|
||||
|
||||
this.title.textContent = 'Просмотр ошибок сервера';
|
||||
|
||||
const inputs = [
|
||||
this.idInput = new FormControl(this.form, {
|
||||
id: 'server-logs-view-form-id',
|
||||
label: 'id',
|
||||
}),
|
||||
this.dateInput = new FormControl(this.form, {
|
||||
id: 'server-logs-view-form-date',
|
||||
label: 'Дата',
|
||||
}),
|
||||
this.typeInput = new FormControl(this.form, {
|
||||
id: 'server-logs-view-form-type',
|
||||
label: 'Тип записи',
|
||||
}),
|
||||
this.messageInput = new FormControl(this.form, {
|
||||
id: 'server-logs-view-form-message',
|
||||
label: 'Сообщение',
|
||||
type: FORM_TYPES.TEXTAREA,
|
||||
className: 'ServerLogsViewForm__message',
|
||||
}),
|
||||
this.stackInput = new FormControl(this.form, {
|
||||
id: 'server-logs-view-form-stack',
|
||||
label: 'Стек',
|
||||
type: FORM_TYPES.TEXTAREA,
|
||||
className: 'ServerLogsViewForm__stack',
|
||||
}),
|
||||
];
|
||||
inputs.forEach((input) => {
|
||||
input.disabled(true);
|
||||
});
|
||||
}
|
||||
|
||||
setForm ({_id, date, message, trace, type}) {
|
||||
this.idInput.setValue(_id);
|
||||
this.dateInput.setValue(date);
|
||||
this.typeInput.setValue(type);
|
||||
this.messageInput.setValue(message);
|
||||
this.stackInput.setValue(trace);
|
||||
|
||||
this.sidebar.show();
|
||||
}
|
||||
}
|
||||
|
||||
export default ServerLogsViewForm;
|
||||
3
src/components/server-logs-view-form/index.js
Normal file
3
src/components/server-logs-view-form/index.js
Normal file
@ -0,0 +1,3 @@
|
||||
import ServerLogsViewForm from './ServerLogsViewForm';
|
||||
|
||||
export default ServerLogsViewForm;
|
||||
@ -35,3 +35,10 @@ export const EVENTS = {
|
||||
ROUTE_SEARCH_CHANGE: 'routeSearchChange',
|
||||
ROW_CLICK: 'rowClick',
|
||||
};
|
||||
|
||||
export const FORM_TYPES = {
|
||||
TEXT: 'TEXT',
|
||||
SELECT: 'SELECT',
|
||||
TEXTAREA: 'TEXTAREA',
|
||||
PASSWORD: 'PASSWORD',
|
||||
};
|
||||
|
||||
@ -31,7 +31,9 @@ export const createElement = (createElementProps) => {
|
||||
element.setAttribute(attr, value);
|
||||
});
|
||||
}
|
||||
parentNode.appendChild(element);
|
||||
if (parentNode) {
|
||||
parentNode.appendChild(element);
|
||||
}
|
||||
return element;
|
||||
};
|
||||
|
||||
|
||||
14
store.http
14
store.http
@ -31,7 +31,7 @@ content-type: application/json
|
||||
Api-Name: store-service-test
|
||||
|
||||
{
|
||||
"key": "testApi",
|
||||
"key": "testApigg",
|
||||
"value": {
|
||||
"name": "ivan23",
|
||||
"age": 22
|
||||
@ -45,3 +45,15 @@ Api-Name: store-service-test
|
||||
// Удаление апи
|
||||
DELETE http://vigdorov.ru:4001/store/testApi HTTP/1.1
|
||||
Api-Name: store-service-test
|
||||
|
||||
###
|
||||
GET http://vigdorov.ru:4001/logs/server HTTP/1.1
|
||||
|
||||
###
|
||||
GET http://vigdorov.ru:4001/logs/client HTTP/1.1
|
||||
|
||||
###
|
||||
DELETE http://vigdorov.ru:4001/logs/server HTTP/1.1
|
||||
|
||||
###
|
||||
DELETE http://vigdorov.ru:4001/logs/client HTTP/1.1
|
||||
Reference in New Issue
Block a user