HM-74. Добавлена модалка-сайдбар. Реализована модалка для просмотра логов. (#32)

This commit is contained in:
Nikolay
2020-07-25 23:39:53 +03:00
committed by GitHub
parent c425ffea45
commit f0013d1514
19 changed files with 472 additions and 7 deletions

View File

@ -52,7 +52,6 @@
"eol-last": ["warn", "always"], "eol-last": ["warn", "always"],
"func-call-spacing": ["warn", "never"], "func-call-spacing": ["warn", "never"],
"func-style": ["warn", "expression"], "func-style": ["warn", "expression"],
"indent": ["warn", 4],
"keyword-spacing": ["warn", {"before": true}], "keyword-spacing": ["warn", {"before": true}],
"line-comment-position": ["warn", {"position": "above"}], "line-comment-position": ["warn", {"position": "above"}],
"lines-between-class-members": ["warn", "always"], "lines-between-class-members": ["warn", "always"],

View File

@ -1,4 +1,17 @@
html, body {
height: 100%;
}
textarea {
resize: none;
}
.Logo { .Logo {
width: 50px; width: 50px;
margin: 0 20px; margin: 0 20px;
} }
.overflow-auto::-webkit-scrollbar {
width: 0px;
background: transparent;
}

View File

@ -5,6 +5,7 @@
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Storage Service</title> <title>Storage Service</title>
<script src="https://kit.fontawesome.com/008e1ef4e8.js" crossorigin="anonymous"></script>
</head> </head>
<body> <body>
@ -32,7 +33,7 @@
</li> </li>
</ul> </ul>
</div> </div>
</nav> </nav>
</template> </template>
<!-- Шаблон контейнера страниц--> <!-- Шаблон контейнера страниц-->
@ -224,6 +225,35 @@
</div> </div>
</div> </div>
</template> </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"> <template id="create-api">
<div class="card text-center Info__container"> <div class="card text-center Info__container">

View File

@ -0,0 +1,7 @@
.ClientLogsViewForm__headersInput {
height: 200px;
}
.ClientLogsViewForm__responseInput {
height: 600px;
}

View 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;

View File

@ -0,0 +1,3 @@
import ClientLogsViewForm from './ClientLogsViewForm';
export default ClientLogsViewForm;

View 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;

View File

@ -0,0 +1,3 @@
import FormControl from './FormControl';
export default FormControl;

View File

@ -8,6 +8,8 @@ import {LOG_TYPE, EVENTS} from '../../consts';
import {createElement, markText, prepareServerDate, prepareObjectToString} from '../../utils/elementUtils'; import {createElement, markText, prepareServerDate, prepareObjectToString} from '../../utils/elementUtils';
import ServerLogsTable from '../server-logs-table'; import ServerLogsTable from '../server-logs-table';
import ClientLogsTable from '../client-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; const ELEMENTS_ON_PAGE = 15;
@ -15,6 +17,9 @@ class LogsPage extends Component {
constructor (mainNodeSelector, parentNode) { constructor (mainNodeSelector, parentNode) {
super(mainNodeSelector, parentNode); super(mainNodeSelector, parentNode);
this.clientLogsForm = new ClientLogsViewForm();
this.serverLogsForm = new ServerLogsViewForm();
this.header = createElement({ this.header = createElement({
tagName: 'div', tagName: 'div',
parentNode: this.mainNode, 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 = () => { renderTable = () => {
this.render(() => { this.render(() => {
const {query} = routeService.getUrlData(); const {query} = routeService.getUrlData();
const {tableType, ...omitQuery} = query; const {tableType, ...omitQuery} = query;
this.table?.mainNode?.remove(); this.table?.destroy();
this.table = this.tables[tableType]; this.table = this.tables[tableType];
this.body.appendChild(this.table.mainNode); 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); 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);
});
}); });
} }
} }

View 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;

View 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;
}

View File

@ -0,0 +1,19 @@
@keyframes ModalSidebarShow {
from {
right: -100%;
}
to {
right: 0;
}
}
@keyframes ModalSidebarHide {
from {
right: 0;
}
to {
right: -100%;
}
}

View File

@ -0,0 +1,3 @@
import ModalSidebar from './ModalSibebar';
export default ModalSidebar;

View File

@ -0,0 +1,7 @@
.ServerLogsViewForm__message {
height: 100px;
}
.ServerLogsViewForm__stack {
height: 200px;
}

View 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;

View File

@ -0,0 +1,3 @@
import ServerLogsViewForm from './ServerLogsViewForm';
export default ServerLogsViewForm;

View File

@ -35,3 +35,10 @@ export const EVENTS = {
ROUTE_SEARCH_CHANGE: 'routeSearchChange', ROUTE_SEARCH_CHANGE: 'routeSearchChange',
ROW_CLICK: 'rowClick', ROW_CLICK: 'rowClick',
}; };
export const FORM_TYPES = {
TEXT: 'TEXT',
SELECT: 'SELECT',
TEXTAREA: 'TEXTAREA',
PASSWORD: 'PASSWORD',
};

View File

@ -31,7 +31,9 @@ export const createElement = (createElementProps) => {
element.setAttribute(attr, value); element.setAttribute(attr, value);
}); });
} }
parentNode.appendChild(element); if (parentNode) {
parentNode.appendChild(element);
}
return element; return element;
}; };

View File

@ -31,7 +31,7 @@ content-type: application/json
Api-Name: store-service-test Api-Name: store-service-test
{ {
"key": "testApi", "key": "testApigg",
"value": { "value": {
"name": "ivan23", "name": "ivan23",
"age": 22 "age": 22
@ -45,3 +45,15 @@ Api-Name: store-service-test
// Удаление апи // Удаление апи
DELETE http://vigdorov.ru:4001/store/testApi HTTP/1.1 DELETE http://vigdorov.ru:4001/store/testApi HTTP/1.1
Api-Name: store-service-test 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