HM-126. Перенос хранилищ на новую модалку и логику, добавление работы… (#65)

This commit is contained in:
Nikolay
2020-09-06 10:28:33 +03:00
committed by GitHub
parent bc9b112904
commit 5d19104445
19 changed files with 565 additions and 364 deletions

View File

@ -31,6 +31,7 @@ export const ENDPOINTS = {
STORE: '/store',
CLIENT_LOGS: '/logs/client',
SERVER_LOGS: '/logs/server',
HOOKS: '/hooks',
};
export const AUTH_SERVICE = 'http://api.auth.vigdorov.ru';

View File

@ -36,37 +36,37 @@
<template id="main-statistic">
<div class="h-100">
<div class="container h-100 d-flex align-items-center">
<div class="row">
<div class="d-flex align-items-center justify-content-center">
<div class="d-flex align-items-center">
<div class="col mr-2">
<img class="col MainStatistic__logo" src="./img/logo.svg" alt="logo">
</div>
<div class="col">
<p class="mb-0 text-nowrap">Добро пожаловать в</p>
<p class="h1 mb-0 text-nowrap">Storage Service</p>
<div class="row">
<div class="d-flex align-items-center justify-content-center">
<div class="d-flex align-items-center">
<div class="col mr-2">
<img class="col MainStatistic__logo" src="./img/logo.svg" alt="logo">
</div>
<div class="col">
<p class="mb-0 text-nowrap">Добро пожаловать в</p>
<p class="h1 mb-0 text-nowrap">Storage Service</p>
</div>
</div>
</div>
</div>
<div class="d-flex justify-content-center mt-4 mb-4">
<p class="MainStatistic__text">Storage Service - <span class="font-weight-light">
это платформа для быстрого создания JSON хранилищ с удобным API для внедрения в
Web-приложения
</span></p>
</div>
<div class="d-flex justify-content-center MainStatistic__notifyContainer">
<button type="button" class="col btn btn-info MainStatistic__notify">
Хранилищ <span class="badge bg-warning text-dark MainStatistic__storeCount">0</span>
</button>
<button type="button" class="col btn btn-info MainStatistic__notify">
Сервисов <span class="badge bg-warning text-dark MainStatistic__serviceCount">0</span>
</button>
<button type="button" class="col btn btn-info MainStatistic__notify">
Авторов <span class="badge bg-warning text-dark MainStatistic__authorCount">0</span>
</button>
<div class="d-flex justify-content-center mt-4 mb-4">
<p class="MainStatistic__text">Storage Service - <span class="font-weight-light">
это платформа для быстрого создания JSON хранилищ с удобным API для внедрения в
Web-приложения
</span></p>
</div>
<div class="d-flex justify-content-center MainStatistic__notifyContainer">
<button type="button" class="col btn btn-info MainStatistic__notify">
Хранилищ <span class="badge bg-warning text-dark MainStatistic__storeCount">0</span>
</button>
<button type="button" class="col btn btn-info MainStatistic__notify">
Сервисов <span class="badge bg-warning text-dark MainStatistic__serviceCount">0</span>
</button>
<button type="button" class="col btn btn-info MainStatistic__notify">
Авторов <span class="badge bg-warning text-dark MainStatistic__authorCount">0</span>
</button>
</div>
</div>
</div>
</div>
</div>
</template>
@ -387,7 +387,7 @@
<template id="user-view-form">
<div class="h-100 overflow-auto d-flex flex-column">
<p class="h2 mb-2 p-3 pr-5 sticky-top border-bottom bg-light"></p>
<form class="p-3 h" autocomplete="off"></form>
<form class="p-3" autocomplete="off"></form>
<div class="mt-auto p-3 pr-5 border-top bg-light">
<button type="button" class="btn btn-primary UserViewForm__create">Создать</button>
<button type="button" class="btn btn-primary UserViewForm__save">Сохранить</button>
@ -400,26 +400,51 @@
<!-- Шаблоны форм для просмотры api -->
<template id="api-view-form">
<div class="h-100 overflow-auto Api__view-container">
<div class="h-100 overflow-auto d-flex flex-column">
<p class="h2 mb-2 p-3 pr-5 sticky-top border-bottom bg-light"></p>
<form class="p-3"></form>
<div class="Api__view-controls stricky-bottom">
<button class="Button__edit btn btn-light">
<i class="fas fa-pencil-alt Edit__icon">
<span class="Button__edit-text"></span>
</i>
<form class="p-3" autocomplete="off"></form>
<div class="mt-auto p-3 pr-5 border-top bg-light">
<button type="button" class="btn btn-primary ApiViewForm__create">Создать</button>
<button type="button" class="btn btn-primary ApiViewForm__save">Сохранить</button>
<button type="button" class="btn btn-warning ApiViewForm__edit">Изменить</button>
<button type="button" class="btn btn-danger ApiViewForm__delete">Удалить</button>
<button type="button" class="btn btn-secondary ApiViewForm__cancel">Отмена</button>
</div>
</div>
</template>
</button>
<button class="Button__delete btn btn-light">
<i class="far fa-trash-alt">
<span class="Button__delete-text"></span>
</i>
</button>
<button class="Button__cancel btn btn-light">
<i class="fas fa-ban">
<span class="Button__cancel-text"></span>
</i>
<!-- Шаблон для отображения хуков хранилища -->
<template id="api-hooks">
<div class="p-3 pt-0">
<label class="form-label">Хуки для хранилища</label>
<div class="d-flex flex-column list-group"></div>
<button type="button" class="btn btn-primary mt-1 ApiHooks__addButton">Добавить хук</button>
</div>
</template>
<!-- Шаблон для строчки хука -->
<template id="api-hooks-row">
<div class="list-group-item list-group-item-action d-flex">
<div class="mr-3">
<small>Держатель</small>
<h6 class="Hook__holder"></h6>
</div>
<div class="mr-3">
<small>Описание</small>
<h6 class="Hook__description"></h6>
</div>
<div class="mr-3">
<small>Чтение</small>
<h6 class="Hook__read"></h6>
</div>
<div>
<small>Запись</small>
<h6 class="Hook__write"></h6>
</div>
<div class="ml-auto">
<button type="button" class="close">
<span aria-hidden="true">&times;</span>
</button>
</div>
</div>
@ -428,7 +453,19 @@
<!-- Шаблон FormControl компонента -->
<template id="form-control">
<div class="mb-3">
<label class="form-label"><span class="FormControl__label"></span><span class="FormControl__star text-danger"></span></label>
<label class="form-label"><span class="FormControl__label"></span><span
class="FormControl__star text-danger"></span></label>
<div class="form-text invalid-feedback"></div>
</div>
</template>
<!-- Шаблон FormCheck компонента -->
<template id="form-check">
<div class="mb-3">
<div class="form-check">
<input class="form-check-input" type="checkbox">
<label class="form-check-label"></label>
</div>
<div class="form-text invalid-feedback"></div>
</div>
</template>

View File

@ -0,0 +1,28 @@
import Component from '../component/Component';
export class FormCheck extends Component {
constructor (parentNode, {
label,
id,
initValue = false,
} = {}) {
super('#form-check', parentNode);
this.label = this.mainNode.querySelector('.form-check-label');
this.checkBox = this.mainNode.querySelector('.form-check-input');
this.label.textContent = label;
this.checkBox.checked = initValue;
this.checkBox.setAttribute('id', id);
this.label.setAttribute('for', id);
}
setValue = (value) => {
this.checkBox.checked = !!value;
}
getValue = () => {
return this.checkBox.checked;
}
}

View File

@ -84,6 +84,7 @@ class FormControl extends Component {
setError = (errorMessage) => {
this.errorText.textContent = errorMessage;
this.input.setCustomValidity(errorMessage);
}
clearError = () => {

View File

@ -33,7 +33,7 @@ class ModalSidebar extends Component {
this.window.appendChild(content);
if (!disabledShadowClose) {
this.addEventListener(this.mainNode, EVENTS.CLICK, (event) => {
this.addEventListener(this.mainNode, EVENTS.MOUSE_DOWN, (event) => {
if (event.target === this.mainNode) {
this.hide();
}

View File

@ -1,5 +1,6 @@
.Modal {
position: fixed;
z-index: 2000;
top: 0;
left: 0;
width: 100%;

View File

@ -7,6 +7,7 @@ export const EVENTS = {
CHANGE_USER_AVATAR: 'changeUserAvatar',
OPEN_MODAL: 'openModal',
CLICK: 'click',
MOUSE_DOWN: 'mousedown',
DBL_CLICK: 'dblclick',
SUBMIT: 'submit',
FOCUS: 'focus',
@ -16,6 +17,11 @@ export const EVENTS = {
CREATE_USER: 'createUser',
SAVE_USER: 'saveUser',
DELETE_USER: 'deleteUser',
CREATE_STORE: 'createStore',
SAVE_STORE: 'saveStore',
DELETE_STORE: 'deleteStore',
DELETE_HOOK: 'deleteHook',
SAVE_HOOK: 'saveHook',
};
export const FORM_TYPES = {
@ -42,6 +48,8 @@ export const TAG_NAME = {
A: 'a',
TD: 'td',
SPAN: 'span',
BUTTON: 'button',
FORM: 'form',
};
export const TABLE_TYPE = {

View File

@ -0,0 +1,33 @@
import {ENDPOINTS} from '../../../api/consts';
import adminConfigsService from '../../../services/AdminConfigsService';
import http from '../../../api/HttpAPI';
class HooksApi {
constructor () {
const {url, options} = adminConfigsService.getApi();
this.URL = `${url}${ENDPOINTS.HOOKS}`;
this.OPTIONS = options;
}
request = async (key) => {
const {data} = await http.get(this.URL, {key}, this.OPTIONS);
return data;
}
create = async (key, createData) => {
const {data} = await http.post(this.URL, createData, {
...this.OPTIONS,
params: {key},
});
return data;
}
remove = async (key, id) => {
const {data} = await http.delete(this.URL, {key, id}, this.OPTIONS);
return data;
}
}
const hooksApi = new HooksApi();
export default hooksApi;

View File

@ -0,0 +1,115 @@
import Modal from '../../../../core/components/modal/Modal';
import {TAG_NAME, EVENTS} from '../../../../core/consts';
import FormControl from '../../../../core/components/form-control/FormControl';
import {FormCheck} from '../../../../core/components/form-check/FormCheck';
import {v4} from 'uuid';
import userInfoService from '../../../../services/UserInfoService';
import routeService from '../../../../services/RouteService';
import hooksApi from '../../api/HooksAPI';
export class AddHookModal extends Modal {
constructor (parentNode) {
super(parentNode);
this.createElement({
tagName: TAG_NAME.DIV,
parentNode: this.header,
options: {
textContent: 'Создание хука',
},
});
this.form = this.createElement({
tagName: TAG_NAME.FORM,
parentNode: this.content,
});
this.inputs = [
this.holderInput = this.createComponent(FormControl, this.form, {
id: 'add-hook-modal-holder',
label: 'Название',
}),
this.descriptionInput = this.createComponent(FormControl, this.form, {
id: 'add-hook-modal-description',
label: 'Описание'
}),
this.readCheckbox = this.createComponent(FormCheck, this.form, {
id: 'add-hook-modal-read',
label: 'Позволяет чтение',
}),
this.writeCheckbox = this.createComponent(FormCheck, this.form, {
id: 'add-hook-modal-write',
label: 'Позволяет запись',
}),
];
this.alert = this.createElement({
tagName: TAG_NAME.DIV,
parentNode: this.content,
options: {
className: 'alert alert-success',
},
});
this.saveButton = this.createElement({
tagName: TAG_NAME.BUTTON,
parentNode: this.footer,
options: {
textContent: 'Создать',
className: 'btn btn-primary',
}
});
this.cancelButton = this.createElement({
tagName: TAG_NAME.BUTTON,
parentNode: this.footer,
options: {
textContent: 'Отмена',
className: 'btn btn-secondary',
},
});
this.addEventListener(this.saveButton, EVENTS.CLICK, this.saveHook);
this.addEventListener(this.cancelButton, EVENTS.CLICK, this.cancel);
}
openForm = () => {
this.inputs.forEach((input) => {
input.mainNode.style.display = 'block';
input.setValue('');
});
this.alert.style.display = 'none';
this.saveButton.disabled = false;
this.cancelButton.textContent = 'Отмена';
this.show();
}
saveHook = async () => {
const token = v4();
const {login} = await userInfoService.getUserInfo();
const {query: {key}} = routeService.getUrlData();
const hook = {
holder: this.holderInput.getValue(),
description: this.descriptionInput.getValue(),
rights: {
write: this.writeCheckbox.getValue(),
read: this.readCheckbox.getValue(),
},
token,
author: login,
};
this.inputs.forEach((input) => {
input.mainNode.style.display = 'none';
});
this.saveButton.disabled = true;
await hooksApi.create(key, hook);
this.cancelButton.textContent = 'Закрыть';
this.alert.textContent = `"${token}" - сохраните ваш хук. Больше его нельзя будет увидеть.`;
this.alert.style.display = 'block';
this.next(EVENTS.SAVE_HOOK);
}
cancel = () => {
this.hide();
}
}

View File

@ -1,39 +0,0 @@
.Info__body {
height: 100%;
display: flex;
flex-direction: column;
}
.Info__container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.Info__title {
text-align: center;
}
.Info__service_name {
align-self: center;
}
.Info__editor {
border: 1px solid #F2F2F2;
text-align: left;
width: 100%;
height: 40vh;
}
.Info__controls {
position: absolute;
right: 15px;
top: 5px;
font-size: 20px;
}
.Button__delete {
background: none;
border: none;
}
.Button__edit {
background: none;
border: none;
}

View File

@ -1,61 +0,0 @@
import Component from '../../../../core/components/component/Component';
import './ApiInfoComponent.css';
import Modal from '../../../../core/components/modal/Modal';
import storageApi from '../../api/StorageServiceAPI';
class ApiInfoComponent extends Component {
constructor (container) {
super('#api-info', container);
this.infoTitle = this.mainNode.querySelector('.Info__title');
this.infoHeader = this.mainNode.querySelector('.Info__header');
this.infoDescription = this.mainNode.querySelector('.Info__description');
this.infoServiceName = this.mainNode.querySelector('.Info__service_name');
this.infoEditor = this.mainNode.querySelector('.Info__editor');
this.infoFooter = this.mainNode.querySelector('.Info__footer');
this.infoBody = this.mainNode.querySelector('.Info__body');
this.infoControls = this.mainNode.querySelector('.Info__controls');
this.btnDelete = this.mainNode.querySelector('.Button__delete');
this.btnEdit = this.mainNode.querySelector('.Button__edit');
this.info = {
headerNode: this.infoHeader,
contentNode: this.infoBody,
footerNode: this.infoFooter
};
this.key = null;
this.modal = this.createComponent(Modal, document.body, this.info);
this.addEventListener(this.btnDelete, 'click', () => {
if (this.key !== null) {
this.deleteApi(this.key);
}
});
}
render = (object, index) => {
this.object = object;
this.index = index;
this.key = object.key;
this.modal.show();
if (this.object) {
let title = '';
if (this.object.key.length > 20) {
title = `${this.object.key.substr(0, 20)} ...`;
} else title = this.object.key;
this.infoTitle.textContent = title;
this.infoServiceName.textContent = this.object.service_name;
this.infoEditor.innerHTML = JSON.stringify(this.object.value, false, 4);
this.infoFooter.textContent = this.object.author;
this.infoDescription.textContent = this.object.description;
}
}
deleteApi = async (key) => {
await storageApi.remove(key);
this.key = null;
this.modal.hide();
this.next('deleteApi');
}
}
export default ApiInfoComponent;

View File

@ -1,111 +1,255 @@
import Component from '../../../../core/components/component/Component';
import ModalSidebar from '../../../../core/components/modal-sidebar/ModalSibebar';
import FormControl from '../../../../core/components/form-control/FormControl';
import {FORM_TYPES} from '../../../../core/consts';
import {FORM_TYPES, EVENTS, MODES} from '../../../../core/consts';
import './ApiTableViewForm.css';
import storageApi from '../../api/StorageServiceAPI';
import routeService from '../../../../services/RouteService';
import {HooksComponent} from '../hooks-component/HooksComponent';
const TITLE_MODES = {
[MODES.Create]: 'Создание нового хранилища',
[MODES.Edit]: 'Редактирование хранилища',
[MODES.View]: 'Просмотр хранилища',
};
const EDIT_MODES = [MODES.Create, MODES.Edit];
const FILL_FIELD = 'Заполните поле';
class ApiTableViewForm extends Component {
constructor (parentNode) {
super('#api-view-form', parentNode);
this.sidebar = this.createComponent(ModalSidebar, {
content: this.mainNode
content: this.mainNode,
});
this.key = null;
this.isEdit = false;
this.title = this.mainNode.querySelector('.h2');
this.form = this.mainNode.querySelector('form');
this.deleteBtn = this.mainNode.querySelector('.Button__delete');
this.addEventListener(this.deleteBtn, 'click', () => {
if (this.key !== null) {
this.deleteApi(this.key);
}
});
this.editBtn = this.mainNode.querySelector('.Button__edit');
this.addEventListener(this.editBtn, 'click', () => {
this.editApi();
});
this.editIcon = this.mainNode.querySelector('.Edit__icon');
this.editBtnText = this.mainNode.querySelector('.Button__edit-text');
this.editBtnText.textContent = 'Редактировать';
this.deleteBtnText = this.mainNode.querySelector('.Button__delete-text');
this.deleteBtnText.textContent = 'Удалить';
this.cancelBtnText = this.mainNode.querySelector('.Button__cancel-text');
this.cancelBtnText.textContent = 'Отменить';
this.title.textContent = 'Информация о хранилище';
this.inputs = [
this.keyInput = this.createComponent(FormControl, this.form, {
id: 'api-key-input',
label: 'Название хранилища'
label: 'Название хранилища',
pattern: '^[a-zA-Z][a-zA-Z0-9_-]{3,}$',
required: true,
}),
this.serviceNameInput = this.createComponent(FormControl, this.form, {
id: 'api-service-name-input',
label: 'Название сервиса'
label: 'Название сервиса',
required: true,
}),
this.authorInput = this.createComponent(FormControl, this.form, {
id: 'api-author-input',
label: 'Автор хранилища'
label: 'Автор хранилища',
required: true,
}),
this.descriptionInput = this.createComponent(FormControl, this.form, {
id: 'api-description-input',
label: 'Краткое описание'
label: 'Краткое описание',
required: true,
}),
this.valueInput = this.createComponent(FormControl, this.form, {
id: 'api-value-input',
type: FORM_TYPES.TEXTAREA,
className: 'Api__value-input',
label: 'Содержимое хранилища'
label: 'Содержимое хранилища',
required: true,
}),
];
this.buttons = [
{
button: this.createButton = this.mainNode.querySelector('.ApiViewForm__create'),
modes: [MODES.Create],
},
{
button: this.saveButton = this.mainNode.querySelector('.ApiViewForm__save'),
modes: [MODES.Edit],
},
{
button: this.editButton = this.mainNode.querySelector('.ApiViewForm__edit'),
modes: [MODES.View],
},
{
button: this.cancelButton = this.mainNode.querySelector('.ApiViewForm__cancel'),
modes: [MODES.Create, MODES.Edit, MODES.View],
},
{
button: this.deleteButton = this.mainNode.querySelector('.ApiViewForm__delete'),
modes: [MODES.View, MODES.Edit],
}
];
this.addEventListener(this.createButton, EVENTS.CLICK, this.createStore);
this.addEventListener(this.saveButton, EVENTS.CLICK, this.saveStore);
this.addEventListener(this.deleteButton, EVENTS.CLICK, this.deleteStore);
this.addEventListener(this.editButton, EVENTS.CLICK, () => {
routeService.pushQuery({
mode: MODES.Edit,
});
});
this.addEventListener(this.cancelButton, EVENTS.CLICK, this.handleCloseModal);
this.inputs.forEach((input) => {
input.disabled(true);
});
this.hooksContainer = this.createComponent(HooksComponent, this.mainNode);
this.form.insertAdjacentElement('afterend', this.hooksContainer.mainNode);
this.addSubscribe(routeService, EVENTS.ROUTE_SEARCH_CHANGE, ({query}) => {
const {mode, key} = query;
this.setForm(mode, key);
this.setVisibleHooks(mode);
});
const {query: {mode, key}} = routeService.getUrlData();
if (mode) {
this.setForm(mode, key);
}
}
stringifyValue (value) {
return JSON.stringify(value, false, 4);
handleCloseModal = () => {
routeService.pushQuery({}, true);
}
setForm ({key, service_name, author, description, value}) {
setVisibleHooks = (mode) => {
const method = mode !== MODES.View ? 'add' : 'remove';
this.hooksContainer.mainNode.classList[method]('invisible');
}
setSidebarTitle = (mode, storeKey) => {
this.title.textContent = [TITLE_MODES[mode], storeKey].filter(Boolean).join(' ');
}
stringifyValue = (value) => {
return value ? JSON.stringify(value, false, 4) : '';
}
loadStore = async (key) => {
if (key) {
return await storageApi.find(key);
}
return {
key: '',
value: '',
description: '',
service_name: '',
author: '',
};
}
setInputDisabled = (mode) => {
const disabled = !EDIT_MODES.includes(mode);
this.inputs.forEach((input) => {
const disabledLogin = this.keyInput === input && mode === MODES.Edit;
input.disabled(disabled ? disabled : disabledLogin);
});
}
showButtons = (mode) => {
this.buttons.forEach(({button, modes}) => {
const isShow = modes.includes(mode);
button.style.display = isShow ? 'inline-block' : 'none';
});
}
parseJSON = (text) => {
return JSON.parse(text);
}
validateInputs = () => {
this.form.classList.add('was-validated');
this.inputs.forEach((input) => {
const value = input.getValue();
if (!value) {
input.setError(FILL_FIELD);
}
});
try {
const value = this.valueInput.getValue();
this.parseJSON(value);
this.valueInput.setError('');
} catch (e) {
this.valueInput.setError('Некорректный json');
}
return this.form.checkValidity();
}
clearForm = () => {
this.form.classList.remove('was-validated');
this.inputs.forEach((input) => {
input.setValue('');
input.setError('');
});
}
createStore = () => {
if (this.validateInputs()) {
this.next(EVENTS.CREATE_STORE, {
key: this.keyInput.getValue(),
value: this.parseJSON(this.valueInput.getValue()),
service_name: this.serviceNameInput.getValue(),
author: this.authorInput.getValue(),
description: this.descriptionInput.getValue(),
});
this.clearForm();
routeService.pushQuery({}, true);
}
}
saveStore = () => {
if (this.validateInputs()) {
this.next(EVENTS.SAVE_STORE, {
key: this.keyInput.getValue(),
value: this.parseJSON(this.valueInput.getValue()),
service_name: this.serviceNameInput.getValue(),
author: this.authorInput.getValue(),
description: this.descriptionInput.getValue(),
});
this.clearForm();
routeService.pushQuery({}, true);
}
}
deleteStore = () => {
const {query: {key}} = routeService.getUrlData();
this.next(EVENTS.DELETE_STORE, key);
routeService.pushQuery({}, true);
}
setKeyInput = (mode, key) => {
const disabled = mode !== MODES.Create;
this.keyInput.disabled(disabled);
this.keyInput.setValue(key);
}
setForm = async (mode, storeKey) => {
const {key, value, description, service_name, author} = await this.loadStore(storeKey);
this.setSidebarTitle(mode, storeKey);
this.setKeyInput(mode, key);
this.serviceNameInput.setValue(service_name);
this.authorInput.setValue(author);
this.descriptionInput.setValue(description);
this.valueInput.setValue(this.stringifyValue(value));
this.key = key;
this.sidebar.show();
}
this.setInputDisabled(mode);
this.showButtons(mode);
deleteApi = async (key) => {
await storageApi.remove(key);
this.key = null;
this.next('deleteApi');
this.sidebar.hide();
}
editApi () {
if (!this.isEdit) {
this.inputs.forEach((input) => {
input.disabled(false);
});
this.editBtnText.textContent = 'Сохранить';
this.editIcon.className = 'fas fa-check Edit__icon';
this.isEdit = true;
if (mode) {
this.sidebar.show();
} else {
this.inputs.forEach((input) => {
input.disabled(true);
});
this.editBtnText.textContent = 'Редактировать';
this.editIcon.className = 'fas fa-pencil-alt Edit__icon';
this.isEdit = false;
this.sidebar.hide();
}
}
}

View File

@ -1,66 +0,0 @@
.Create__body {
height: 100%;
display: flex;
}
.Create__container {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
}
.Create__title {
text-align: center;
}
.Create__send {
align-self: flex-end;
}
.Create__sidebar {
width: 30%;
overflow-y: auto;
}
.Create__footer {
flex-direction: row;
flex-wrap: nowrap;
justify-content: flex-end;
display: flex;
width: 100%;
}
.Create__error-container {
font-size: 0.8em;
color: #F46E42;
margin: auto;
}
.Create__editor {
border: 1px solid #F2F2F2;
text-align: left;
width: 100%;
min-height: 40vh;
margin: 10px 0;
}
.Create__controls {
position: absolute;
right: 10px;
top: 15px;
font-size: 20px;
}
.Create__input {
margin: 10px;
}
.Large__container {
max-width: 800px;
}
.Scroll__body {
overflow-y: auto;
max-height: 60vh;
}
@media (max-width: 800px) {
.Create__body {
flex-direction: column;
}
}

View File

@ -1,75 +0,0 @@
import Component from '../../../../core/components/component/Component';
import ButtonComponent from '../../../../core/components/button-component/ButtonComponent';
import './CreateApiComponent.css';
import storageApi from '../../api/StorageServiceAPI';
import Modal from '../../../../core/components/modal/Modal';
class CreateApiComponent extends Component {
constructor (container) {
super('#create-api', container);
this.inputKey = this.mainNode.querySelector('.Create__key');
this.inputServiceName = this.mainNode.querySelector('.Create__serviceName');
this.inputDescription = this.mainNode.querySelector('.Create__description');
this.inputAuthor = this.mainNode.querySelector('.Create__author');
this.createError = this.mainNode.querySelector('.Create__error-container');
this.header = this.mainNode.querySelector('.Create__title');
this.body = this.mainNode.querySelector('.Create__body');
this.footer = this.mainNode.querySelector('.Create__footer');
this.form = this.mainNode.querySelector('.Create__form');
this.editor = this.mainNode.querySelector('.Create__editor');
this.button = this.createComponent(ButtonComponent, this.footer, 'Создать', 'btn btn-outline-primary Create__send');
this.content = {
headerNode: this.header,
contentNode: this.body,
footerNode: this.footer
};
this.modal = this.createComponent(Modal, document.body, this.content);
this.modal.container.classList.add('Large__container');
this.modal.content.classList.add('Scroll__body');
this.button.subscribe('click', () => {
this.send();
});
}
show = () => {
this.modal.show();
}
parseString = async (text) => {
const obj = await JSON.parse(text);
return obj;
}
send = async () => {
try {
const obj = await JSON.parse(this.editor.textContent);
this.data = {
key: this.inputKey.value,
value: obj,
description: this.inputDescription.value,
service_name: this.inputServiceName.value,
author: this.inputAuthor.value
};
try {
await storageApi.create(this.data);
this.createError.textContent = '';
this.modal.hide();
this.next('renderTable');
} catch (err) {
if (err.response.status === 502) {
this.createError.textContent = 'Заполните все необходимые поля для создания хранилища';
} else if (err.response.status === 404) {
this.createError.textContent = 'Не удалось создать хранилище. Возможно Вы используете не уникальное название хранилища';
} else {
this.createError.textContent = 'Что-то пошло не так';
}
}
} catch {
this.createError.textContent = 'Тело хранилища не соответствует требованиям';
}
}
}
export default CreateApiComponent;

View File

@ -0,0 +1,23 @@
import Component from '../../../../core/components/component/Component';
import {EVENTS} from '../../../../core/consts';
export class HooksRow extends Component {
constructor (parentNode, hook) {
super('#api-hooks-row', parentNode);
this.holder = this.mainNode.querySelector('.Hook__holder');
this.description = this.mainNode.querySelector('.Hook__description');
this.read = this.mainNode.querySelector('.Hook__read');
this.write = this.mainNode.querySelector('.Hook__write');
this.deleteButton = this.mainNode.querySelector('.close');
this.holder.textContent = hook.holder;
this.description.textContent = hook.description;
this.read.textContent = hook.rights.read ? 'Да' : 'Нет';
this.write.textContent = hook.rights.write ? 'Да' : 'Нет';
this.addEventListener(this.deleteButton, EVENTS.CLICK, () => {
this.next(EVENTS.DELETE_HOOK, hook.id);
});
}
}

View File

@ -0,0 +1,51 @@
import Component from '../../../../core/components/component/Component';
import hooksApi from '../../api/HooksAPI';
import routeService from '../../../../services/RouteService';
import {EVENTS} from '../../../../core/consts';
import {HooksRow} from './HookRow';
import {AddHookModal} from '../add-hook-modal/AddHookModal';
export class HooksComponent extends Component {
constructor () {
super('#api-hooks', null);
this.hooks = [];
this.container = this.mainNode.querySelector('.list-group');
this.addSubscribe(routeService, EVENTS.ROUTE_SEARCH_CHANGE, ({query}) => {
const {mode, key} = query;
this.initHooks(mode, key);
});
this.addHookButton = this.mainNode.querySelector('.ApiHooks__addButton');
this.addHookModal = this.createComponent(AddHookModal, this.mainNode);
this.addEventListener(this.addHookButton, EVENTS.CLICK, this.addHookModal.openForm);
this.addSubscribe(this.addHookModal, EVENTS.SAVE_HOOK, this.initHooks);
this.initHooks();
}
initHooks = async () => {
const {query: {mode, key}} = routeService.getUrlData();
this.hooks.forEach((hook) => {
hook.destroy();
});
this.hooks = [];
if (key && mode) {
const hooks = await hooksApi.request(key);
this.hooks = hooks.map((hook) => {
const component = this.createComponent(HooksRow, this.container, hook);
this.addSubscribe(component, EVENTS.DELETE_HOOK, async (id) => {
await hooksApi.remove(key, id);
this.initHooks(mode, key);
});
return component;
});
}
}
}

View File

@ -1,12 +1,11 @@
import Component from '../../../../core/components/component/Component';
import storageApi from '../../api/StorageServiceAPI';
import FilterApiComponent from '../filter-api-component/FilterApiComponent';
import ApiInfoComponent from '../api-info-component/ApiInfoComponent';
import CreateApiComponent from '../create-api-component/CreateApiComponent';
import ButtonComponent from '../../../../core/components/button-component/ButtonComponent';
import ApiTableComponent from '../api-table-component/ApiTableComponent';
import ApiTableViewForm from '../api-table-view-form/ApiTableViewForm';
import {EVENTS} from '../../../../core/consts';
import {EVENTS, MODES} from '../../../../core/consts';
import routeService from '../../../../services/RouteService';
class ApiPage extends Component {
constructor (mainNodeSelector, parentNode) {
@ -16,26 +15,31 @@ class ApiPage extends Component {
this.apiViewForm = this.createComponent(ApiTableViewForm);
this.infoBox = this.createComponent(ApiInfoComponent);
this.createWindow = this.createComponent(CreateApiComponent);
this.createBtn = this.createComponent(ButtonComponent, this.filterBox.filterButtonBox, '✚', 'btn btn-primary mb-3 Create__btn');
this.addSubscribe(this.createBtn, 'click', () => {
this.createWindow.show();
});
this.addSubscribe(this.createWindow, 'renderTable', () => {
this.initStorageListTable();
this.addSubscribe(this.createBtn, EVENTS.CLICK, () => {
routeService.pushQuery({mode: MODES.Create}, true);
});
this.initStorageListTable();
this.addSubscribe(this.apiViewForm, 'deleteApi', () => {
this.addSubscribe(this.apiViewForm, EVENTS.CREATE_STORE, async (store) => {
await storageApi.create(store);
this.initStorageListTable();
});
this.addSubscribe(this.apiViewForm, EVENTS.SAVE_STORE, async (store) => {
await storageApi.update(store);
this.initStorageListTable();
});
this.addSubscribe(this.apiViewForm, EVENTS.DELETE_STORE, async (storeKey) => {
await storageApi.remove(storeKey);
this.initStorageListTable();
});
this.apiTable = this.createComponent(ApiTableComponent, this.mainNode);
this.addSubscribe(this.apiTable, EVENTS.ROW_DOUBLE_CLICK, (_, row) => {
this.apiViewForm.setForm(row);
routeService.pushQuery({mode: MODES.View, key: row.key}, true);
});
}

View File

@ -2,7 +2,7 @@ import Component from '../../../../core/components/component/Component';
import UsersTable from '../users-table/UsersTable';
import usersServiceApi from '../../api/UsersServiceAPI';
import UserViewForm from '../user-view-form/UserViewForm';
import {EVENTS, MODES} from '../../../../core/consts';
import {EVENTS, MODES, TAG_NAME} from '../../../../core/consts';
import routeService from '../../../../services/RouteService';
import userInfoService from '../../../../services/UserInfoService';
@ -12,10 +12,10 @@ class UsersPage extends Component {
this.usersForm = this.createComponent(UserViewForm);
this.createElement({tagName: 'div', parentNode: this.mainNode});
this.createElement({tagName: TAG_NAME.DIV, parentNode: this.mainNode});
this.createUserButton = this.createElement({
tagName: 'button',
tagName: TAG_NAME.BUTTON,
parentNode: this.mainNode,
options: {
className: 'btn btn-primary m-3',
@ -30,7 +30,7 @@ class UsersPage extends Component {
this.createUserButton.disabled = !is_admin;
});
this.addEventListener(this.createUserButton, 'click', () => {
this.addEventListener(this.createUserButton, EVENTS.CLICK, () => {
routeService.pushQuery({mode: MODES.Create}, true);
});

View File

@ -212,17 +212,13 @@ class UserViewForm extends Component {
setPassword = (mode) => {
const isShow = mode === MODES.Create;
this.password.setValue('');
this.password.mainNode.style.display = isShow ? 'block' : 'none';
}
setLogin = (mode, login) => {
const disabled = mode !== MODES.Create;
this.login.disabled(disabled);
this.login.setValue(login);
}