diff --git a/.eslintrc.json b/.eslintrc.json
index ef9b39e..97603a0 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -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"],
diff --git a/src/app.css b/src/app.css
index c32a180..022d579 100644
--- a/src/app.css
+++ b/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;
+}
diff --git a/src/app.html b/src/app.html
index ed4cd1e..f47db5f 100644
--- a/src/app.html
+++ b/src/app.html
@@ -5,6 +5,7 @@
diff --git a/src/components/client-logs-view-form/ClientLogsViewForm.css b/src/components/client-logs-view-form/ClientLogsViewForm.css
new file mode 100644
index 0000000..6ac05ed
--- /dev/null
+++ b/src/components/client-logs-view-form/ClientLogsViewForm.css
@@ -0,0 +1,7 @@
+.ClientLogsViewForm__headersInput {
+ height: 200px;
+}
+
+.ClientLogsViewForm__responseInput {
+ height: 600px;
+}
diff --git a/src/components/client-logs-view-form/ClientLogsViewForm.js b/src/components/client-logs-view-form/ClientLogsViewForm.js
new file mode 100644
index 0000000..6b6ddcb
--- /dev/null
+++ b/src/components/client-logs-view-form/ClientLogsViewForm.js
@@ -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;
diff --git a/src/components/client-logs-view-form/index.js b/src/components/client-logs-view-form/index.js
new file mode 100644
index 0000000..a615ba0
--- /dev/null
+++ b/src/components/client-logs-view-form/index.js
@@ -0,0 +1,3 @@
+import ClientLogsViewForm from './ClientLogsViewForm';
+
+export default ClientLogsViewForm;
diff --git a/src/components/form-control/FormControl.js b/src/components/form-control/FormControl.js
new file mode 100644
index 0000000..f43d38b
--- /dev/null
+++ b/src/components/form-control/FormControl.js
@@ -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;
diff --git a/src/components/form-control/index.js b/src/components/form-control/index.js
new file mode 100644
index 0000000..71c41f0
--- /dev/null
+++ b/src/components/form-control/index.js
@@ -0,0 +1,3 @@
+import FormControl from './FormControl';
+
+export default FormControl;
diff --git a/src/components/logs-page/LogsPage.js b/src/components/logs-page/LogsPage.js
index 07f979a..360e8d8 100644
--- a/src/components/logs-page/LogsPage.js
+++ b/src/components/logs-page/LogsPage.js
@@ -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);
+ });
});
}
}
diff --git a/src/components/modal-sidebar/ModalSibebar.js b/src/components/modal-sidebar/ModalSibebar.js
new file mode 100644
index 0000000..011b6bf
--- /dev/null
+++ b/src/components/modal-sidebar/ModalSibebar.js
@@ -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;
diff --git a/src/components/modal-sidebar/ModalSidebar.css b/src/components/modal-sidebar/ModalSidebar.css
new file mode 100644
index 0000000..3359695
--- /dev/null
+++ b/src/components/modal-sidebar/ModalSidebar.css
@@ -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;
+}
diff --git a/src/components/modal-sidebar/ModalSidebarAnimation.css b/src/components/modal-sidebar/ModalSidebarAnimation.css
new file mode 100644
index 0000000..37c30cf
--- /dev/null
+++ b/src/components/modal-sidebar/ModalSidebarAnimation.css
@@ -0,0 +1,19 @@
+@keyframes ModalSidebarShow {
+ from {
+ right: -100%;
+ }
+
+ to {
+ right: 0;
+ }
+}
+
+@keyframes ModalSidebarHide {
+ from {
+ right: 0;
+ }
+
+ to {
+ right: -100%;
+ }
+}
diff --git a/src/components/modal-sidebar/index.js b/src/components/modal-sidebar/index.js
new file mode 100644
index 0000000..fe63c5e
--- /dev/null
+++ b/src/components/modal-sidebar/index.js
@@ -0,0 +1,3 @@
+import ModalSidebar from './ModalSibebar';
+
+export default ModalSidebar;
diff --git a/src/components/server-logs-view-form/ServerLogsViewForm.css b/src/components/server-logs-view-form/ServerLogsViewForm.css
new file mode 100644
index 0000000..91f2b07
--- /dev/null
+++ b/src/components/server-logs-view-form/ServerLogsViewForm.css
@@ -0,0 +1,7 @@
+.ServerLogsViewForm__message {
+ height: 100px;
+}
+
+.ServerLogsViewForm__stack {
+ height: 200px;
+}
diff --git a/src/components/server-logs-view-form/ServerLogsViewForm.js b/src/components/server-logs-view-form/ServerLogsViewForm.js
new file mode 100644
index 0000000..8609f1d
--- /dev/null
+++ b/src/components/server-logs-view-form/ServerLogsViewForm.js
@@ -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;
diff --git a/src/components/server-logs-view-form/index.js b/src/components/server-logs-view-form/index.js
new file mode 100644
index 0000000..e979112
--- /dev/null
+++ b/src/components/server-logs-view-form/index.js
@@ -0,0 +1,3 @@
+import ServerLogsViewForm from './ServerLogsViewForm';
+
+export default ServerLogsViewForm;
diff --git a/src/consts.js b/src/consts.js
index 0addd7a..efa852f 100644
--- a/src/consts.js
+++ b/src/consts.js
@@ -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',
+};
diff --git a/src/utils/elementUtils.js b/src/utils/elementUtils.js
index 11754d4..951ce02 100644
--- a/src/utils/elementUtils.js
+++ b/src/utils/elementUtils.js
@@ -31,7 +31,9 @@ export const createElement = (createElementProps) => {
element.setAttribute(attr, value);
});
}
- parentNode.appendChild(element);
+ if (parentNode) {
+ parentNode.appendChild(element);
+ }
return element;
};
diff --git a/store.http b/store.http
index cf04675..dce3779 100644
--- a/store.http
+++ b/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
\ No newline at end of file