Compare commits
10 Commits
5d19104445
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 658c0f6267 | |||
| 2e7451e906 | |||
| e8d1adae98 | |||
| 775d503627 | |||
| cccd5e7556 | |||
| 4774d6959c | |||
| f70de4f43d | |||
| 00a2c29a09 | |||
| 39a5e29781 | |||
| 966febc7b5 |
@ -58,10 +58,11 @@
|
||||
"webpack-dev-server": "^3.11.0"
|
||||
},
|
||||
"dependencies": {
|
||||
"@popperjs/core": "^2.9.2",
|
||||
"ace-builds": "^1.4.12",
|
||||
"axios": "^0.19.2",
|
||||
"bootstrap": "^5.0.0-alpha1",
|
||||
"moment": "^2.27.0",
|
||||
"popper.js": "^1.16.1",
|
||||
"query-string": "^6.13.1",
|
||||
"uuid": "^8.2.0"
|
||||
}
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import http from './HttpAPI';
|
||||
|
||||
const ROOT_URL = 'http://api.auth.vigdorov.ru/users';
|
||||
const ROOT_URL = 'https://api.auth.vigdorov.ru/users';
|
||||
|
||||
const profileServiceApi = {
|
||||
getSelfInfo: async () => {
|
||||
|
||||
@ -10,11 +10,11 @@ export const API_NAME_HEADER = 'api-name';
|
||||
|
||||
export const API_OPTIONS = {
|
||||
[API_NAMES.PRODUCTION]: {
|
||||
url: 'http://api.storage.vigdorov.ru',
|
||||
url: 'https://api.storage.vigdorov.ru',
|
||||
options: {},
|
||||
},
|
||||
[API_NAMES.TESTING]: {
|
||||
url: 'http://api.storage.vigdorov.ru',
|
||||
url: 'https://api.storage.vigdorov.ru',
|
||||
options: {
|
||||
headers: {
|
||||
'Api-Name': 'store-service-test',
|
||||
@ -22,7 +22,7 @@ export const API_OPTIONS = {
|
||||
},
|
||||
},
|
||||
[API_NAMES.DEVELOP]: {
|
||||
url: 'http://localhost:4001',
|
||||
url: 'https://localhost:4001',
|
||||
options: {},
|
||||
},
|
||||
};
|
||||
@ -34,7 +34,7 @@ export const ENDPOINTS = {
|
||||
HOOKS: '/hooks',
|
||||
};
|
||||
|
||||
export const AUTH_SERVICE = 'http://api.auth.vigdorov.ru';
|
||||
export const AUTH_SERVICE = 'https://api.auth.vigdorov.ru';
|
||||
|
||||
export const AUTH_ENDPOINTS = {
|
||||
AUTH: '/auth',
|
||||
|
||||
26
src/app.html
26
src/app.html
@ -32,6 +32,26 @@
|
||||
</nav>
|
||||
</template>
|
||||
|
||||
<!-- Шаблон для CodeEditor -->
|
||||
<template id="code-editor">
|
||||
<div class="CodeEditor">
|
||||
<div class="CodeEditor__editor form-control"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Шаблон для FormCodeEditor -->
|
||||
<template id="form-code-editor">
|
||||
<div class="mb-3 FormCodeEditor">
|
||||
<label class="form-label">
|
||||
<span class="FormCodeEditor__label"></span>
|
||||
<span class="FormCodeEditor__star text-danger"></span>
|
||||
</label>
|
||||
<input type="text" class="FormCodeEditor__input" />
|
||||
<div class="FormCodeEditor__editor"></div>
|
||||
<div class="FormCodeEditor__errorText form-text invalid-feedback"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<!-- Шаблон карточек на главной-->
|
||||
<template id="main-statistic">
|
||||
<div class="h-100">
|
||||
@ -81,10 +101,6 @@
|
||||
<div class="Login__title h3"></div>
|
||||
<form class="Login__form needs-validation" novalidate>
|
||||
<div class="Login__inputContainer"></div>
|
||||
<div class="form-group form-check Login__check">
|
||||
<input type="checkbox" class="form-check-input" id="check">
|
||||
<label class="form-check-label" for="check">Оставаться в системе</label>
|
||||
</div>
|
||||
<button type="submit" class="btn btn-primary Login__submit">Войти</button>
|
||||
</form>
|
||||
</div>
|
||||
@ -472,7 +488,7 @@
|
||||
|
||||
<!-- Шаблон для Модального Сайдбара -->
|
||||
<template id="modal-sidebar">
|
||||
<div class="ModalSidebar row justify-content-end m-0">
|
||||
<div class="ModalSidebar row justify-content-end m-0 invisible">
|
||||
<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>
|
||||
|
||||
@ -15,6 +15,7 @@ import authServiceApi from './api/AuthServiceAPI';
|
||||
import userInfoService from './services/UserInfoService';
|
||||
import {EVENTS, ROUTES} from './core/consts';
|
||||
import UsersPage from './pages/users/components/page/Page';
|
||||
import {DocumentationPage} from './pages/documentation/components/page/Page';
|
||||
|
||||
const initAppComponents = () => {
|
||||
const mainMenu = new MainMenu();
|
||||
@ -36,10 +37,11 @@ const initAppComponents = () => {
|
||||
routerPagesContainer.addRoutes([
|
||||
{url: ROUTES.MAIN, pageComponent: MainPage},
|
||||
{url: ROUTES.STORE, pageComponent: ApiPage},
|
||||
{url: ROUTES.LOGS, pageComponent: LogsPage},
|
||||
{url: ROUTES.LOGS, pageComponent: LogsPage, onlyAdmin: true},
|
||||
{url: ROUTES.USERS, pageComponent: UsersPage},
|
||||
{url: ROUTES.LOGIN, pageComponent: LoginPage},
|
||||
{url: ROUTES.PROFILE, pageComponent: ProfilePage},
|
||||
{url: ROUTES.DOCUMENTATION, pageComponent: DocumentationPage},
|
||||
]);
|
||||
|
||||
/**
|
||||
|
||||
7
src/core/components/code-editor/CodeEditor.css
Normal file
7
src/core/components/code-editor/CodeEditor.css
Normal file
@ -0,0 +1,7 @@
|
||||
.CodeEditor__editor {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.CodeEditor__editor_disabled {
|
||||
background-color: #e9ecefa6;
|
||||
}
|
||||
45
src/core/components/code-editor/CodeEditor.js
Normal file
45
src/core/components/code-editor/CodeEditor.js
Normal file
@ -0,0 +1,45 @@
|
||||
import ace from 'ace-builds/src-noconflict/ace';
|
||||
import theme from 'ace-builds/src-noconflict/theme-github';
|
||||
import {Mode as JSONMode} from 'ace-builds/src-noconflict/mode-json';
|
||||
import Component from '../component/Component';
|
||||
import {CODE_EDITOR_MODE} from '../../consts';
|
||||
import './CodeEditor.css';
|
||||
|
||||
const Modes = {
|
||||
[CODE_EDITOR_MODE.JSON]: JSONMode,
|
||||
};
|
||||
|
||||
export class CodeEditor extends Component {
|
||||
constructor ({
|
||||
parentNode,
|
||||
id,
|
||||
mode,
|
||||
rows = 5,
|
||||
} = {}) {
|
||||
super('#code-editor', parentNode);
|
||||
|
||||
this.editorContainer = this.mainNode.querySelector('.CodeEditor__editor');
|
||||
this.editorContainer.setAttribute('id', id);
|
||||
|
||||
this.editorContainer.style.minHeight = `${Math.round(rows * 24.33)}px`;
|
||||
|
||||
this.editor = ace.edit(id);
|
||||
this.editor.session.setMode(new Modes[mode]());
|
||||
|
||||
this.editor.setTheme(theme);
|
||||
}
|
||||
|
||||
setValue = (value) => {
|
||||
this.editor.session.setValue(value);
|
||||
}
|
||||
|
||||
getValue = () => {
|
||||
return this.editor.getValue();
|
||||
}
|
||||
|
||||
disabled = (value) => {
|
||||
const method = value ? 'add' : 'remove';
|
||||
this.editorContainer.classList[method]('CodeEditor__editor_disabled');
|
||||
this.editor.setReadOnly(value);
|
||||
}
|
||||
}
|
||||
13
src/core/components/form-code-editor/FormCodeEditor.css
Normal file
13
src/core/components/form-code-editor/FormCodeEditor.css
Normal file
@ -0,0 +1,13 @@
|
||||
.FormCodeEditor {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.FormCodeEditor__input {
|
||||
position: absolute;
|
||||
z-index: -1000;
|
||||
}
|
||||
|
||||
.FormCodeEditor__input:invalid + .FormCodeEditor__editor {
|
||||
border: 1px solid var(--bs-red);
|
||||
border-radius: .25rem;
|
||||
}
|
||||
69
src/core/components/form-code-editor/FormCodeEditor.js
Normal file
69
src/core/components/form-code-editor/FormCodeEditor.js
Normal file
@ -0,0 +1,69 @@
|
||||
import Component from '../component/Component';
|
||||
import {CodeEditor} from '../code-editor/CodeEditor';
|
||||
import './FormCodeEditor.css';
|
||||
import {EVENTS} from '../../consts';
|
||||
|
||||
export class FormCodeEditor extends Component {
|
||||
constructor (parentNode, {
|
||||
id,
|
||||
mode,
|
||||
label,
|
||||
required = false,
|
||||
initValue = '',
|
||||
className = '',
|
||||
rows,
|
||||
} = {}) {
|
||||
super('#form-code-editor', parentNode);
|
||||
|
||||
this.label = this.mainNode.querySelector('.form-label');
|
||||
this.labelText = this.mainNode.querySelector('.FormCodeEditor__label');
|
||||
this.star = this.mainNode.querySelector('.FormCodeEditor__star');
|
||||
this.editorContainer = this.mainNode.querySelector('.FormCodeEditor__editor');
|
||||
this.errorText = this.mainNode.querySelector('.FormCodeEditor__errorText');
|
||||
this.input = this.mainNode.querySelector('.FormCodeEditor__input');
|
||||
|
||||
this.addEventListener(this.editorContainer, EVENTS.CLICK, this.clearError);
|
||||
this.addEventListener(this.editorContainer, EVENTS.KEYDOWN, this.clearError);
|
||||
|
||||
this.labelText.textContent = label;
|
||||
|
||||
this.editor = this.createComponent(CodeEditor, {
|
||||
parentNode: this.editorContainer,
|
||||
id,
|
||||
mode,
|
||||
rows,
|
||||
});
|
||||
|
||||
if (className) {
|
||||
this.editorContainer.classList.add(className);
|
||||
}
|
||||
|
||||
if (required) {
|
||||
this.star.textContent = ' *';
|
||||
}
|
||||
|
||||
this.setValue(initValue);
|
||||
|
||||
}
|
||||
|
||||
disabled = (value) => {
|
||||
this.editor.disabled(value);
|
||||
}
|
||||
|
||||
getValue = () => {
|
||||
return this.editor.getValue();
|
||||
}
|
||||
|
||||
setValue = (value) => {
|
||||
this.editor.setValue(value);
|
||||
}
|
||||
|
||||
setError = (errorMessage) => {
|
||||
this.errorText.textContent = errorMessage;
|
||||
this.input.setCustomValidity(errorMessage);
|
||||
}
|
||||
|
||||
clearError = () => {
|
||||
this.setError('');
|
||||
}
|
||||
}
|
||||
@ -88,7 +88,7 @@ class FormControl extends Component {
|
||||
}
|
||||
|
||||
clearError = () => {
|
||||
this.errorText.textContent = '';
|
||||
this.setError('');
|
||||
}
|
||||
|
||||
getInputTagName = (type) => {
|
||||
|
||||
@ -4,6 +4,7 @@ import routeService from '../../../services/RouteService';
|
||||
import './MainMenu.css';
|
||||
import {EVENTS, ROUTES, TAG_NAME} from '../../consts';
|
||||
import tokenApi from '../../../api/TokenAPI';
|
||||
import userInfoService from '../../../services/UserInfoService';
|
||||
|
||||
const NAV_MENU = [
|
||||
{
|
||||
@ -17,11 +18,16 @@ const NAV_MENU = [
|
||||
{
|
||||
title: 'Журнал',
|
||||
url: ROUTES.LOGS,
|
||||
onlyAdmin: true,
|
||||
},
|
||||
{
|
||||
title: 'Пользователи',
|
||||
url: ROUTES.USERS,
|
||||
},
|
||||
{
|
||||
title: 'Документация',
|
||||
url: ROUTES.DOCUMENTATION,
|
||||
},
|
||||
{
|
||||
title: 'Личный кабинет',
|
||||
url: ROUTES.PROFILE,
|
||||
@ -72,7 +78,7 @@ class MainMenu extends Component {
|
||||
}
|
||||
|
||||
render = () => {
|
||||
this.menuItems = NAV_MENU.map(({url, title, className = ''}) => {
|
||||
this.menuItems = NAV_MENU.map(({url, title, className = '', onlyAdmin}) => {
|
||||
const li = this.createElement({
|
||||
tagName: TAG_NAME.LI,
|
||||
parentNode: this.buttonsContainer,
|
||||
@ -95,7 +101,7 @@ class MainMenu extends Component {
|
||||
}
|
||||
});
|
||||
|
||||
return {url, link};
|
||||
return {url, li, link, onlyAdmin};
|
||||
});
|
||||
}
|
||||
|
||||
@ -107,7 +113,12 @@ class MainMenu extends Component {
|
||||
this.mainNode.remove();
|
||||
}
|
||||
|
||||
showMenu = () => {
|
||||
showMenu = async () => {
|
||||
const {is_admin} = await userInfoService.getUserInfo();
|
||||
this.menuItems.forEach(({li, onlyAdmin}) => {
|
||||
const isShow = !onlyAdmin || is_admin;
|
||||
li.style.display = isShow ? 'list-item' : 'none';
|
||||
});
|
||||
document.body.prepend(this.mainNode);
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@ const HIDE_SHADOW_CLASS = 'ModalSidebar__shadow_hide';
|
||||
const SHADOW_CLASS = 'ModalSidebar__shadow';
|
||||
const CROSS_BUTTON_CLASS = 'ModalSidebar__close';
|
||||
const WINDOW_CLASS = 'ModalSidebar__window';
|
||||
|
||||
const WINDOW_INIT_CLASS = 'invisible';
|
||||
class ModalSidebar extends Component {
|
||||
constructor ({
|
||||
content,
|
||||
@ -56,6 +56,7 @@ class ModalSidebar extends Component {
|
||||
}
|
||||
|
||||
show = () => {
|
||||
this.mainNode.classList.remove(WINDOW_INIT_CLASS);
|
||||
this.mainNode.classList.add(SHOW_WINDOW_CLASS);
|
||||
this.shadow.classList.add(SHOW_SHADOW_CLASS);
|
||||
|
||||
|
||||
@ -3,6 +3,7 @@ import routeService from '../../../services/RouteService';
|
||||
import NotFoundPage from '../../../pages/not-found/components/page/Page';
|
||||
|
||||
import {EVENTS, ROUTES} from '../../consts';
|
||||
import userInfoService from '../../../services/UserInfoService';
|
||||
|
||||
/**
|
||||
* @interface Route
|
||||
@ -59,6 +60,14 @@ class RouterPagesContainer extends Component {
|
||||
this.currentPage = new PageComponent('#page', this.mainNode);
|
||||
}
|
||||
});
|
||||
|
||||
this.addSubscribe(userInfoService, EVENTS.CHANGE_USER_INFO, ({is_admin}) => {
|
||||
const {url} = routeService.getUrlData();
|
||||
const currentRoute = this.routes.find((route) => route.url === url);
|
||||
if (currentRoute.onlyAdmin && !is_admin) {
|
||||
routeService.goTo(ROUTES.MAIN);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -69,4 +69,9 @@ export const ROUTES = {
|
||||
USERS: '/users',
|
||||
LOGIN: '/login',
|
||||
PROFILE: '/profile',
|
||||
DOCUMENTATION: '/documentation',
|
||||
};
|
||||
|
||||
export const CODE_EDITOR_MODE = {
|
||||
JSON: 'json',
|
||||
};
|
||||
|
||||
16
src/pages/documentation/components/page/Page.js
Normal file
16
src/pages/documentation/components/page/Page.js
Normal file
@ -0,0 +1,16 @@
|
||||
import Component from '../../../../core/components/component/Component';
|
||||
import {TAG_NAME} from '../../../../core/consts';
|
||||
|
||||
export class DocumentationPage extends Component {
|
||||
constructor (mainNodeSelector, parentNode) {
|
||||
super(mainNodeSelector, parentNode);
|
||||
|
||||
this.createElement({
|
||||
tagName: TAG_NAME.DIV,
|
||||
parentNode: this.mainNode,
|
||||
options: {
|
||||
textContent: 'Документация',
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@ -11,7 +11,6 @@ class LoginForm extends Component {
|
||||
this.form = this.mainNode.querySelector('.Login__form');
|
||||
this.inputContainer = this.mainNode.querySelector('.Login__inputContainer');
|
||||
this.submitButton = this.mainNode.querySelector('.Login__submit');
|
||||
this.checkboxSystem = this.mainNode.querySelector('.form-check-input');
|
||||
this.logoImage = this.mainNode.querySelector('.Login__logo');
|
||||
this.logoImage.src = Image;
|
||||
|
||||
@ -39,7 +38,6 @@ class LoginForm extends Component {
|
||||
this.loginControl.input,
|
||||
this.passwordControl.input,
|
||||
this.submitButton,
|
||||
this.checkboxSystem,
|
||||
];
|
||||
|
||||
elements.forEach((element) => {
|
||||
@ -61,6 +59,11 @@ class LoginForm extends Component {
|
||||
return this.form.checkValidity();
|
||||
}
|
||||
|
||||
setError = (message) => {
|
||||
this.loginControl.setError(message);
|
||||
this.passwordControl.setError(message);
|
||||
}
|
||||
|
||||
submit = (event) => {
|
||||
event.preventDefault();
|
||||
if (this.validateInputs()) {
|
||||
|
||||
@ -6,6 +6,11 @@ import routeService from '../../../../services/RouteService';
|
||||
import notify from '../../../../services/NotifyService';
|
||||
import {ROUTES, EVENTS} from '../../../../core/consts';
|
||||
|
||||
const ERRORS = {
|
||||
NOT_CORRECT: 'Неверный логин или пароль',
|
||||
UNKNOWN_ERROR: 'Неизвестная ошибка',
|
||||
};
|
||||
|
||||
class LoginPage extends Component {
|
||||
constructor (mainNodeSelector, parentNode) {
|
||||
super(mainNodeSelector, parentNode);
|
||||
@ -21,8 +26,12 @@ class LoginPage extends Component {
|
||||
routeService.goTo(ROUTES.MAIN);
|
||||
})
|
||||
.catch((e) => {
|
||||
const message = e?.response?.data?.message || 'Неизвестная ошибка';
|
||||
notify.warn(message);
|
||||
const message = e?.response?.data?.message || ERRORS.UNKNOWN_ERROR;
|
||||
if (message === ERRORS.NOT_CORRECT) {
|
||||
this.form.setError(ERRORS.NOT_CORRECT);
|
||||
} else {
|
||||
notify.warn(message);
|
||||
}
|
||||
this.form.disabled(false);
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import Component from '../../../../core/components/component/Component';
|
||||
import FormControl from '../../../../core/components/form-control/FormControl';
|
||||
import {FORM_TYPES, EVENTS} from '../../../../core/consts';
|
||||
import {INPUT_IDS, LABELS} from './consts';
|
||||
import {INPUT_IDS, LABELS, ERRORS} from './consts';
|
||||
import notify from '../../../../services/NotifyService';
|
||||
import profileServiceApi from '../../../../api/ProfileServiceAPI';
|
||||
|
||||
@ -48,21 +48,22 @@ class ProfileContent extends Component {
|
||||
const newPasswordRepeat = this.newPasswordRepeat.getValue();
|
||||
|
||||
if (!oldPassword) {
|
||||
this.oldPassword.setError('Заполните старый пароль');
|
||||
this.oldPassword.setError(ERRORS.TYPED_OLD_PASSWORD);
|
||||
}
|
||||
|
||||
if (!newPassword) {
|
||||
this.newPassword.setError('Заполните новый пароль');
|
||||
this.newPassword.setError(ERRORS.TYPED_NEW_PASSWORD);
|
||||
}
|
||||
|
||||
const isEqualPassword = newPassword === newPasswordRepeat;
|
||||
|
||||
if (!isEqualPassword) {
|
||||
notify.warn('Пароли не совпадают');
|
||||
this.newPassword.setError(ERRORS.PASSWORD_NOT_EQUAL);
|
||||
this.newPasswordRepeat.setError(ERRORS.PASSWORD_NOT_EQUAL);
|
||||
}
|
||||
|
||||
if (!newPasswordRepeat) {
|
||||
this.newPasswordRepeat.setError('Повторите новый пароль');
|
||||
this.newPasswordRepeat.setError(ERRORS.REPEAT_PASSWORD);
|
||||
}
|
||||
|
||||
return this.form.checkValidity() && isEqualPassword;
|
||||
@ -80,9 +81,16 @@ class ProfileContent extends Component {
|
||||
if (this.validateInputs()) {
|
||||
const oldPassword = this.oldPassword.getValue();
|
||||
const newPassword = this.newPassword.getValue();
|
||||
await profileServiceApi.changePassword(oldPassword, newPassword);
|
||||
try {
|
||||
await profileServiceApi.changePassword(oldPassword, newPassword);
|
||||
} catch (e) {
|
||||
if (e?.response?.data?.message === ERRORS.NOT_CORRECT_PASSWORD) {
|
||||
this.oldPassword.setError(ERRORS.NOT_CORRECT_PASSWORD);
|
||||
}
|
||||
throw new Error(e);
|
||||
}
|
||||
this.clearForm();
|
||||
notify.success('Пароль успешно изменен');
|
||||
notify.success(LABELS.SUCCESS_CHANGE_PASSWORD);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -11,4 +11,13 @@ export const LABELS = {
|
||||
OLD_PASSWORD_PLACEHOLDER: 'Введите старый пароль',
|
||||
NEW_PASSWORD_PLACEHOLDER: 'Введите новый пароль',
|
||||
NEW_PASSWORD_REPEAT_PLACEHOLDER: 'Введите новый пароль еще раз',
|
||||
SUCCESS_CHANGE_PASSWORD: 'Пароль изменен',
|
||||
};
|
||||
|
||||
export const ERRORS = {
|
||||
TYPED_OLD_PASSWORD: 'Заполните старый пароль',
|
||||
TYPED_NEW_PASSWORD: 'Заполните новый пароль',
|
||||
PASSWORD_NOT_EQUAL: 'Пароли не совпадают',
|
||||
REPEAT_PASSWORD: 'Повторите новый пароль',
|
||||
NOT_CORRECT_PASSWORD: 'Неверный старый пароль',
|
||||
};
|
||||
|
||||
@ -1,6 +1,3 @@
|
||||
.Api__value-input {
|
||||
height: 200px;
|
||||
}
|
||||
.Api__view-controls {
|
||||
position: sticky;
|
||||
bottom: 0;
|
||||
|
||||
@ -1,11 +1,14 @@
|
||||
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, EVENTS, MODES} from '../../../../core/consts';
|
||||
import {EVENTS, MODES, CODE_EDITOR_MODE, FORM_TYPES} from '../../../../core/consts';
|
||||
import './ApiTableViewForm.css';
|
||||
import storageApi from '../../api/StorageServiceAPI';
|
||||
import routeService from '../../../../services/RouteService';
|
||||
import {HooksComponent} from '../hooks-component/HooksComponent';
|
||||
import {FormCodeEditor} from '../../../../core/components/form-code-editor/FormCodeEditor';
|
||||
import userInfoService from '../../../../services/UserInfoService';
|
||||
import usersServiceApi from '../../../users/api/UsersServiceAPI';
|
||||
|
||||
const TITLE_MODES = {
|
||||
[MODES.Create]: 'Создание нового хранилища',
|
||||
@ -30,37 +33,6 @@ class ApiTableViewForm extends Component {
|
||||
|
||||
this.title.textContent = 'Информация о хранилище';
|
||||
|
||||
this.inputs = [
|
||||
this.keyInput = this.createComponent(FormControl, this.form, {
|
||||
id: 'api-key-input',
|
||||
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: 'Название сервиса',
|
||||
required: true,
|
||||
}),
|
||||
this.authorInput = this.createComponent(FormControl, this.form, {
|
||||
id: 'api-author-input',
|
||||
label: 'Автор хранилища',
|
||||
required: true,
|
||||
}),
|
||||
this.descriptionInput = this.createComponent(FormControl, this.form, {
|
||||
id: 'api-description-input',
|
||||
label: 'Краткое описание',
|
||||
required: true,
|
||||
}),
|
||||
this.valueInput = this.createComponent(FormControl, this.form, {
|
||||
id: 'api-value-input',
|
||||
type: FORM_TYPES.TEXTAREA,
|
||||
className: 'Api__value-input',
|
||||
label: 'Содержимое хранилища',
|
||||
required: true,
|
||||
}),
|
||||
];
|
||||
|
||||
this.buttons = [
|
||||
{
|
||||
button: this.createButton = this.mainNode.querySelector('.ApiViewForm__create'),
|
||||
@ -69,10 +41,12 @@ class ApiTableViewForm extends Component {
|
||||
{
|
||||
button: this.saveButton = this.mainNode.querySelector('.ApiViewForm__save'),
|
||||
modes: [MODES.Edit],
|
||||
onlyOwner: true,
|
||||
},
|
||||
{
|
||||
button: this.editButton = this.mainNode.querySelector('.ApiViewForm__edit'),
|
||||
modes: [MODES.View],
|
||||
onlyOwner: true,
|
||||
},
|
||||
{
|
||||
button: this.cancelButton = this.mainNode.querySelector('.ApiViewForm__cancel'),
|
||||
@ -81,6 +55,7 @@ class ApiTableViewForm extends Component {
|
||||
{
|
||||
button: this.deleteButton = this.mainNode.querySelector('.ApiViewForm__delete'),
|
||||
modes: [MODES.View, MODES.Edit],
|
||||
onlyOwner: true,
|
||||
}
|
||||
];
|
||||
|
||||
@ -96,31 +71,74 @@ class ApiTableViewForm extends Component {
|
||||
|
||||
this.addEventListener(this.cancelButton, EVENTS.CLICK, this.handleCloseModal);
|
||||
|
||||
this.hooksContainer = this.createComponent(HooksComponent, this.mainNode);
|
||||
this.form.insertAdjacentElement('afterend', this.hooksContainer.mainNode);
|
||||
|
||||
this.addSubscribe(routeService, EVENTS.ROUTE_SEARCH_CHANGE, this.setForm);
|
||||
|
||||
this.renderInputs();
|
||||
}
|
||||
|
||||
renderInputs = async () => {
|
||||
const users = await usersServiceApi.request();
|
||||
this.inputs = [
|
||||
this.keyInput = this.createComponent(FormControl, this.form, {
|
||||
id: 'api-key-input',
|
||||
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: 'Название сервиса',
|
||||
required: true,
|
||||
}),
|
||||
this.authorInput = this.createComponent(FormControl, this.form, {
|
||||
id: 'api-author-input',
|
||||
label: 'Автор хранилища',
|
||||
type: FORM_TYPES.SELECT,
|
||||
options: users.map((user) => ({
|
||||
value: user.login,
|
||||
text: user.login,
|
||||
})),
|
||||
}),
|
||||
this.descriptionInput = this.createComponent(FormControl, this.form, {
|
||||
id: 'api-description-input',
|
||||
label: 'Краткое описание',
|
||||
required: true,
|
||||
}),
|
||||
this.valueInput = this.createComponent(FormCodeEditor, this.form, {
|
||||
id: 'api-value-input',
|
||||
className: 'Api__value-input',
|
||||
label: 'Содержимое хранилища',
|
||||
mode: CODE_EDITOR_MODE.JSON,
|
||||
required: true,
|
||||
}),
|
||||
this.hideSelect = this.createComponent(FormControl, this.form, {
|
||||
id: 'api-hide-select',
|
||||
label: 'Видимость хранилища',
|
||||
type: FORM_TYPES.SELECT,
|
||||
options: [
|
||||
{value: true, text: 'Скрыть'},
|
||||
{value: false, text: 'Показать'},
|
||||
],
|
||||
}),
|
||||
];
|
||||
|
||||
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);
|
||||
}
|
||||
this.setForm();
|
||||
}
|
||||
|
||||
handleCloseModal = () => {
|
||||
routeService.pushQuery({}, true);
|
||||
}
|
||||
|
||||
setVisibleHooks = (mode) => {
|
||||
const method = mode !== MODES.View ? 'add' : 'remove';
|
||||
setVisibleHooks = (mode, isAvailable) => {
|
||||
const isHide = mode !== MODES.View || !isAvailable;
|
||||
const method = isHide ? 'add' : 'remove';
|
||||
this.hooksContainer.mainNode.classList[method]('invisible');
|
||||
}
|
||||
|
||||
@ -145,17 +163,26 @@ class ApiTableViewForm extends Component {
|
||||
};
|
||||
}
|
||||
|
||||
setInputDisabled = (mode) => {
|
||||
setInputDisabled = (mode, isAdmin) => {
|
||||
const disabled = !EDIT_MODES.includes(mode);
|
||||
this.inputs.forEach((input) => {
|
||||
const disabledLogin = this.keyInput === input && mode === MODES.Edit;
|
||||
input.disabled(disabled ? disabled : disabledLogin);
|
||||
if (input === this.keyInput) {
|
||||
const disabledKey = mode !== MODES.Create;
|
||||
input.disabled(disabled ? disabled : disabledKey);
|
||||
return;
|
||||
}
|
||||
if (input === this.authorInput) {
|
||||
const disabledAuthor = mode !== MODES.Create || !isAdmin;
|
||||
input.disabled(disabled ? disabled : disabledAuthor);
|
||||
return;
|
||||
}
|
||||
input.disabled(disabled);
|
||||
});
|
||||
}
|
||||
|
||||
showButtons = (mode) => {
|
||||
this.buttons.forEach(({button, modes}) => {
|
||||
const isShow = modes.includes(mode);
|
||||
showButtons = (mode, isAvailable) => {
|
||||
this.buttons.forEach(({button, modes, onlyOwner}) => {
|
||||
const isShow = modes.includes(mode) && (onlyOwner ? isAvailable : true);
|
||||
button.style.display = isShow ? 'inline-block' : 'none';
|
||||
});
|
||||
}
|
||||
@ -201,6 +228,7 @@ class ApiTableViewForm extends Component {
|
||||
service_name: this.serviceNameInput.getValue(),
|
||||
author: this.authorInput.getValue(),
|
||||
description: this.descriptionInput.getValue(),
|
||||
hide: this.hideSelect.getValue(),
|
||||
});
|
||||
|
||||
this.clearForm();
|
||||
@ -216,6 +244,7 @@ class ApiTableViewForm extends Component {
|
||||
service_name: this.serviceNameInput.getValue(),
|
||||
author: this.authorInput.getValue(),
|
||||
description: this.descriptionInput.getValue(),
|
||||
hide: this.hideSelect.getValue(),
|
||||
});
|
||||
this.clearForm();
|
||||
routeService.pushQuery({}, true);
|
||||
@ -234,17 +263,22 @@ class ApiTableViewForm extends Component {
|
||||
this.keyInput.setValue(key);
|
||||
}
|
||||
|
||||
setForm = async (mode, storeKey) => {
|
||||
const {key, value, description, service_name, author} = await this.loadStore(storeKey);
|
||||
setForm = async () => {
|
||||
const {query: {mode, key: storeKey}} = routeService.getUrlData();
|
||||
const {key, value, description, service_name, author, hide} = await this.loadStore(storeKey);
|
||||
const {login, is_admin} = await userInfoService.getUserInfo();
|
||||
const isAvailableEdit = author === login || is_admin;
|
||||
this.setSidebarTitle(mode, storeKey);
|
||||
this.setKeyInput(mode, key);
|
||||
this.serviceNameInput.setValue(service_name);
|
||||
this.authorInput.setValue(author);
|
||||
this.authorInput.setValue(author || login);
|
||||
this.descriptionInput.setValue(description);
|
||||
this.valueInput.setValue(this.stringifyValue(value));
|
||||
this.hideSelect.setValue(hide);
|
||||
|
||||
this.setInputDisabled(mode);
|
||||
this.showButtons(mode);
|
||||
this.setInputDisabled(mode, is_admin);
|
||||
this.showButtons(mode, isAvailableEdit);
|
||||
this.setVisibleHooks(mode, isAvailableEdit);
|
||||
|
||||
if (mode) {
|
||||
this.sidebar.show();
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import http from '../../../api/HttpAPI';
|
||||
|
||||
const ROOT_URL = 'http://api.auth.vigdorov.ru/users';
|
||||
const ROOT_URL = 'https://api.auth.vigdorov.ru/users';
|
||||
|
||||
/**
|
||||
* Объект с полями для создания пользователя
|
||||
|
||||
@ -27,7 +27,7 @@ class UsersPage extends Component {
|
||||
});
|
||||
|
||||
this.addSubscribe(userInfoService, EVENTS.CHANGE_USER_INFO, ({is_admin}) => {
|
||||
this.createUserButton.disabled = !is_admin;
|
||||
this.setCreateUserButton(is_admin);
|
||||
});
|
||||
|
||||
this.addEventListener(this.createUserButton, EVENTS.CLICK, () => {
|
||||
@ -61,9 +61,13 @@ class UsersPage extends Component {
|
||||
this.initPage();
|
||||
}
|
||||
|
||||
setCreateUserButton = (isAdmin) => {
|
||||
this.createUserButton.style.display = isAdmin ? 'inline-block' : 'none';
|
||||
}
|
||||
|
||||
initPage = async () => {
|
||||
const user = await userInfoService.getUserInfo();
|
||||
this.createUserButton.disabled = !user.is_admin;
|
||||
this.setCreateUserButton(user.is_admin);
|
||||
this.userList = await usersServiceApi.request();
|
||||
this.renderTable();
|
||||
}
|
||||
|
||||
@ -85,20 +85,20 @@ class UserViewForm extends Component {
|
||||
}
|
||||
];
|
||||
|
||||
this.addEventListener(this.cancelButton, 'click', () => {
|
||||
this.addEventListener(this.cancelButton, EVENTS.CLICK, () => {
|
||||
routeService.pushQuery({}, true);
|
||||
});
|
||||
|
||||
this.addEventListener(this.editButton, 'click', () => {
|
||||
this.addEventListener(this.editButton, EVENTS.CLICK, () => {
|
||||
routeService.pushQuery({mode: MODES.Edit});
|
||||
});
|
||||
|
||||
this.addEventListener(this.form, 'submit', (event) => {
|
||||
this.addEventListener(this.form, EVENTS.SUBMIT, (event) => {
|
||||
event.preventDefault();
|
||||
});
|
||||
this.addEventListener(this.createButton, 'click', this.createUser);
|
||||
this.addEventListener(this.saveButton, 'click', this.saveUser);
|
||||
this.addEventListener(this.deleteButton, 'click', this.deleteUser);
|
||||
this.addEventListener(this.createButton, EVENTS.CLICK, this.createUser);
|
||||
this.addEventListener(this.saveButton, EVENTS.CLICK, this.saveUser);
|
||||
this.addEventListener(this.deleteButton, EVENTS.CLICK, this.deleteUser);
|
||||
|
||||
this.addSubscribe(routeService, EVENTS.ROUTE_SEARCH_CHANGE, ({query}) => {
|
||||
const {mode, login} = query;
|
||||
@ -128,7 +128,7 @@ class UserViewForm extends Component {
|
||||
});
|
||||
}
|
||||
|
||||
validateInputs = () => {
|
||||
validateInputs = (isEditMode) => {
|
||||
this.form.classList.add('was-validated');
|
||||
const login = this.login.getValue();
|
||||
|
||||
@ -147,6 +147,10 @@ class UserViewForm extends Component {
|
||||
return '';
|
||||
})();
|
||||
|
||||
if (isEditMode) {
|
||||
this.password.setError('');
|
||||
}
|
||||
|
||||
this.login.setError(loginErrorMessage);
|
||||
|
||||
return this.form.checkValidity();
|
||||
@ -174,7 +178,7 @@ class UserViewForm extends Component {
|
||||
}
|
||||
|
||||
saveUser = () => {
|
||||
if (this.validateInputs()) {
|
||||
if (this.validateInputs(true)) {
|
||||
this.next(EVENTS.SAVE_USER, {
|
||||
login: this.login.getValue(),
|
||||
avatar: this.avatar.getValue(),
|
||||
@ -214,6 +218,11 @@ class UserViewForm extends Component {
|
||||
const isShow = mode === MODES.Create;
|
||||
this.password.setValue('');
|
||||
this.password.mainNode.style.display = isShow ? 'block' : 'none';
|
||||
if (isShow) {
|
||||
this.login.mainNode.after(this.password.mainNode);
|
||||
} else {
|
||||
this.mainNode.appendChild(this.password.mainNode);
|
||||
}
|
||||
}
|
||||
|
||||
setLogin = (mode, login) => {
|
||||
|
||||
@ -11,6 +11,7 @@ class UserInfoService extends EmitService {
|
||||
this.userInfo = {
|
||||
login: NOT_USER,
|
||||
avatar: DEFAULT_AVATAR,
|
||||
is_admin: false,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user