From fd1bc408bf6fe2e4033f4123ea53c22b7e336df7 Mon Sep 17 00:00:00 2001 From: Petr Krasnoshchekov Date: Wed, 25 Dec 2024 12:28:23 +0500 Subject: [PATCH 01/11] Add authentication --- Makefile | 6 +- app/index.ejs | 28 ++- app/scripts/app.js | 7 +- app/scripts/app.routes.js | 1 - .../loginForm/loginForm.controller.js | 8 +- .../controllers/accessLevelController.js | 42 ----- .../controllers/navigationController.js | 16 +- app/scripts/i18n/access/en.json | 27 --- app/scripts/i18n/access/ru.json | 27 --- app/scripts/i18n/app/en.json | 14 +- app/scripts/i18n/app/ru.json | 14 +- app/scripts/i18n/configurations/en.json | 3 +- app/scripts/i18n/configurations/ru.json | 3 +- app/scripts/i18n/devices/en.json | 3 +- app/scripts/i18n/devices/ru.json | 3 +- .../i18n/react/locales/en/translations.json | 21 ++- .../i18n/react/locales/ru/translations.json | 21 ++- app/scripts/i18n/rules/en.json | 3 +- app/scripts/i18n/rules/ru.json | 3 +- .../components/access-level/accessLevel.jsx | 1 - .../react-directives/forms/formStore.js | 4 +- .../react-directives/users/pageStore.js | 113 ++++++++++++ app/scripts/react-directives/users/users.js | 27 +++ .../react-directives/users/usersPage.jsx | 53 ++++++ app/scripts/services/roles.factory.js | 66 ++++--- app/styles/css/new.css | 16 ++ app/styles/main.css | 36 ++-- app/views/access-level.html | 49 +---- app/views/config.html | 3 +- app/views/configs.html | 3 +- app/views/devices.html | 3 +- app/views/logs.html | 3 +- app/views/network-connections.html | 3 +- app/views/scripts.html | 3 +- app/views/system.html | 3 +- debian/changelog | 6 + debian/control | 4 +- login/login.html | 169 ++++++++++++++++++ webpack.config.js | 16 +- 39 files changed, 573 insertions(+), 258 deletions(-) delete mode 100644 app/scripts/controllers/accessLevelController.js delete mode 100644 app/scripts/i18n/access/en.json delete mode 100644 app/scripts/i18n/access/ru.json create mode 100644 app/scripts/react-directives/users/pageStore.js create mode 100644 app/scripts/react-directives/users/users.js create mode 100644 app/scripts/react-directives/users/usersPage.jsx create mode 100644 login/login.html diff --git a/Makefile b/Makefile index 684e6dbc..ef0bac94 100644 --- a/Makefile +++ b/Makefile @@ -44,6 +44,7 @@ install: configs install -d -m 0777 $(DESTDIR)/var/www/images install -d -m 0777 $(DESTDIR)/var/www/uploads install -d -m 0777 $(DESTDIR)/var/www/scripts/i18n + install -d -m 0777 $(DESTDIR)/var/www/fonts cp -a dist/css/*.css $(DESTDIR)/var/www/css cp -a dist/images/* $(DESTDIR)/var/www/images @@ -52,13 +53,12 @@ install: configs cp -a dist/*.js $(DESTDIR)/var/www/ cp -a dist/*.svg $(DESTDIR)/var/www/ cp -a dist/*.png $(DESTDIR)/var/www/ - cp -a dist/*.ttf $(DESTDIR)/var/www/ - cp -a dist/*.woff $(DESTDIR)/var/www/ - cp -a dist/*.woff2 $(DESTDIR)/var/www/ || : + cp -a dist/fonts/* $(DESTDIR)/var/www/fonts install -m 0644 dist/404.html $(DESTDIR)/var/www/ install -m 0644 dist/robots.txt $(DESTDIR)/var/www/ install -m 0644 dist/index.html $(DESTDIR)/var/www/ + install -m 0644 login/login.html $(DESTDIR)/var/www/ install -Dm0644 dist/configs/*.json -t $(DESTDIR)/usr/share/wb-mqtt-homeui install -Dm0755 convert_config_v1v2.py $(DESTDIR)/usr/lib/wb-mqtt-homeui/convert_config_v1v2 diff --git a/app/index.ejs b/app/index.ejs index 00fd9d0e..aec416d3 100644 --- a/app/index.ejs +++ b/app/index.ejs @@ -24,6 +24,10 @@
+
@@ -102,7 +112,7 @@
  • {{'navigation.menu.channels' | translate}}
  • -
  • +
  • {{'navigation.menu.access' | translate}}
  • diff --git a/app/scripts/app.js b/app/scripts/app.js index 1febe99f..9cb98ce7 100644 --- a/app/scripts/app.js +++ b/app/scripts/app.js @@ -67,7 +67,6 @@ import HomeCtrl from './controllers/homeController'; import NavigationCtrl from './controllers/navigationController'; import LoginCtrl from './controllers/loginController'; import MQTTCtrl from './controllers/MQTTChannelsController'; -import AccessLevelCtrl from './controllers/accessLevelController'; import DateTimePickerModalCtrl from './controllers/dateTimePickerModalController'; import DiagnosticCtrl from './controllers/diagnosticController'; import BackupCtrl from './controllers/backupController'; @@ -95,6 +94,7 @@ import onResizeDirective from './directives/resize'; import confirmDirective from './directives/confirm'; import fullscreenToggleDirective from './directives/fullscreenToggle'; import expCheckMetaDirective from './react-directives/exp-check/exp-check'; +import usersPageDirective from './react-directives/users/users'; // Angular routes import routingModule from './app.routes'; @@ -178,7 +178,6 @@ module .controller('HomeCtrl', HomeCtrl) .controller('LoginCtrl', LoginCtrl) .controller('MQTTCtrl', MQTTCtrl) - .controller('AccessLevelCtrl', AccessLevelCtrl) .controller('DateTimePickerModalCtrl', DateTimePickerModalCtrl) .controller('DiagnosticCtrl', DiagnosticCtrl) .controller('BackupCtrl', BackupCtrl) @@ -266,7 +265,8 @@ module .directive('onResize', ['$parse', onResizeDirective]) .directive('ngConfirm', confirmDirective) .directive('fullscreenToggle', fullscreenToggleDirective) - .directive('expCheckWidget', expCheckMetaDirective); + .directive('expCheckWidget', expCheckMetaDirective) + .directive('usersPage', usersPageDirective); module .config([ @@ -277,7 +277,6 @@ module 'app', 'console', 'help', - 'access', 'mqtt', 'system', 'ui', diff --git a/app/scripts/app.routes.js b/app/scripts/app.routes.js index fb951e39..ac04c8dc 100644 --- a/app/scripts/app.routes.js +++ b/app/scripts/app.routes.js @@ -53,7 +53,6 @@ function routing($stateProvider, $locationProvider, $urlRouterProvider) { .state('accessLevel', { url: '/access-level', template: require('../views/access-level.html'), - controller: 'AccessLevelCtrl as $ctrl', }) .state('scan', { url: '/scan', diff --git a/app/scripts/components/loginForm/loginForm.controller.js b/app/scripts/components/loginForm/loginForm.controller.js index 71bbcf51..433a2423 100644 --- a/app/scripts/components/loginForm/loginForm.controller.js +++ b/app/scripts/components/loginForm/loginForm.controller.js @@ -3,11 +3,11 @@ class LoginFormCtrl { constructor($window, $rootScope, $state, $location, rolesFactory) { 'ngInject'; - var currentURL = new URL("/mqtt", $window.location.href); + var currentURL = new URL('/mqtt', $window.location.href); currentURL.protocol = currentURL.protocol.replace('http', 'ws'); this.rootScope = $rootScope; - this.isDev = ($window.location.host === 'localhost:8080'); // FIXME: find more beautiful way to detect local dev + this.isDev = $window.location.host === 'localhost:8080'; // FIXME: find more beautiful way to detect local dev this.localStorage = $window.localStorage; this.state = $state; this.rolesFactory = rolesFactory; @@ -62,8 +62,9 @@ class LoginFormCtrl { //........................................................................... updateLoginSettings() { // Update settings in Local Storage - if (this.isDev) + if (this.isDev) { this.localStorage.setItem('url', this.url); + } this.localStorage.setItem('prefix', this.prefix); @@ -84,7 +85,6 @@ class LoginFormCtrl { isDev: this.isDev, }; - this.rolesFactory.setRole(1); this.rootScope.requestConfig(loginData); location.reload(); } diff --git a/app/scripts/controllers/accessLevelController.js b/app/scripts/controllers/accessLevelController.js deleted file mode 100644 index 11f65152..00000000 --- a/app/scripts/controllers/accessLevelController.js +++ /dev/null @@ -1,42 +0,0 @@ -/** - * Created by ozknemoy on 21.06.2017. - */ - -export default class accessLevelController { - constructor($timeout, rolesFactory) { - 'ngInject'; - - this.$timeout = $timeout; - this.rolesFactory = rolesFactory; - - this.ok = false; - this.isLevelUp = false; - this.one = rolesFactory.ROLES[0]; - this.two = rolesFactory.ROLES[1]; - this.three = rolesFactory.ROLES[2]; - this.activeRole = rolesFactory.current.role; - this.level = '' + rolesFactory.getRole(); - this.type = { id: +this.level }; - } - - select(newType) { - if (newType.id > this.activeRole) { - this.ok = false; - this.isLevelUp = true; - } else if (newType.id == this.activeRole) { - this.ok = false; - this.isLevelUp = false; - } else { - this.ok = true; - this.isLevelUp = false; - this.type = newType; - } - } - - apply() { - this.rolesFactory.setRole(this.level); - this.ok = false; - this.isLevelUp = false; - this.activeRole = this.level; - } -} diff --git a/app/scripts/controllers/navigationController.js b/app/scripts/controllers/navigationController.js index 6a7d1653..fd96d895 100644 --- a/app/scripts/controllers/navigationController.js +++ b/app/scripts/controllers/navigationController.js @@ -8,11 +8,13 @@ class NavigationCtrl { whenMqttReady, errors, uiConfig, - rolesFactory + rolesFactory, + $rootScope ) { 'ngInject'; $scope.roles = rolesFactory; + $rootScope.roles = rolesFactory; $scope.isActive = function (viewLocation) { return viewLocation === $location.path(); @@ -84,6 +86,18 @@ class NavigationCtrl { ? pageWrapperClassList.remove(overlayClass) : pageWrapperClassList.add(overlayClass); }; + + $scope.showAccessControl = function () { + return rolesFactory.current.roles.isAdmin || rolesFactory.notConfiguredUsers.length; + }; + + $scope.logout = function () { + fetch('/logout', { + method: 'POST', + }).then(() => { + window.location.href = '/login'; + }); + }; } //----------------------------------------------------------------------------- diff --git a/app/scripts/i18n/access/en.json b/app/scripts/i18n/access/en.json deleted file mode 100644 index ec3b084c..00000000 --- a/app/scripts/i18n/access/en.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "access": { - "title": "Access level", - "labels": { - "active": "active", - "confirm": "I take full responsibility for my actions" - }, - "buttons": { - "apply": "Apply" - }, - "user": { - "name": "User", - "short": "U", - "description": "Can view dashboards and history" - }, - "operator": { - "name": "Operator", - "short": "O", - "description": "Can create and edit dashboards" - }, - "admin": { - "name": "Administrator", - "short": "A", - "description": "Has full access to device settings and rules" - } - } -} diff --git a/app/scripts/i18n/access/ru.json b/app/scripts/i18n/access/ru.json deleted file mode 100644 index 6f65fd61..00000000 --- a/app/scripts/i18n/access/ru.json +++ /dev/null @@ -1,27 +0,0 @@ -{ - "access": { - "title": "Права доступа", - "labels": { - "active": "активно", - "confirm": "Я принимаю всю ответственность за свои действия" - }, - "buttons": { - "apply": "Применить" - }, - "user": { - "name": "Пользователь", - "short": "П", - "description": "Может просматривать панели и историю" - }, - "operator": { - "name": "Оператор", - "short": "O", - "description": "Может создавать и редактировать панели" - }, - "admin": { - "name": "Администратор", - "short": "A", - "description": "Имеет полный доступ к настройкам и правилам" - } - } -} diff --git a/app/scripts/i18n/app/en.json b/app/scripts/i18n/app/en.json index e92d61f3..ca008196 100644 --- a/app/scripts/i18n/app/en.json +++ b/app/scripts/i18n/app/en.json @@ -4,11 +4,23 @@ "load": "Cannot load WebUI config.", "save": "Config saving failed", "overflow": "Config saving failed. Try to clear page's localStorage and restart the browser. If problem remains, try to reduce overall size of SVG images.", - "stop-scan": "The controller started searching for Modbus devices. This could lead to slow polling of already configured devices. The search process is forcibly stopped" + "stop-scan": "The controller started searching for Modbus devices. This could lead to slow polling of already configured devices. The search process is forcibly stopped", + "not-configured-admin": "The administrator password is not set. Please set it in the settings", + "not-configured-user": "The user password is not set. Please set it in the settings", + "not-configured-operator": "The operator password is not set. Please set it in the settings", + "not-configured-operator-user": "The user and operator passwords are not set. Please set them in the settings" }, "prompt": { "dirty": "The page has unsaved changes. Are you sure you want to leave?", "serial-config-leave": "Scanning will be canceled. Do you really want to leave the page?" + }, + "buttons": { + "logout": "Logout" + }, + "roles": { + "user": "User", + "operator": "Operator", + "admin": "Administrator" } }, "home": { diff --git a/app/scripts/i18n/app/ru.json b/app/scripts/i18n/app/ru.json index 83cfb9fc..3fd5a9f2 100644 --- a/app/scripts/i18n/app/ru.json +++ b/app/scripts/i18n/app/ru.json @@ -4,11 +4,23 @@ "load": "Не удалось загрузить настройки WebUI.", "save": "Не удалось сохранить настройки", "overflow": "Не удалось сохранить настройки. Попробуйте очистить localStorage страницы и перезапустить браузер. Если не помогло — попробуйте уменьшить суммарный размер SVG-изображений.", - "stop-scan": "В контроллере был запущен процесс поиска Modbus устройств. Это могло приводить к медленному опросу уже настроенных устройств. Процесс поиска принудительно остановлен" + "stop-scan": "В контроллере был запущен процесс поиска Modbus устройств. Это могло приводить к медленному опросу уже настроенных устройств. Процесс поиска принудительно остановлен", + "not-configured-admin": "Пароль администратора не установлен. Пожалуйста, установите его в настройках", + "not-configured-user": "Пароль пользователя не установлен. Пожалуйста, установите его в настройках", + "not-configured-operator": "Пароль оператора не установлен. Пожалуйста, установите его в настройках", + "not-configured-operator-user": "Пароли пользователя и оператора не установлены. Пожалуйста, установите их в настройках" }, "prompt": { "dirty": "На странице остались несохранённые изменения. Вы действительно хотите покинуть страницу?", "serial-config-leave": "Процесс поиска устройств будет остановлен. Вы действительно хотите перейти на другую страницу?" + }, + "buttons": { + "logout": "Выйти" + }, + "roles": { + "user": "Пользователь", + "operator": "Оператор", + "admin": "Администратор" } }, "home": { diff --git a/app/scripts/i18n/configurations/en.json b/app/scripts/i18n/configurations/en.json index 794525f7..960cba66 100644 --- a/app/scripts/i18n/configurations/en.json +++ b/app/scripts/i18n/configurations/en.json @@ -6,8 +6,7 @@ "file": "File", "title": "Title", "description": "Description", - "notice": "You cannot view this page. You can change", - "access": "access level" + "access-notice": "You don't have enough permissions to view this page" }, "buttons": { "save": "Save" diff --git a/app/scripts/i18n/configurations/ru.json b/app/scripts/i18n/configurations/ru.json index 6e9d22fb..cf2e4584 100644 --- a/app/scripts/i18n/configurations/ru.json +++ b/app/scripts/i18n/configurations/ru.json @@ -6,8 +6,7 @@ "file": "Файл", "title": "Название", "description": "Описание", - "notice": "Для просмотра этой страницы необходимо получить соответствующие ", - "access": "права доступа" + "access-notice": "У вас недостаточно прав для просмотра этой страницы" }, "buttons": { "save": "Записать" diff --git a/app/scripts/i18n/devices/en.json b/app/scripts/i18n/devices/en.json index fe4b560f..87776bb2 100644 --- a/app/scripts/i18n/devices/en.json +++ b/app/scripts/i18n/devices/en.json @@ -3,8 +3,7 @@ "labels": { "nothing": "No devices available for this moment.", "delete": "Delete device", - "notice": "You cannot view this page. You can change", - "access": "access level" + "access-notice": "You don't have enough permissions to view this page" }, "prompt": { "delete": "Remove {{name}}?" diff --git a/app/scripts/i18n/devices/ru.json b/app/scripts/i18n/devices/ru.json index 72484250..c5996d8e 100644 --- a/app/scripts/i18n/devices/ru.json +++ b/app/scripts/i18n/devices/ru.json @@ -3,8 +3,7 @@ "labels": { "nothing": "Нет устройств, доступных для отображения.", "delete": "Удалить устройство", - "notice": "Для просмотра этой страницы необходимо получить соответствующие ", - "access": "права доступа" + "access-notice": "У вас недостаточно прав для просмотра этой страницы" }, "prompt": { "delete": "Удалить {{name}}?" diff --git a/app/scripts/i18n/react/locales/en/translations.json b/app/scripts/i18n/react/locales/en/translations.json index cf66dbf3..5dc1058f 100644 --- a/app/scripts/i18n/react/locales/en/translations.json +++ b/app/scripts/i18n/react/locales/en/translations.json @@ -233,8 +233,7 @@ } }, "errors": { - "access-failed": "You cannot view this page. You can change ", - "access-failed-link-text": "access level" + "access-failed": "You cannot view this page. You can change access level" }, "forms": { "default-text-prefix": "If the value is not set, ", @@ -326,5 +325,23 @@ "search-device": "Search device", "search-control": "Search channel" } + }, + "users": { + "title": "Users", + "errors": { + "forbidden": "You don't have permission to view this page", + "old-backend": "The backend is outdated. Please update it", + "unknown": "Error: {{msg}}" + }, + "labels":{ + "login": "Login", + "password": "Password", + "edit-user": "Edit user", + "edit-operator": "Edit operator", + "edit-admin": "Edit admin" + }, + "buttons": { + "save": "Save" + } } } diff --git a/app/scripts/i18n/react/locales/ru/translations.json b/app/scripts/i18n/react/locales/ru/translations.json index 80b21ba4..9a1dd7b0 100644 --- a/app/scripts/i18n/react/locales/ru/translations.json +++ b/app/scripts/i18n/react/locales/ru/translations.json @@ -231,8 +231,7 @@ } }, "errors": { - "access-failed": "Для просмотра этой страницы необходимо получить соответствующие ", - "access-failed-link-text": "права доступа" + "access-failed": "Для просмотра этой страницы необходимо получить соответствующие права доступа" }, "forms": { "default-text-prefix": "Если значение не указано, используется ", @@ -324,5 +323,23 @@ "search-device": "Найти устройство", "search-control": "Найти канал" } + }, + "users": { + "title": "Пользователи", + "errors": { + "forbidden": "У вас нет прав на изменение настроек", + "old-backend": "ПО на контроллере устарело. Пожалуйста, обновите его", + "unknown": "Ошибка: {{msg}}" + }, + "labels":{ + "login": "Имя пользователя", + "password": "Пароль", + "edit-user": "Редактировать пользователя", + "edit-operator": "Редактировать оператора", + "edit-admin": "Редактировать администратора" + }, + "buttons": { + "save": "Сохранить" + } } } diff --git a/app/scripts/i18n/rules/en.json b/app/scripts/i18n/rules/en.json index 0d9ae7b9..66382c6b 100644 --- a/app/scripts/i18n/rules/en.json +++ b/app/scripts/i18n/rules/en.json @@ -3,8 +3,7 @@ "title": "Rules", "labels": { "loading": "Loading...", - "notice": "You cannot view this page. You can change", - "access": "access level" + "access-notice": "You don't have enough permissions to view this page" }, "buttons": { "new": "New...", diff --git a/app/scripts/i18n/rules/ru.json b/app/scripts/i18n/rules/ru.json index 2534b35f..2b271094 100644 --- a/app/scripts/i18n/rules/ru.json +++ b/app/scripts/i18n/rules/ru.json @@ -3,8 +3,7 @@ "title": "Правила", "labels": { "loading": "Загрузка...", - "notice": "Для просмотра этой страницы необходимо получить соответствующие ", - "access": "права доступа" + "access-notice": "У вас недостаточно прав для просмотра этой страницы" }, "buttons": { "new": "Создать...", diff --git a/app/scripts/react-directives/components/access-level/accessLevel.jsx b/app/scripts/react-directives/components/access-level/accessLevel.jsx index 3b1e58a6..10b62b14 100644 --- a/app/scripts/react-directives/components/access-level/accessLevel.jsx +++ b/app/scripts/react-directives/components/access-level/accessLevel.jsx @@ -12,7 +12,6 @@ const AccessLevelErrorBanner = observer(({ store, children }) => { return (
    {t('errors.access-failed')} - {t('errors.access-failed-link-text')}
    ); }); diff --git a/app/scripts/react-directives/forms/formStore.js b/app/scripts/react-directives/forms/formStore.js index 29934975..7fa19f8f 100644 --- a/app/scripts/react-directives/forms/formStore.js +++ b/app/scripts/react-directives/forms/formStore.js @@ -3,10 +3,10 @@ import { makeAutoObservable } from 'mobx'; export class FormStore { - constructor(name) { + constructor(name, params) { this.type = 'object'; this.name = name; - this.params = {}; + this.params = params || {}; makeAutoObservable(this); } diff --git a/app/scripts/react-directives/users/pageStore.js b/app/scripts/react-directives/users/pageStore.js new file mode 100644 index 00000000..d851f389 --- /dev/null +++ b/app/scripts/react-directives/users/pageStore.js @@ -0,0 +1,113 @@ +'use strict'; + +import { makeAutoObservable } from 'mobx'; +import PageWrapperStore from '../components/page-wrapper/pageWrapperStore'; +import { StringStore } from '../forms/stringStore'; +import i18n from '../../i18n/react/config'; +import { FormStore } from '../forms/formStore'; +import { makeNotEmptyValidator } from '../forms/stringValidators'; +import FormModalState from '../components/modals/formModalState'; + +class UsersPageAccessLevelStore { + constructor(rolesFactory) { + this.isAdmin = rolesFactory.current.roles.isAdmin; + this.accessGranted = this.isAdmin || rolesFactory.notConfiguredUsers.length; + this.allowToEditUser = this.isAdmin; + this.allowToEditOperator = this.isAdmin; + this.allowToEditAdmin = this.accessGranted; + } +} + +class UsersPageStore { + constructor(rolesFactory) { + this.rolesFactory = rolesFactory; + this.pageWrapperStore = new PageWrapperStore(i18n.t('users.title')); + this.accessLevelStore = new UsersPageAccessLevelStore(rolesFactory); + this.userParamsStore = new FormStore('userParams', { + login: new StringStore({ + name: i18n.t('users.labels.login'), + validator: makeNotEmptyValidator(), + }), + password: new StringStore({ + name: i18n.t('users.labels.password'), + validator: makeNotEmptyValidator(), + }), + }); + this.formModalState = new FormModalState(); + this.pageWrapperStore.setLoading(false); + + makeAutoObservable(this); + } + + async showUserEditModal() { + this.userParamsStore.reset(); + return await this.formModalState.show( + i18n.t('users.labels.edit-user'), + this.userParamsStore, + i18n.t('users.buttons.save') + ); + } + + async putSettings(userType) { + const user = await this.showUserEditModal(); + if (user) { + user['type'] = userType; + this.pageWrapperStore.clearError(); + this.pageWrapperStore.setLoading(true); + const res = await fetch('/auth/user', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(user), + }); + this.rolesFactory.setConfiguredUser(userType); + this.pageWrapperStore.setLoading(false); + if (!res.ok) { + switch (res.status) { + case 403: + this.pageWrapperStore.setError(i18n.t('users.errors.forbidden')); + break; + case 404: + this.pageWrapperStore.setError(i18n.t('users.errors.old-backend')); + break; + default: + res + .text() + .then(text => + this.pageWrapperStore.setError(i18n.t('users.errors.unknown', { msg: text })) + ); + } + } + } + } + + async editUser() { + try { + await this.putSettings('user'); + } catch (error) { + this.pageWrapperStore.setError(error); + } + } + + async editOperator() { + try { + await this.putSettings('operator'); + } catch (error) { + this.pageWrapperStore.setError(error); + } + } + + async editAdmin() { + try { + await this.putSettings('admin'); + if (!this.accessLevelStore.isAdmin) { + window.location.href = '/login'; + } + } catch (error) { + this.pageWrapperStore.setError(error); + } + } +} + +export default UsersPageStore; diff --git a/app/scripts/react-directives/users/users.js b/app/scripts/react-directives/users/users.js new file mode 100644 index 00000000..9909d803 --- /dev/null +++ b/app/scripts/react-directives/users/users.js @@ -0,0 +1,27 @@ +'use strict'; + +import ReactDOM from 'react-dom/client'; +import CreateUsersPage from './usersPage'; +import UsersStore from './pageStore'; +import { setReactLocale } from '../locale'; + +function usersDirective(rolesFactory) { + 'ngInject'; + + setReactLocale(); + return { + restrict: 'E', + scope: {}, + link: function (scope, element) { + scope.store = new UsersStore(rolesFactory); + scope.root = ReactDOM.createRoot(element[0]); + scope.root.render(CreateUsersPage({ store: scope.store })); + + element.on('$destroy', function () { + scope.root.unmount(); + }); + }, + }; +} + +export default usersDirective; diff --git a/app/scripts/react-directives/users/usersPage.jsx b/app/scripts/react-directives/users/usersPage.jsx new file mode 100644 index 00000000..db4ec75e --- /dev/null +++ b/app/scripts/react-directives/users/usersPage.jsx @@ -0,0 +1,53 @@ +import React from 'react'; +import { observer } from 'mobx-react-lite'; +import { PageWrapper, PageTitle, PageBody } from '../components/page-wrapper/pageWrapper'; +import { useTranslation } from 'react-i18next'; +import FormModal from '../components/modals/formModal'; +import { Button } from '../common'; + +const UsersPage = observer(({ store }) => { + const { t } = useTranslation(); + return ( + + + + +
    + {store.accessLevelStore.allowToEditUser && ( +
    +
    +
    + ); +}); + +function CreateUsersPage({ store }) { + return ; +} + +export default CreateUsersPage; diff --git a/app/scripts/services/roles.factory.js b/app/scripts/services/roles.factory.js index 1e3dbdab..8147d391 100644 --- a/app/scripts/services/roles.factory.js +++ b/app/scripts/services/roles.factory.js @@ -6,25 +6,21 @@ export default function rolesFactory() { 'ngInject'; var roles = {}; + const DEFAULT_ROLE = 1; + roles._ROLE_ONE = { id: 1, - name: 'access.user.name', - shortName: 'access.user.short', - description: 'access.user.description', + name: 'app.roles.user', isAdmin: false, }; roles._ROLE_TWO = { id: 2, - name: 'access.operator.name', - shortName: 'access.operator.short', - description: 'access.operator.description', + name: 'app.roles.operator', isAdmin: false, }; roles._ROLE_THREE = { id: 3, - name: 'access.admin.name', - shortName: 'access.admin.short', - description: 'access.admin.description', + name: 'app.roles.admin', isAdmin: true, }; @@ -33,36 +29,48 @@ export default function rolesFactory() { roles.ROLE_THREE = roles._ROLE_THREE.id; roles.ROLES = [roles._ROLE_ONE, roles._ROLE_TWO, roles._ROLE_THREE]; - const setDefaultRole = (defaultRole = 1) => { - localStorage.setItem('role', defaultRole); - return defaultRole; + const typeToRoleId = { + admin: roles.ROLE_THREE, + operator: roles.ROLE_TWO, + user: roles.ROLE_ONE, }; + const roleId = typeToRoleId[localStorage.getItem('user_type')] || DEFAULT_ROLE; + roles.current = { role: roleId, roles: roles.ROLES[roleId - 1] }; + roles.notConfiguredUsers = []; + roles.notConfiguredUsersMessage = ''; - roles.current = { - role: localStorage.getItem('role') || setDefaultRole(), - roles: roles.ROLES[(localStorage.getItem('role') || 1) - 1], + const updateMessage = () => { + if (!roles.notConfiguredUsers.length) { + roles.notConfiguredUsersMessage = ''; + } else { + roles.notConfiguredUsersMessage = + 'app.errors.not-configured-' + roles.notConfiguredUsers.join('-'); + } }; - roles.getRole = () => { - roles.current.role = localStorage.getItem('role'); - roles.current.roles = roles.ROLES[roles.current.role - 1]; - return roles.current.role; + // проверяет есть ли права доступа/просмотра + // принимает значение минимально возможного статуса для доступа/просмотра + roles.checkRights = onlyRoleGreatThanOrEqual => { + return roles.current.role >= onlyRoleGreatThanOrEqual; }; - roles.setRole = n => { - roles.current = { role: n, roles: roles.ROLES[n - 1] }; - localStorage.setItem('role', n); + roles.setNotConfiguredUsers = notConfiguredUsers => { + if (!notConfiguredUsers) { + roles.notConfiguredUsers = []; + } else { + roles.notConfiguredUsers = notConfiguredUsers.split(',').sort(); + } + updateMessage(); }; - roles.resetRole = n => { - localStorage.setItem('role', n); + roles.setConfiguredUser = configuredUser => { + roles.notConfiguredUsers = roles.notConfiguredUsers.filter(user => user != configuredUser); + updateMessage(); }; - // проверяет есть ли права доступа/просмотра - // принимает значение минимально возможного статуса для доступа/просмотра - roles.checkRights = onlyRoleGreatThanOrEqual => { - return roles.getRole() >= onlyRoleGreatThanOrEqual; - }; + fetch('/not_configured_users') + .then(response => response.text()) + .then(data => roles.setNotConfiguredUsers(data)); return roles; } diff --git a/app/styles/css/new.css b/app/styles/css/new.css index 05ad6625..c69f0f9e 100644 --- a/app/styles/css/new.css +++ b/app/styles/css/new.css @@ -369,3 +369,19 @@ a[ng-click]:hover { word-wrap: break-word; white-space: break-spaces; } + +.user-menu span { + font-size: var(--bar-font-size); + padding: 3px 20px; + display: block; + color: black; +} + +.user-menu .divider { + margin: 4px 0; +} + +.user-menu .glyphicon-user { + font-size: 20px; + color: lightgray; +} diff --git a/app/styles/main.css b/app/styles/main.css index ebe9e85a..012e0b9f 100644 --- a/app/styles/main.css +++ b/app/styles/main.css @@ -170,24 +170,25 @@ ul.alert-dropdown { /* connection status */ .connection-status{ - float: right; - padding-right: 20px; - display: none; + float: right; + padding-right: 22px; + display: none; + margin-right: 0px; + margin-top: 13px; + margin-bottom: 0px; + display: flex; + align-items: center; + gap: 10px; } -@media(min-width:360px) { - .connection-status{ - display: inherit; - } + +.connection-status .glyphicon-user{ + font-size: 20px; } @media(min-width:768px) { .navbar-header { float: none; } - - .connection-status{ - padding-right: 40px; - } } @@ -933,19 +934,6 @@ body > double-bounce-spinner .double-bounce-spinner .double-bounce2 { background: #5cb300; } -@media (max-width: 500px) { - .big-screen-access-level { - display: none; - } -} - -@media (min-width: 501px) { - .mobile-screen-access-level { - display: none; - } -} - - /* Для совместимости с wb-mqtt-serial >= 2.9.0 */ h3.je-header { margin-top: 0px; diff --git a/app/views/access-level.html b/app/views/access-level.html index c1c6d306..3b9aeb2b 100644 --- a/app/views/access-level.html +++ b/app/views/access-level.html @@ -1,48 +1 @@ -

    {{'access.title'}}

    - -
    -
    - -
    -
    -
    -
    - -
    -
    -
    -
    - -
    -
    -

    -
    -
    - -
    -
    - - + diff --git a/app/views/config.html b/app/views/config.html index c5ff3cf7..91b3beb0 100644 --- a/app/views/config.html +++ b/app/views/config.html @@ -1,6 +1,5 @@
    diff --git a/app/views/configs.html b/app/views/configs.html index 2e6f2b10..e15f3a04 100644 --- a/app/views/configs.html +++ b/app/views/configs.html @@ -1,8 +1,7 @@

    {{'configurations.title'}}

    diff --git a/app/views/devices.html b/app/views/devices.html index de075168..3bc5bbfc 100644 --- a/app/views/devices.html +++ b/app/views/devices.html @@ -1,6 +1,5 @@
    {{'devices.labels.nothing'}}
    diff --git a/app/views/logs.html b/app/views/logs.html index b03a385a..186b08b1 100644 --- a/app/views/logs.html +++ b/app/views/logs.html @@ -5,8 +5,7 @@

    diff --git a/app/views/network-connections.html b/app/views/network-connections.html index 411612ce..353e7e01 100644 --- a/app/views/network-connections.html +++ b/app/views/network-connections.html @@ -1,6 +1,5 @@
    diff --git a/app/views/scripts.html b/app/views/scripts.html index 7149b580..ff526323 100644 --- a/app/views/scripts.html +++ b/app/views/scripts.html @@ -3,8 +3,7 @@

    {{'rules.title'}}

    diff --git a/app/views/system.html b/app/views/system.html index cd6865ef..b9f099a0 100644 --- a/app/views/system.html +++ b/app/views/system.html @@ -1,8 +1,7 @@

    {{'system.title'}}

    diff --git a/debian/changelog b/debian/changelog index 1b7b3455..ffb8521b 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,9 @@ +wb-mqtt-homeui (2.108.0) stable; urgency=medium + + * Add authentication + + -- Petr Krasnoshchekov Wed, 18 Dec 2024 15:49:56 +0500 + wb-mqtt-homeui (2.107.4) stable; urgency=medium * Fix enum translations in text dashboards diff --git a/debian/control b/debian/control index 9d539b82..08a0a57f 100644 --- a/debian/control +++ b/debian/control @@ -1,5 +1,5 @@ Source: wb-mqtt-homeui -Maintainer: Evgeny Boger +Maintainer: Wiren Board team Section: misc Priority: optional Standards-Version: 4.5.1 @@ -10,7 +10,7 @@ Package: wb-mqtt-homeui Architecture: all Conflicts: wb-homa-webinterface Depends: ${shlibs:Depends}, ${misc:Depends}, mosquitto, mqtt-wss, mqtt-tools, nginx-extras, diffutils, wb-utils (>= 4.20.1), - wb-configs (>= 3.26.0) + wb-configs (>= 3.35.0~~) Recommends: wb-mqtt-logs, wb-device-manager Suggests: wb-mqtt-confed (>= 1.4.0), Breaks: wb-mqtt-confed (<< 1.0.3), wb-mqtt-db (<< 1.5), wb-mqtt-serial (<< 2.116.0~~), wb-device-manager (<< 1.4.0~~), diff --git a/login/login.html b/login/login.html new file mode 100644 index 00000000..6441b4e0 --- /dev/null +++ b/login/login.html @@ -0,0 +1,169 @@ + + + + + + + Login Page + + + +
    + +
    +
    + + +
    +
    + + +
    +
    + +
    +
    +
    +
    + + + \ No newline at end of file diff --git a/webpack.config.js b/webpack.config.js index 490e8145..f2d70533 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -87,13 +87,19 @@ module.exports = (function makeWebpackConfig() { type: 'asset/resource', }, { - // without hash - test: /\.(svg|woff|woff2|ttf|eot)$/, + test: /\.(svg)$/, type: 'asset/resource', generator: { filename: '[name][ext]', }, }, + { + test: /\.(woff|woff2|ttf|eot)$/, + type: 'asset/resource', + generator: { + filename: 'fonts/[name][ext]', + }, + }, { test: /\.html$/, type: 'asset/source', @@ -305,6 +311,12 @@ module.exports = (function makeWebpackConfig() { }, port: 8080, hot: true, + proxy: [ + { + context: ['/not_configured_users', '/auth/user', '/login'], + target: 'http://10.200.200.1', + }, + ], }; return config; From 2f4e5a32c25860b2045e679cfc7107fde27d90d6 Mon Sep 17 00:00:00 2001 From: Petr Krasnoshchekov Date: Wed, 25 Dec 2024 14:00:39 +0500 Subject: [PATCH 02/11] Fix --- app/index.ejs | 2 +- app/styles/main.css | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/app/index.ejs b/app/index.ejs index aec416d3..80137afc 100644 --- a/app/index.ejs +++ b/app/index.ejs @@ -46,7 +46,7 @@ {{'navigation.connection.active'}} {{'navigation.connection.inactive'}}
    - +
    + + + + + + + + + + + + {store.users.map(user => ( + + + + + + ))} + +
    {t('users.labels.login')}{t('users.labels.type')}
    {user.login}{t('users.labels.' + user.type)} +
    +
    +
    ); diff --git a/app/scripts/services/roles.factory.js b/app/scripts/services/roles.factory.js index 5d76fde2..641a4b70 100644 --- a/app/scripts/services/roles.factory.js +++ b/app/scripts/services/roles.factory.js @@ -36,17 +36,11 @@ export default function rolesFactory() { }; const roleId = typeToRoleId[localStorage.getItem('user_type')] || DEFAULT_ROLE; roles.current = { role: roleId, roles: roles.ROLES[roleId - 1] }; - roles.notConfiguredUsers = []; - roles.notConfiguredUsersMessage = ''; - - const updateMessage = () => { - if (!roles.notConfiguredUsers.length) { - roles.notConfiguredUsersMessage = ''; - } else { - roles.notConfiguredUsersMessage = - 'app.errors.not-configured-' + roles.notConfiguredUsers.join('-'); - } - }; + roles.notConfiguredAdminResolve = null; + roles.notConfiguredAdmin = false; + roles.notConfiguredAdminPromise = new Promise(resolve => { + roles.notConfiguredAdminResolve = resolve; + }); // проверяет есть ли права доступа/просмотра // принимает значение минимально возможного статуса для доступа/просмотра @@ -54,23 +48,12 @@ export default function rolesFactory() { return roles.current.role >= onlyRoleGreatThanOrEqual; }; - roles.setNotConfiguredUsers = notConfiguredUsers => { - if (!notConfiguredUsers) { - roles.notConfiguredUsers = []; - } else { - roles.notConfiguredUsers = notConfiguredUsers.split(',').sort(); + fetch('/auth/check_config').then(response => { + if (response.ok) { + roles.notConfiguredAdmin = true; } - updateMessage(); - }; - - roles.setConfiguredUser = configuredUser => { - roles.notConfiguredUsers = roles.notConfiguredUsers.filter(user => user !== configuredUser); - updateMessage(); - }; - - fetch('/not_configured_users') - .then(response => response.text()) - .then(data => roles.setNotConfiguredUsers(data)); + roles.notConfiguredAdminResolve(); + }); return roles; } diff --git a/webpack.config.js b/webpack.config.js index f2d70533..97360b05 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -313,7 +313,7 @@ module.exports = (function makeWebpackConfig() { hot: true, proxy: [ { - context: ['/not_configured_users', '/auth/user', '/login'], + context: ['/auth/check_config', '/auth/users', '/login'], target: 'http://10.200.200.1', }, ], From 0fe56c1975942369ef6fd45f328038cc1aa1a304 Mon Sep 17 00:00:00 2001 From: Petr Krasnoshchekov Date: Fri, 10 Jan 2025 12:30:19 +0500 Subject: [PATCH 08/11] Add russian translation to login --- login/login.html | 24 ++++++++++++++++++++---- 1 file changed, 20 insertions(+), 4 deletions(-) diff --git a/login/login.html b/login/login.html index 00176d7d..b085e1ea 100644 --- a/login/login.html +++ b/login/login.html @@ -111,17 +111,17 @@
    - +
    - +
    @@ -141,6 +141,10 @@ item.classList.remove('disabled'); } + function getLanguage() { + return navigator.language.split('-')[0]; + } + document.querySelector('form').addEventListener('submit', function (e) { e.preventDefault(); const formItems = ['#login', '#password', '#button']; @@ -159,11 +163,23 @@ if (response.ok) { window.location.href = '/'; } else { - document.querySelector('#error-message').textContent = 'Login failed'; + if (getLanguage() === 'ru') { + document.querySelector('#error-message').textContent = 'Ошибка входа'; + } else { + document.querySelector('#error-message').textContent = 'Login failed'; + } document.querySelector('#spinner').style.display = 'none'; formItems.forEach(enable); } }); }); + + window.addEventListener('DOMContentLoaded', () => { + if (getLanguage() === 'ru') { + document.querySelector('#button_label').textContent = 'Войти'; + document.querySelector('#login_label').textContent = 'Имя пользователя'; + document.querySelector('#password_label').textContent = 'Пароль' + } + }); \ No newline at end of file From 387ad79ec824b7d6c2d39c62149313b624fd3dd5 Mon Sep 17 00:00:00 2001 From: KraPete <86825564+KraPete@users.noreply.github.com> Date: Fri, 10 Jan 2025 12:42:31 +0500 Subject: [PATCH 09/11] Update login/login.html Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- login/login.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/login/login.html b/login/login.html index b085e1ea..765cba60 100644 --- a/login/login.html +++ b/login/login.html @@ -178,7 +178,7 @@ if (getLanguage() === 'ru') { document.querySelector('#button_label').textContent = 'Войти'; document.querySelector('#login_label').textContent = 'Имя пользователя'; - document.querySelector('#password_label').textContent = 'Пароль' + document.querySelector('#password_label').textContent = 'Пароль'; } }); From 31bc79bef5b7a8570a6dedf05ec640ddde595bc4 Mon Sep 17 00:00:00 2001 From: KraPete <86825564+KraPete@users.noreply.github.com> Date: Fri, 10 Jan 2025 12:48:19 +0500 Subject: [PATCH 10/11] Update login/login.html Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- login/login.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/login/login.html b/login/login.html index 765cba60..2d3a16eb 100644 --- a/login/login.html +++ b/login/login.html @@ -130,7 +130,7 @@