Compare commits

...

10 Commits

25 changed files with 369 additions and 100 deletions

View File

@ -58,10 +58,11 @@
"webpack-dev-server": "^3.11.0" "webpack-dev-server": "^3.11.0"
}, },
"dependencies": { "dependencies": {
"@popperjs/core": "^2.9.2",
"ace-builds": "^1.4.12",
"axios": "^0.19.2", "axios": "^0.19.2",
"bootstrap": "^5.0.0-alpha1", "bootstrap": "^5.0.0-alpha1",
"moment": "^2.27.0", "moment": "^2.27.0",
"popper.js": "^1.16.1",
"query-string": "^6.13.1", "query-string": "^6.13.1",
"uuid": "^8.2.0" "uuid": "^8.2.0"
} }

View File

@ -1,6 +1,6 @@
import http from './HttpAPI'; import http from './HttpAPI';
const ROOT_URL = 'http://api.auth.vigdorov.ru/users'; const ROOT_URL = 'https://api.auth.vigdorov.ru/users';
const profileServiceApi = { const profileServiceApi = {
getSelfInfo: async () => { getSelfInfo: async () => {

View File

@ -10,11 +10,11 @@ export const API_NAME_HEADER = 'api-name';
export const API_OPTIONS = { export const API_OPTIONS = {
[API_NAMES.PRODUCTION]: { [API_NAMES.PRODUCTION]: {
url: 'http://api.storage.vigdorov.ru', url: 'https://api.storage.vigdorov.ru',
options: {}, options: {},
}, },
[API_NAMES.TESTING]: { [API_NAMES.TESTING]: {
url: 'http://api.storage.vigdorov.ru', url: 'https://api.storage.vigdorov.ru',
options: { options: {
headers: { headers: {
'Api-Name': 'store-service-test', 'Api-Name': 'store-service-test',
@ -22,7 +22,7 @@ export const API_OPTIONS = {
}, },
}, },
[API_NAMES.DEVELOP]: { [API_NAMES.DEVELOP]: {
url: 'http://localhost:4001', url: 'https://localhost:4001',
options: {}, options: {},
}, },
}; };
@ -34,7 +34,7 @@ export const ENDPOINTS = {
HOOKS: '/hooks', HOOKS: '/hooks',
}; };
export const AUTH_SERVICE = 'http://api.auth.vigdorov.ru'; export const AUTH_SERVICE = 'https://api.auth.vigdorov.ru';
export const AUTH_ENDPOINTS = { export const AUTH_ENDPOINTS = {
AUTH: '/auth', AUTH: '/auth',

View File

@ -32,6 +32,26 @@
</nav> </nav>
</template> </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"> <template id="main-statistic">
<div class="h-100"> <div class="h-100">
@ -81,10 +101,6 @@
<div class="Login__title h3"></div> <div class="Login__title h3"></div>
<form class="Login__form needs-validation" novalidate> <form class="Login__form needs-validation" novalidate>
<div class="Login__inputContainer"></div> <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> <button type="submit" class="btn btn-primary Login__submit">Войти</button>
</form> </form>
</div> </div>
@ -472,7 +488,7 @@
<!-- Шаблон для Модального Сайдбара --> <!-- Шаблон для Модального Сайдбара -->
<template id="modal-sidebar"> <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"> <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"> <button class="ModalSidebar__close d-flex justify-center align-items-center" type="button">
<i class="fas fa-times"></i> <i class="fas fa-times"></i>

View File

@ -15,6 +15,7 @@ import authServiceApi from './api/AuthServiceAPI';
import userInfoService from './services/UserInfoService'; import userInfoService from './services/UserInfoService';
import {EVENTS, ROUTES} from './core/consts'; import {EVENTS, ROUTES} from './core/consts';
import UsersPage from './pages/users/components/page/Page'; import UsersPage from './pages/users/components/page/Page';
import {DocumentationPage} from './pages/documentation/components/page/Page';
const initAppComponents = () => { const initAppComponents = () => {
const mainMenu = new MainMenu(); const mainMenu = new MainMenu();
@ -36,10 +37,11 @@ const initAppComponents = () => {
routerPagesContainer.addRoutes([ routerPagesContainer.addRoutes([
{url: ROUTES.MAIN, pageComponent: MainPage}, {url: ROUTES.MAIN, pageComponent: MainPage},
{url: ROUTES.STORE, pageComponent: ApiPage}, {url: ROUTES.STORE, pageComponent: ApiPage},
{url: ROUTES.LOGS, pageComponent: LogsPage}, {url: ROUTES.LOGS, pageComponent: LogsPage, onlyAdmin: true},
{url: ROUTES.USERS, pageComponent: UsersPage}, {url: ROUTES.USERS, pageComponent: UsersPage},
{url: ROUTES.LOGIN, pageComponent: LoginPage}, {url: ROUTES.LOGIN, pageComponent: LoginPage},
{url: ROUTES.PROFILE, pageComponent: ProfilePage}, {url: ROUTES.PROFILE, pageComponent: ProfilePage},
{url: ROUTES.DOCUMENTATION, pageComponent: DocumentationPage},
]); ]);
/** /**

View File

@ -0,0 +1,7 @@
.CodeEditor__editor {
width: 100%;
}
.CodeEditor__editor_disabled {
background-color: #e9ecefa6;
}

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

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

View 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('');
}
}

View File

@ -88,7 +88,7 @@ class FormControl extends Component {
} }
clearError = () => { clearError = () => {
this.errorText.textContent = ''; this.setError('');
} }
getInputTagName = (type) => { getInputTagName = (type) => {

View File

@ -4,6 +4,7 @@ import routeService from '../../../services/RouteService';
import './MainMenu.css'; import './MainMenu.css';
import {EVENTS, ROUTES, TAG_NAME} from '../../consts'; import {EVENTS, ROUTES, TAG_NAME} from '../../consts';
import tokenApi from '../../../api/TokenAPI'; import tokenApi from '../../../api/TokenAPI';
import userInfoService from '../../../services/UserInfoService';
const NAV_MENU = [ const NAV_MENU = [
{ {
@ -17,11 +18,16 @@ const NAV_MENU = [
{ {
title: 'Журнал', title: 'Журнал',
url: ROUTES.LOGS, url: ROUTES.LOGS,
onlyAdmin: true,
}, },
{ {
title: 'Пользователи', title: 'Пользователи',
url: ROUTES.USERS, url: ROUTES.USERS,
}, },
{
title: 'Документация',
url: ROUTES.DOCUMENTATION,
},
{ {
title: 'Личный кабинет', title: 'Личный кабинет',
url: ROUTES.PROFILE, url: ROUTES.PROFILE,
@ -72,7 +78,7 @@ class MainMenu extends Component {
} }
render = () => { render = () => {
this.menuItems = NAV_MENU.map(({url, title, className = ''}) => { this.menuItems = NAV_MENU.map(({url, title, className = '', onlyAdmin}) => {
const li = this.createElement({ const li = this.createElement({
tagName: TAG_NAME.LI, tagName: TAG_NAME.LI,
parentNode: this.buttonsContainer, 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(); 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); document.body.prepend(this.mainNode);
} }
} }

View File

@ -9,7 +9,7 @@ const HIDE_SHADOW_CLASS = 'ModalSidebar__shadow_hide';
const SHADOW_CLASS = 'ModalSidebar__shadow'; const SHADOW_CLASS = 'ModalSidebar__shadow';
const CROSS_BUTTON_CLASS = 'ModalSidebar__close'; const CROSS_BUTTON_CLASS = 'ModalSidebar__close';
const WINDOW_CLASS = 'ModalSidebar__window'; const WINDOW_CLASS = 'ModalSidebar__window';
const WINDOW_INIT_CLASS = 'invisible';
class ModalSidebar extends Component { class ModalSidebar extends Component {
constructor ({ constructor ({
content, content,
@ -56,6 +56,7 @@ class ModalSidebar extends Component {
} }
show = () => { show = () => {
this.mainNode.classList.remove(WINDOW_INIT_CLASS);
this.mainNode.classList.add(SHOW_WINDOW_CLASS); this.mainNode.classList.add(SHOW_WINDOW_CLASS);
this.shadow.classList.add(SHOW_SHADOW_CLASS); this.shadow.classList.add(SHOW_SHADOW_CLASS);

View File

@ -3,6 +3,7 @@ import routeService from '../../../services/RouteService';
import NotFoundPage from '../../../pages/not-found/components/page/Page'; import NotFoundPage from '../../../pages/not-found/components/page/Page';
import {EVENTS, ROUTES} from '../../consts'; import {EVENTS, ROUTES} from '../../consts';
import userInfoService from '../../../services/UserInfoService';
/** /**
* @interface Route * @interface Route
@ -59,6 +60,14 @@ class RouterPagesContainer extends Component {
this.currentPage = new PageComponent('#page', this.mainNode); 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);
}
});
} }
/** /**

View File

@ -69,4 +69,9 @@ export const ROUTES = {
USERS: '/users', USERS: '/users',
LOGIN: '/login', LOGIN: '/login',
PROFILE: '/profile', PROFILE: '/profile',
DOCUMENTATION: '/documentation',
};
export const CODE_EDITOR_MODE = {
JSON: 'json',
}; };

View 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: 'Документация',
},
});
}
}

View File

@ -11,7 +11,6 @@ class LoginForm extends Component {
this.form = this.mainNode.querySelector('.Login__form'); this.form = this.mainNode.querySelector('.Login__form');
this.inputContainer = this.mainNode.querySelector('.Login__inputContainer'); this.inputContainer = this.mainNode.querySelector('.Login__inputContainer');
this.submitButton = this.mainNode.querySelector('.Login__submit'); this.submitButton = this.mainNode.querySelector('.Login__submit');
this.checkboxSystem = this.mainNode.querySelector('.form-check-input');
this.logoImage = this.mainNode.querySelector('.Login__logo'); this.logoImage = this.mainNode.querySelector('.Login__logo');
this.logoImage.src = Image; this.logoImage.src = Image;
@ -39,7 +38,6 @@ class LoginForm extends Component {
this.loginControl.input, this.loginControl.input,
this.passwordControl.input, this.passwordControl.input,
this.submitButton, this.submitButton,
this.checkboxSystem,
]; ];
elements.forEach((element) => { elements.forEach((element) => {
@ -61,6 +59,11 @@ class LoginForm extends Component {
return this.form.checkValidity(); return this.form.checkValidity();
} }
setError = (message) => {
this.loginControl.setError(message);
this.passwordControl.setError(message);
}
submit = (event) => { submit = (event) => {
event.preventDefault(); event.preventDefault();
if (this.validateInputs()) { if (this.validateInputs()) {

View File

@ -6,6 +6,11 @@ import routeService from '../../../../services/RouteService';
import notify from '../../../../services/NotifyService'; import notify from '../../../../services/NotifyService';
import {ROUTES, EVENTS} from '../../../../core/consts'; import {ROUTES, EVENTS} from '../../../../core/consts';
const ERRORS = {
NOT_CORRECT: 'Неверный логин или пароль',
UNKNOWN_ERROR: 'Неизвестная ошибка',
};
class LoginPage extends Component { class LoginPage extends Component {
constructor (mainNodeSelector, parentNode) { constructor (mainNodeSelector, parentNode) {
super(mainNodeSelector, parentNode); super(mainNodeSelector, parentNode);
@ -21,8 +26,12 @@ class LoginPage extends Component {
routeService.goTo(ROUTES.MAIN); routeService.goTo(ROUTES.MAIN);
}) })
.catch((e) => { .catch((e) => {
const message = e?.response?.data?.message || 'Неизвестная ошибка'; const message = e?.response?.data?.message || ERRORS.UNKNOWN_ERROR;
notify.warn(message); if (message === ERRORS.NOT_CORRECT) {
this.form.setError(ERRORS.NOT_CORRECT);
} else {
notify.warn(message);
}
this.form.disabled(false); this.form.disabled(false);
}); });
}); });

View File

@ -1,7 +1,7 @@
import Component from '../../../../core/components/component/Component'; import Component from '../../../../core/components/component/Component';
import FormControl from '../../../../core/components/form-control/FormControl'; import FormControl from '../../../../core/components/form-control/FormControl';
import {FORM_TYPES, EVENTS} from '../../../../core/consts'; 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 notify from '../../../../services/NotifyService';
import profileServiceApi from '../../../../api/ProfileServiceAPI'; import profileServiceApi from '../../../../api/ProfileServiceAPI';
@ -48,21 +48,22 @@ class ProfileContent extends Component {
const newPasswordRepeat = this.newPasswordRepeat.getValue(); const newPasswordRepeat = this.newPasswordRepeat.getValue();
if (!oldPassword) { if (!oldPassword) {
this.oldPassword.setError('Заполните старый пароль'); this.oldPassword.setError(ERRORS.TYPED_OLD_PASSWORD);
} }
if (!newPassword) { if (!newPassword) {
this.newPassword.setError('Заполните новый пароль'); this.newPassword.setError(ERRORS.TYPED_NEW_PASSWORD);
} }
const isEqualPassword = newPassword === newPasswordRepeat; const isEqualPassword = newPassword === newPasswordRepeat;
if (!isEqualPassword) { if (!isEqualPassword) {
notify.warn('Пароли не совпадают'); this.newPassword.setError(ERRORS.PASSWORD_NOT_EQUAL);
this.newPasswordRepeat.setError(ERRORS.PASSWORD_NOT_EQUAL);
} }
if (!newPasswordRepeat) { if (!newPasswordRepeat) {
this.newPasswordRepeat.setError('Повторите новый пароль'); this.newPasswordRepeat.setError(ERRORS.REPEAT_PASSWORD);
} }
return this.form.checkValidity() && isEqualPassword; return this.form.checkValidity() && isEqualPassword;
@ -80,9 +81,16 @@ class ProfileContent extends Component {
if (this.validateInputs()) { if (this.validateInputs()) {
const oldPassword = this.oldPassword.getValue(); const oldPassword = this.oldPassword.getValue();
const newPassword = this.newPassword.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(); this.clearForm();
notify.success('Пароль успешно изменен'); notify.success(LABELS.SUCCESS_CHANGE_PASSWORD);
} }
} }

View File

@ -11,4 +11,13 @@ export const LABELS = {
OLD_PASSWORD_PLACEHOLDER: 'Введите старый пароль', OLD_PASSWORD_PLACEHOLDER: 'Введите старый пароль',
NEW_PASSWORD_PLACEHOLDER: 'Введите новый пароль', NEW_PASSWORD_PLACEHOLDER: 'Введите новый пароль',
NEW_PASSWORD_REPEAT_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: 'Неверный старый пароль',
}; };

View File

@ -1,6 +1,3 @@
.Api__value-input {
height: 200px;
}
.Api__view-controls { .Api__view-controls {
position: sticky; position: sticky;
bottom: 0; bottom: 0;

View File

@ -1,11 +1,14 @@
import Component from '../../../../core/components/component/Component'; import Component from '../../../../core/components/component/Component';
import ModalSidebar from '../../../../core/components/modal-sidebar/ModalSibebar'; import ModalSidebar from '../../../../core/components/modal-sidebar/ModalSibebar';
import FormControl from '../../../../core/components/form-control/FormControl'; 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 './ApiTableViewForm.css';
import storageApi from '../../api/StorageServiceAPI'; import storageApi from '../../api/StorageServiceAPI';
import routeService from '../../../../services/RouteService'; import routeService from '../../../../services/RouteService';
import {HooksComponent} from '../hooks-component/HooksComponent'; 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 = { const TITLE_MODES = {
[MODES.Create]: 'Создание нового хранилища', [MODES.Create]: 'Создание нового хранилища',
@ -30,37 +33,6 @@ class ApiTableViewForm extends Component {
this.title.textContent = 'Информация о хранилище'; 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 = [ this.buttons = [
{ {
button: this.createButton = this.mainNode.querySelector('.ApiViewForm__create'), button: this.createButton = this.mainNode.querySelector('.ApiViewForm__create'),
@ -69,10 +41,12 @@ class ApiTableViewForm extends Component {
{ {
button: this.saveButton = this.mainNode.querySelector('.ApiViewForm__save'), button: this.saveButton = this.mainNode.querySelector('.ApiViewForm__save'),
modes: [MODES.Edit], modes: [MODES.Edit],
onlyOwner: true,
}, },
{ {
button: this.editButton = this.mainNode.querySelector('.ApiViewForm__edit'), button: this.editButton = this.mainNode.querySelector('.ApiViewForm__edit'),
modes: [MODES.View], modes: [MODES.View],
onlyOwner: true,
}, },
{ {
button: this.cancelButton = this.mainNode.querySelector('.ApiViewForm__cancel'), button: this.cancelButton = this.mainNode.querySelector('.ApiViewForm__cancel'),
@ -81,6 +55,7 @@ class ApiTableViewForm extends Component {
{ {
button: this.deleteButton = this.mainNode.querySelector('.ApiViewForm__delete'), button: this.deleteButton = this.mainNode.querySelector('.ApiViewForm__delete'),
modes: [MODES.View, MODES.Edit], modes: [MODES.View, MODES.Edit],
onlyOwner: true,
} }
]; ];
@ -96,31 +71,74 @@ class ApiTableViewForm extends Component {
this.addEventListener(this.cancelButton, EVENTS.CLICK, this.handleCloseModal); 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) => { this.inputs.forEach((input) => {
input.disabled(true); input.disabled(true);
}); });
this.hooksContainer = this.createComponent(HooksComponent, this.mainNode); this.setForm();
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);
}
} }
handleCloseModal = () => { handleCloseModal = () => {
routeService.pushQuery({}, true); routeService.pushQuery({}, true);
} }
setVisibleHooks = (mode) => { setVisibleHooks = (mode, isAvailable) => {
const method = mode !== MODES.View ? 'add' : 'remove'; const isHide = mode !== MODES.View || !isAvailable;
const method = isHide ? 'add' : 'remove';
this.hooksContainer.mainNode.classList[method]('invisible'); 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); const disabled = !EDIT_MODES.includes(mode);
this.inputs.forEach((input) => { this.inputs.forEach((input) => {
const disabledLogin = this.keyInput === input && mode === MODES.Edit; if (input === this.keyInput) {
input.disabled(disabled ? disabled : disabledLogin); 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) => { showButtons = (mode, isAvailable) => {
this.buttons.forEach(({button, modes}) => { this.buttons.forEach(({button, modes, onlyOwner}) => {
const isShow = modes.includes(mode); const isShow = modes.includes(mode) && (onlyOwner ? isAvailable : true);
button.style.display = isShow ? 'inline-block' : 'none'; button.style.display = isShow ? 'inline-block' : 'none';
}); });
} }
@ -201,6 +228,7 @@ class ApiTableViewForm extends Component {
service_name: this.serviceNameInput.getValue(), service_name: this.serviceNameInput.getValue(),
author: this.authorInput.getValue(), author: this.authorInput.getValue(),
description: this.descriptionInput.getValue(), description: this.descriptionInput.getValue(),
hide: this.hideSelect.getValue(),
}); });
this.clearForm(); this.clearForm();
@ -216,6 +244,7 @@ class ApiTableViewForm extends Component {
service_name: this.serviceNameInput.getValue(), service_name: this.serviceNameInput.getValue(),
author: this.authorInput.getValue(), author: this.authorInput.getValue(),
description: this.descriptionInput.getValue(), description: this.descriptionInput.getValue(),
hide: this.hideSelect.getValue(),
}); });
this.clearForm(); this.clearForm();
routeService.pushQuery({}, true); routeService.pushQuery({}, true);
@ -234,17 +263,22 @@ class ApiTableViewForm extends Component {
this.keyInput.setValue(key); this.keyInput.setValue(key);
} }
setForm = async (mode, storeKey) => { setForm = async () => {
const {key, value, description, service_name, author} = await this.loadStore(storeKey); 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.setSidebarTitle(mode, storeKey);
this.setKeyInput(mode, key); this.setKeyInput(mode, key);
this.serviceNameInput.setValue(service_name); this.serviceNameInput.setValue(service_name);
this.authorInput.setValue(author); this.authorInput.setValue(author || login);
this.descriptionInput.setValue(description); this.descriptionInput.setValue(description);
this.valueInput.setValue(this.stringifyValue(value)); this.valueInput.setValue(this.stringifyValue(value));
this.hideSelect.setValue(hide);
this.setInputDisabled(mode); this.setInputDisabled(mode, is_admin);
this.showButtons(mode); this.showButtons(mode, isAvailableEdit);
this.setVisibleHooks(mode, isAvailableEdit);
if (mode) { if (mode) {
this.sidebar.show(); this.sidebar.show();

View File

@ -1,6 +1,6 @@
import http from '../../../api/HttpAPI'; import http from '../../../api/HttpAPI';
const ROOT_URL = 'http://api.auth.vigdorov.ru/users'; const ROOT_URL = 'https://api.auth.vigdorov.ru/users';
/** /**
* Объект с полями для создания пользователя * Объект с полями для создания пользователя

View File

@ -27,7 +27,7 @@ class UsersPage extends Component {
}); });
this.addSubscribe(userInfoService, EVENTS.CHANGE_USER_INFO, ({is_admin}) => { this.addSubscribe(userInfoService, EVENTS.CHANGE_USER_INFO, ({is_admin}) => {
this.createUserButton.disabled = !is_admin; this.setCreateUserButton(is_admin);
}); });
this.addEventListener(this.createUserButton, EVENTS.CLICK, () => { this.addEventListener(this.createUserButton, EVENTS.CLICK, () => {
@ -61,9 +61,13 @@ class UsersPage extends Component {
this.initPage(); this.initPage();
} }
setCreateUserButton = (isAdmin) => {
this.createUserButton.style.display = isAdmin ? 'inline-block' : 'none';
}
initPage = async () => { initPage = async () => {
const user = await userInfoService.getUserInfo(); const user = await userInfoService.getUserInfo();
this.createUserButton.disabled = !user.is_admin; this.setCreateUserButton(user.is_admin);
this.userList = await usersServiceApi.request(); this.userList = await usersServiceApi.request();
this.renderTable(); this.renderTable();
} }

View File

@ -85,20 +85,20 @@ class UserViewForm extends Component {
} }
]; ];
this.addEventListener(this.cancelButton, 'click', () => { this.addEventListener(this.cancelButton, EVENTS.CLICK, () => {
routeService.pushQuery({}, true); routeService.pushQuery({}, true);
}); });
this.addEventListener(this.editButton, 'click', () => { this.addEventListener(this.editButton, EVENTS.CLICK, () => {
routeService.pushQuery({mode: MODES.Edit}); routeService.pushQuery({mode: MODES.Edit});
}); });
this.addEventListener(this.form, 'submit', (event) => { this.addEventListener(this.form, EVENTS.SUBMIT, (event) => {
event.preventDefault(); event.preventDefault();
}); });
this.addEventListener(this.createButton, 'click', this.createUser); this.addEventListener(this.createButton, EVENTS.CLICK, this.createUser);
this.addEventListener(this.saveButton, 'click', this.saveUser); this.addEventListener(this.saveButton, EVENTS.CLICK, this.saveUser);
this.addEventListener(this.deleteButton, 'click', this.deleteUser); this.addEventListener(this.deleteButton, EVENTS.CLICK, this.deleteUser);
this.addSubscribe(routeService, EVENTS.ROUTE_SEARCH_CHANGE, ({query}) => { this.addSubscribe(routeService, EVENTS.ROUTE_SEARCH_CHANGE, ({query}) => {
const {mode, login} = query; const {mode, login} = query;
@ -128,7 +128,7 @@ class UserViewForm extends Component {
}); });
} }
validateInputs = () => { validateInputs = (isEditMode) => {
this.form.classList.add('was-validated'); this.form.classList.add('was-validated');
const login = this.login.getValue(); const login = this.login.getValue();
@ -147,6 +147,10 @@ class UserViewForm extends Component {
return ''; return '';
})(); })();
if (isEditMode) {
this.password.setError('');
}
this.login.setError(loginErrorMessage); this.login.setError(loginErrorMessage);
return this.form.checkValidity(); return this.form.checkValidity();
@ -174,7 +178,7 @@ class UserViewForm extends Component {
} }
saveUser = () => { saveUser = () => {
if (this.validateInputs()) { if (this.validateInputs(true)) {
this.next(EVENTS.SAVE_USER, { this.next(EVENTS.SAVE_USER, {
login: this.login.getValue(), login: this.login.getValue(),
avatar: this.avatar.getValue(), avatar: this.avatar.getValue(),
@ -214,6 +218,11 @@ class UserViewForm extends Component {
const isShow = mode === MODES.Create; const isShow = mode === MODES.Create;
this.password.setValue(''); this.password.setValue('');
this.password.mainNode.style.display = isShow ? 'block' : 'none'; 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) => { setLogin = (mode, login) => {

View File

@ -11,6 +11,7 @@ class UserInfoService extends EmitService {
this.userInfo = { this.userInfo = {
login: NOT_USER, login: NOT_USER,
avatar: DEFAULT_AVATAR, avatar: DEFAULT_AVATAR,
is_admin: false,
}; };
} }