diff --git a/CHANGELOG_EN.md b/CHANGELOG_EN.md new file mode 100644 index 0000000..81d6d75 --- /dev/null +++ b/CHANGELOG_EN.md @@ -0,0 +1,39 @@ +## V.0.9.0 RC +### Back +1. The organization of a transparent REST API through the fox\externalCallable interface - allows you to organize access to the necessary entities with minimal costs, but with all the necessary checks. +2. Support for third-party modules with the ability to install multiple instances of the same module (if supported by the module). +3. User authentication — both using built-in tools and using external OAuth sources (currently Gitlab, Gitea, Yandex, VK support is implemented). +4. User authorization — with the help of the built-in access control system based on roles, flexible verification of the user's access rights is performed. The lists of user roles are checked both by the back when making REST API calls, and by the front — for this, after authentication, the list of access rights of the current user is transmitted. +5. The ability to make requests without authorization, if necessary. For example, to implement Webhooks. +6. Embedded database migrations based on the description of the class structure. +7. A base class for performing standard database functions (write, read, search, delete) on the basis of which you can quickly create entities. +8. memcached support for both embedded and custom objects for quick access to frequently used data +9. Configuration storage for each module in the database (with cache) so it is in the environment of the container. +10. Built-in functionality for registering users by invitation with the ability to automatically add to groups both for granting access and for organizing into logical groups ("lists"). +11. Built-in functionality of action confirmation codes for both built-in functions (password recovery, mail confirmation, etc.) and for user modules. +12. Support for S3-compatible storage management (creating bouquets, deleting data, etc.). +13. The ability to add any other object storage based on a typical interface. +14. Storing files (email attachments, servicedesk attachments, templates and report results, etc.) in S3-compatible object storage +15. Built-in support for OpenDocument ODS and ODT formats for automated document generation (reports, invoices, acts, etc.) +16. Built-in Fox Converter support for exporting documents in any office format. +17. Built-in support at the base class level of data exchange in JSON format with the ability to control the visibility of individual properties and create virtual properties. +18. Built-in eMail client that supports simultaneous work both for receiving and sending emails, including attachments from multiple accounts using IMAP and SMTP protocols (with and without authorization). +19. Storing metadata of modules (for example, the last synchronization, etc.) in the built-in storage with caching. +20. Support for receiving and generating data in Prometheus format +21. Built-in REST API Client for communication with third-party systems. +22. Built-in stringExportable && stringImportable interfaces for automatic conversion of objects to a string and back (for example, time to unixTime or ISO). +23. Built-in encryption module for critical data (for example, passwords) as well as the formation of hashes based on an individual master password. +24. A system for generating unique identifiers in the format 1000-0000-00 with a checksum for the formation of a register of documents, inventory and other objects. For example, to organize a global search (in future releases) and barcodes. +25. Built-in cron, which allows you to run periodic processes of modules in parallel with a limit on the maximum execution time and with the possibility of blocking the restart until the last process is completed separately for each task. +26. Support for multiple languages, for example, for sending notifications to e-mail or messengers. The list of languages may vary for Chimera Core and additional modules. +27. Logging of all user actions. + +### Front +1. Built-in methods of REST interaction with both your own backup and other services. +2. Formation of a visual basic user interface in several languages +3. Basic system administration functions +4. Authorization, user registration. Session monitoring +5. Built-in Fox UI library for creating menus, dialogs, buttons, forms including forms for automatic password generation and autofill. +6. Access rights verification library to control the display of interface elements. +7. The ability to customize the interface by creating your own color themes and images. +8. Using FontAwesome 5 to form interfaces, it is also possible to add your own fonts with icons. \ No newline at end of file diff --git a/CHANGELOG_RU.md b/CHANGELOG_RU.md new file mode 100644 index 0000000..6843dc0 --- /dev/null +++ b/CHANGELOG_RU.md @@ -0,0 +1,39 @@ +## V.0.9.0 RC +### Бэк +1. Огранизация прозрачного REST API через интерфейс fox\externalCallable - позволяет с минимальными затратами но при этом со всеми необходимыми проверками организовать доступ к нужным сущностям. +2. Поддержка сторонних модулей с возможностью установки нескольких инстансов одного модуля (если поддерживается модулем). +3. Аутентификация пользователей — как с помощью встроенных средств так и с помощью внешних источников oAuth (на данный момент реализована поддержка Gitlab, Gitea, Yandex, VK). +4. Авторизация пользователей — с помощью встроенной системы контроля доступа, основанной на ролях производится гибкая проверка наличия у пользователя прав доступа. Списки ролей пользователя проверяются как бэком при выполнении вызовов REST API, так и фронтом — для этого после аутентификации передается список прав доступа текущего пользователя. +5. Возможность выполнения запросов без авторизации, если такое необходимо. Например для реализации Вебхуки. +6. Встроенные миграции БД на основе описания структуры класса. +7. Базовый класс для выполнения стандартных функции работы с БД (запись, чтение, поиск, удаление) на основе которого можно быстро создавать сущности. +8. Поддержка memcached как для встроенных объектов так и для пользовательских для быстрого доступа к часто используемым данным +9. Хранилище конфигурации для каждого модуля в БД (с кэшем) так и глобальной в окружении контейнера. +10. Встроенный функционал регистрации пользователей по приглашениям с возможность автоматического добавления в группы как для предоставления доступа так и для организации в логические группы («списки»). +11. Встроенный функционал кодов подтверждения действий как для встроенных функций (восстановление пароля, подтверждение почты итд) так и для пользовательских модулей. +12. Поддержка управления S3-совместимым хранилищем (создание букетов, удаление данных итд). +13. Возможность добавления любых других объектных хранилищ на основе типового интерфейса. +14. Хранение файлов (вложений электронной почты, вложений в servicedesk, шаблонов и результатов отчетов итд) в S3-совместимом объектном хранилище +15. Встроенная поддержка форматов OpenDocument ODS и ODT для автоматизированного формирования документов (отчеты, счета, акты итд) +16. Встроенная поддержка Fox Converter для экспорта документов в любом офисном формате. +17. Встроенная поддержка на уровне базового класса обмена данными в формате JSON с возможность контроля видимости отдельных свойств и создания виртуальных свойств. +18. Встроенный eMail клиент, поддерживающий одновременную работу как на получение так и на отправку писем в том чисте с вложениями с нескольких учетных записей по протоколам IMAP и SMTP (с авторизацией и без). +19. Хранение метаданных модулей (например, последняя синхронизация итд) во встроенном хранилище с кешированием. +20. Поддержка получения и формирования данных в формате Prometheus +21. Встроенный REST API Client для связи со сторонними системами. +22. Встроенные интерфейсы stringExportable && stringImportable для автоматической конвертации объектов в строку и обратно (например время в unixTime или ISO). +23. Встроенный модуль шифрования критиченых данных (например, паролей) а так же формирования хэшей на основе индивидуального мастер-пароля. +24. Система формирования уникальных идентификаторов в формате 1000-0000-00 с контрольной суммой для формирования реестра документов, инвентаризации и других объектов. Например для организации глобального поиска (в будущих релизах) и штрих-кодов. +25. Встроенный cron, позволяющий запускать периодические процессы модулей параллельно с ограничением по максимальному времени выполнения и с возможностью блокировки повторного запуска, пока не завершится прошлый процесс отдельно для каждой задачи. +26. Поддержка нескольких языков, например для отправки уведомлений на электронную почту или мессенджеры. Список языков может различаться для Chimera Core и дополнительных модулей. +27. Логирование всех действий пользователей. + +### Фронт +1. Встроенные методы взаимодействия по REST как с собственным бэком, так и с другими службами. +2. Формирование визуального базового интерфейса пользователя на нескольких языках +3. Базовые функции администрирования системы +4. Авторизациия, регистрации пользователей. Контроль сессий +5. Встроенная библиотека Fox UI для формирования меню, диалогов, кнопок, форм включая формы автоматической генерации паролей и автозаполнения. +6. Библиотека проверки прав доступа для контроля за отображением элементов интерфейса. +7. Возможность кастомизации интерфейса в помощью создания собственных цветовых тем и изображений. +8. Использование FontAwesome 5 для формирования интерфейсов, так же возможно добавление собственных шрифтов с иконками. \ No newline at end of file diff --git a/README.md b/README.md index edf412f..ce43c05 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,34 @@ # Chimera Fox Platform Mark2 +Chimera Fox is a universal framework for quickly creating web applications. The back is written in PHP, and provides basic functions. Front on JS. Interaction via REST. -Полностью новая версия платформы Chimera Fox. +# How to run in docker +``` +version: "2" -Предыдущие версии будут поддерживаться только в качестве обновлений безопасности. +networks: + interlink: -Обратная совместимость с предыдущими версиями отсутствует - это основная причина выхода новой платформы - совместимость со старыми версиями занимает слишком много ресурсов на поддержку, а фактически она уже не нужна. +services: -*Актуальные модули старой версии будут портированы на Mark2* \ No newline at end of file +fox-web-mk2: + restart: always + image: mxfox/chimera-mk2-basic:lastest + container_name: fox-web-mk2 + volumes: + - ./fox-log-web/logs:/var/log/apache2 + - ./fox-log-cron/logs:/var/log/fox + + networks: + - interlink + + environment: + - "FOX_SQLSERVER=XXXXXX" + - "FOX_SQLUSER=XXXXX" + - "FOX_SQLPASSWD=XXXX" + - "FOX_SQLDB=XXXXX" + - "FOX_CACHEHOST=memcached" + - "FOX_TITLE=Mark2" + - "FOX_SITEPREFIX=https://mark2.fox.local" + - "FOX_MASTERSECRET=SuperSecretPassword" + - "FOX_INIT_PASSWORD=AnotherSuperSecretPassword" + ``` diff --git a/core/fox/auth/register.php b/core/fox/auth/register.php index 268cd65..e0297d6 100644 --- a/core/fox/auth/register.php +++ b/core/fox/auth/register.php @@ -11,6 +11,7 @@ use fox\oAuthProfile; use fox\config; use fox\userGroup; use fox\authToken; +use fox\logEntry; class register implements externalCallable { @@ -33,9 +34,6 @@ class register implements externalCallable { $ic=null; if (!empty($regCode)) { $ic=userInvitation::getByCode($regCode); - if (!$ic || ($ic->expireStamp->stamp>time())) { - $ic=null; - } } else { $ic =userInvitation::getByEMail($eMail); } @@ -88,11 +86,9 @@ class register implements externalCallable { } if ($ic) { - if ($ic && ($ic->expireStamp->stamp<=time())) { - foreach ($ic->joinGroupsId as $grid) { - $group = new userGroup($grid); - $group->join($u); - } + foreach ($ic->joinGroupsId as $grid) { + $group = new userGroup($grid); + $group->join($u); } if (!$ic->allowMultiUse) { $ic->delete(); } } @@ -102,7 +98,7 @@ class register implements externalCallable { } catch (\Exception $e) { trigger_error($e->getMessage()); } - + logEntry::add($request->instance, static::class, __FUNCTION__, null, "User ".$u->login." registered ", "INFO", $u, "user", $u->id); $t = authToken::issue($u, "WEB"); return [ "token" => $t->token, @@ -148,6 +144,8 @@ class register implements externalCallable { $t = authToken::issue($u, "WEB"); + + logEntry::add($request->instance, static::class, __FUNCTION__, null, "Password recovered for user ".$u->login, "INFO", $u, "user", $u->id); return [ "token" => $t->token, "expire" => $t->expireStamp->isNull() ? "Never" : $t->expireStamp diff --git a/core/fox/baseClass.php b/core/fox/baseClass.php index bac1134..8b526c9 100644 --- a/core/fox/baseClass.php +++ b/core/fox/baseClass.php @@ -268,7 +268,12 @@ class baseClass extends dbStoredBase implements \JsonSerializable, jsonImportabl $stringRef = (is_bool($ref->{$key}) || ! (is_object($ref->{$key}) || is_array($ref->{$key}))); $stringVal = (is_bool($val) || ! (is_object($val) || is_array($val))); - $this->changelog .= "key: " . $key . " changed from " . ($stringRef ? (is_bool($ref->{$key}) ? ($ref->{$key} ? "true" : "false") : $ref->{$key}) : "<" . gettype($ref->{$key}) . ">") . " to " . ($stringVal ? (is_bool($val) ? ($val ? "true" : "false") : $val) : "<" . gettype($val) . ">") . ";\n "; + if (preg_match("/^_/",$key)) { + $this->changelog .= "key: " . $key . " changed \n"; + } else { + $this->changelog .= "key: " . $key . " changed from " . ($stringRef ? (is_bool($ref->{$key}) ? ($ref->{$key} ? "true" : "false") : $ref->{$key}) : "<" . gettype($ref->{$key}) . ">") . " to " . ($stringVal ? (is_bool($val) ? ($val ? "true" : "false") : $val) : "<" . gettype($val) . ">") . ";\n "; + } + } } @@ -284,17 +289,18 @@ class baseClass extends dbStoredBase implements \JsonSerializable, jsonImportabl if (property_exists($this, static::$sqlIdx) && ($this->{static::$sqlIdx} == null)) { return false; } + + if (!$this->validateDelete()) { + throw new \Exception("ValidateDelete failed"); + } + if (static::$allowDeleteFromDB) { - if ($this->validateDelete()) { - $this->checkSql(); - $this->sql->quickExec("DELETE FROM `" . static::$sqlTable . "` where " . static::$sqlIdx . " = '" . $this->{static::$sqlIdx} . "'"); - if (! (empty(static::$deletedFieldName))) { - $this->{static::$deletedFieldName} = true; - } - $this->{static::$sqlIdx} = null; - } else { - throw new \Exception("ValidateDelete failed"); + $this->checkSql(); + $this->sql->quickExec("DELETE FROM `" . static::$sqlTable . "` where " . static::$sqlIdx . " = '" . $this->{static::$sqlIdx} . "'"); + if (! (empty(static::$deletedFieldName))) { + $this->{static::$deletedFieldName} = true; } + $this->{static::$sqlIdx} = null; } elseif (! (empty(static::$deletedFieldName))) { $this->checkSql(); $this->sql->quickExec("UPDATE `" . static::$sqlTable . "` set `" . static::$deletedFieldName . "`='1' where " . $this::$sqlIdx . " = '" . $this->{static::$sqlIdx} . "'"); @@ -583,4 +589,8 @@ class baseClass extends dbStoredBase implements \JsonSerializable, jsonImportabl } return $rv; } + + protected static function log(string $instance, $method, string $message, ?user $user=null, ?string $refType=null, ?string $refId=null, string $msgCode=null, ?string $severity="INFO", $payload=null) { + return logEntry::add($instance, static::class, $method, $msgCode, $message, $severity, $user, $refType, $refId, $payload); + } } \ No newline at end of file diff --git a/core/fox/lang/ru.php b/core/fox/lang/ru.php index d3ac282..b7cfa84 100644 --- a/core/fox/lang/ru.php +++ b/core/fox/lang/ru.php @@ -30,7 +30,7 @@ class ru { Добрый день!
Для подтверждения адреса электронной почты укажите код \${confCodePrint} в форме подтверждения по адресу \${sitePrefix}/core/userEmailConfirm
+\${sitePrefix}/core/userEmailConfirm'>\${sitePrefix}/core/userEmailConfirm
или перейдите по ссылке

diff --git a/core/fox/logEntry.php b/core/fox/logEntry.php new file mode 100644 index 0000000..ed279e5 --- /dev/null +++ b/core/fox/logEntry.php @@ -0,0 +1,61 @@ +["type"=>"CHAR(10)","index"=>"INDEX"], + "module"=>["type"=>"VARCHAR(255)","index"=>"INDEX"], + "class"=>["type"=>"VARCHAR(255)"], + "method"=>["type"=>"VARCHAR(255)"], + "userId"=>["type"=>"INT","index"=>"INDEX"], + "msgCode"=>["type"=>"CHAR(16)"], + "message"=>["type"=>"TEXT"], + "refType"=>["type"=>"VARCHAR(255)","index"=>"INDEX"], + "refId"=>["type"=>"VARCHAR(255)","index"=>"INDEX"], + ]; + + const sevDebug="DEBUG"; + const sevInfo="INFO"; + const sevWarning="WARN"; + const sevAlert="ALERT"; + + protected function __xConstruct() { + $this->entryStamp=new time(); + } + + protected function validateSave() { + if ($this->entryStamp->isNull()) { $this->entryStamp=time::current(); } + return true; + } + + public static function add(string $instance, string $class, string $method, ?string $msgCode=null, string $message, ?string $severity="INFO", ?user $user=null, ?string $refType=null, ?string $refId=null, $payload=null) { + $l = new static(); + $l->module = $instance; + $l->class=$class; + $l->method=$method; + $l->userId = empty($user)?null:$user->id; + $l->msgCode=$msgCode; + $l->message=$message; + $l->refType=$refType; + $l->refId=$refId; + $l->payload=empty($payload)?[]:$payload; + $l->severity=$severity; + $l->save(); + return $l; + } +} +?> \ No newline at end of file diff --git a/core/fox/mailAccount.php b/core/fox/mailAccount.php index 934c4f3..f08afd1 100644 --- a/core/fox/mailAccount.php +++ b/core/fox/mailAccount.php @@ -22,10 +22,12 @@ namespace fox; * @property mixed $rxPassword * @property mixed $rxLogin * @property mixed $rxPassword + * @property mixed $rxURL + * @property mixed $txURL * **/ -class mailAccount extends baseClass { +class mailAccount extends baseClass implements externalCallable { protected $id; public $address; public $rxServer; @@ -45,6 +47,17 @@ class mailAccount extends baseClass { public bool $default=false; public bool $deleted=false; + const urlSchemas=[ + "rx"=>[ + "imap"=>["ssl"=>false,"port"=>143,"proto"=>"imap"], + "imaps"=>["ssl"=>true,"port"=>993,"proto"=>"imap"], + ], + "tx"=>[ + "smtp"=>["ssl"=>false,"port"=>587,"proto"=>"smtp"], + "smtps"=>["ssl"=>true,"port"=>465,"proto"=>"smtp"], + ] + ]; + public static $deletedFieldName = "deleted"; public static $sqlTable = 'tblMailAccounts'; @@ -76,21 +89,63 @@ class mailAccount extends baseClass { public function __set($key, $val) { switch ($key) { + case "txURL": + $this->parceURL($val, "tx"); + break; + case "rxURL": + $this->parceURL($val, "rx"); + break; case "password": $this->__password = xcrypt::encrypt($val); break; default: parent::__set($key, $val); } } + protected function parceURL($val, $dst) { + if (!array_key_exists($dst, static::urlSchemas)) { + foxException::throw("ERR","Invalid dst scheme",400,"UDSCH"); + } + + $schemaRef=static::urlSchemas[$dst]; + $ref=parse_url($val); + if (!array_key_exists($ref["scheme"], $schemaRef)) { + foxException::throw("ERR","Invalid URL scheme",400,"UUSCH"); + } + $this->{$dst."SSL"}=$schemaRef[$ref["scheme"]]["ssl"]; + $this->{$dst."Port"}=empty($ref["port"])?$schemaRef[$ref["scheme"]]["port"]:$ref["port"]; + $this->{$dst."Server"}=$ref["host"]; + $this->{$dst."Proto"}=$schemaRef[$ref["scheme"]]["proto"]; + + } + public function connect() { return new mailClient($this); } protected function validateSave() { - if (empty($this->login) || empty($this->__password) || empty($this->address)) { return false;} + if (empty($this->login) || empty($this->__password) || empty($this->address)) { + throw new foxException("Validation failed",406); + } return true; } + protected function validateDelete() + { + if ($this->default) { + foxException::throw("ERR","Default account deletion prohibited",406,"DDAX"); + } + return true; + } + + public function setDefault() { + if ($this->default) { return;} + if ($this->deleted) { throw new foxException("Unacceptable",406); } + + $this->checkSql(); + $this->sql->quickExec("UPDATE `".static::$sqlTable."` set `default`=0"); + $this->default=true; + $this->save(); + } public static function getDefaultAccount(&$sql=null) { $ref = new static(); @@ -101,4 +156,72 @@ class mailAccount extends baseClass { } else {return null;} } + public static function API_GET_list(request $request) { + if (! $request->user->checkAccess("adminMailAccounts", "core")) { + throw new foxException("Forbidden", 403); + } + + return static::search(); + } + + public static function API_DELETE(request $request) { + if (! $request->user->checkAccess("adminMailAccounts", "core")) { + throw new foxException("Forbidden", 403); + } + $m=new static(common::clearInput($request->function,"0-9")); + $m->delete(); + static::log($request->instance,__FUNCTION__, "Mail account ".$m->address." deleted",$request->user,"mailAccount",$m->id); + } + + public static function API_GET(request $request) { + if (! $request->user->checkAccess("adminMailAccounts", "core")) { + throw new foxException("Forbidden", 403); + } + $m=new static(common::clearInput($request->function,"0-9")); + return $m; + } + + public static function APIX_GET_setDefault(request $request) { + if (! $request->user->checkAccess("adminMailAccounts", "core")) { + throw new foxException("Forbidden", 403); + } + $m=new static(common::clearInput($request->function,"0-9")); + $m->setDefault(); + static::log($request->instance,__FUNCTION__, "Default mail account changed to ".$m->address,$request->user,"mailAccount",$m->id); + } + + public static function API_PUT(request $request) { + if (! $request->user->checkAccess("adminMailAccounts", "core")) { + throw new foxException("Forbidden", 403); + } + $m=new static(); + $m->address=common::clearInput($request->requestBody->address,"0-9A-Za-z._@-"); + $m->rxURL=common::clearInput($request->requestBody->rxURL,"0-9A-Za-z._:/-"); + $m->txURL=common::clearInput($request->requestBody->txURL,"0-9A-Za-z._:/-"); + $m->login=common::clearInput($request->requestBody->login,"0-9A-Za-z._@-"); + $m->password=$request->requestBody->password; + $m->rxFolder=common::clearInput($request->requestBody->rxFolder,"0-9A-Za-z._-"); + $m->rxArchiveFolder=common::clearInput($request->requestBody->rxArchiveFolder,"0-9A-Za-z._-");$m->save(); + static::log($request->instance,__FUNCTION__, "Mail account ".$m->address." created",$request->user,"mailAccount",$m->id,null,logEntry::sevInfo); + return $m; + } + + public static function API_PATCH(request $request) { + if (! $request->user->checkAccess("adminMailAccounts", "core")) { + throw new foxException("Forbidden", 403); + } + $m=new static(common::clearInput($request->function,"0-9")); + $m->address=common::clearInput($request->requestBody->address,"0-9A-Za-z._@-"); + $m->rxURL=common::clearInput($request->requestBody->rxURL,"0-9A-Za-z._:/-"); + trigger_error(common::clearInput($request->requestBody->rxURL,"0-9A-Za-z._:/-")); + $m->txURL=common::clearInput($request->requestBody->txURL,"0-9A-Za-z._:/-"); + $m->login=common::clearInput($request->requestBody->login,"0-9A-Za-z._@-"); + if (!empty($request->requestBody->password)) { $m->password=$request->requestBody->password; } + $m->rxFolder=common::clearInput($request->requestBody->rxFolder,"0-9A-Za-z._-"); + $m->rxArchiveFolder=common::clearInput($request->requestBody->rxArchiveFolder,"0-9A-Za-z._-"); + $m->save(); + static::log($request->instance,__FUNCTION__, "Mail account ".$m->address." updated",$request->user,"mailAccount",$m->id,null,logEntry::sevInfo,["changelog"=>"$m->changelog"]); + return $m; + } + } \ No newline at end of file diff --git a/core/fox/mailBlocklist.php b/core/fox/mailBlocklist.php index 7240072..58075f3 100644 --- a/core/fox/mailBlocklist.php +++ b/core/fox/mailBlocklist.php @@ -57,6 +57,7 @@ class mailBlocklist extends baseClass implements externalCallable { $bl=new static(); $bl->address=$eMail; + static::log($request->instance,__FUNCTION__, "Address ".$eMail." added to blockList",$request->user,"eMailAddress",$eMail,null,logEntry::sevInfo); $bl->save(); } @@ -73,6 +74,7 @@ class mailBlocklist extends baseClass implements externalCallable { if ($bl = static::getByAddress($eMail)) { $bl->delete(); + static::log($request->instance,__FUNCTION__, "Address ".$eMail." removed from blockList",$request->user,"eMailAddress",$eMail,null,logEntry::sevInfo); foxRequestResult::throw("200", "Deleted"); } else { foxException::throw("WARN", "Not found", 404,"ANF"); diff --git a/core/fox/moduleInfo.php b/core/fox/moduleInfo.php index 51caaaf..3993fbc 100644 --- a/core/fox/moduleInfo.php +++ b/core/fox/moduleInfo.php @@ -314,6 +314,7 @@ class moduleInfo extends baseClass implements externalCallable if (empty($request->parameters[0])) { $mod->delete(); + static::log($request->instance,__FUNCTION__, "Module ".$mod->name." deleted",$request->user); foxRequestResult::throw(200, "Deleted"); } @@ -324,12 +325,14 @@ class moduleInfo extends baseClass implements externalCallable unset($mod->features[$idx]); $mod->features=array_values($mod->features); $mod->save(); + static::log($request->instance,__FUNCTION__, "feature ".common::clearInput($request->requestBody->feature)." deleted from module ".$mod->name,$request->user); foxRequestResult::throw(200, "Deleted"); } break; case "config": config::del(common::clearInput($request->requestBody->key), $mod->name); + static::log($request->instance,__FUNCTION__, "config key ".common::clearInput($request->requestBody->key)." deleted from module ".$mod->name,$request->user); foxRequestResult::throw(200, "Deleted"); break; default: @@ -359,6 +362,7 @@ class moduleInfo extends baseClass implements externalCallable $mod->features[]=common::clearInput($request->requestBody->feature); $mod->features=array_values($mod->features); $mod->save(); + static::log($request->instance,__FUNCTION__, "feature ".common::clearInput($request->requestBody->feature)." set for module ".$mod->name,$request->user,"module",$user->id,null,logEntry::sevInfo); foxRequestResult::throw(201, "Created"); } foxRequestResult::throw(201, "Created"); @@ -366,6 +370,7 @@ class moduleInfo extends baseClass implements externalCallable case "config": config::set(common::clearInput($request->requestBody->key), $request->requestBody->value, $mod->name); + static::log($request->instance,__FUNCTION__, "config key ".common::clearInput($request->requestBody->key)." set for module ".$mod->name,$request->user,"module",$user->id,null,logEntry::sevInfo); foxRequestResult::throw(201, "Created"); break; default: diff --git a/core/fox/modules.php b/core/fox/modules.php index c751015..f3f186a 100644 --- a/core/fox/modules.php +++ b/core/fox/modules.php @@ -37,6 +37,8 @@ class modules implements externalCallable "adminModulesInstall"=>"Install modules", "adminUsers"=>"Manage users", "adminUserGroups"=>"Manage userGroups", + "adminMailAccounts"=>"Manage system mail accounts", + "adminAuthMethods"=>"Manage auth methods", ], "configKeys"=> [ "converterURL"=>"FoxConverter URL prefix", @@ -73,10 +75,26 @@ class modules implements externalCallable "ru" => "Группы", "en" => "Groups" ], - "titleLangIdx" => "adminGroups", "function" => "groups", "pageKey" => "adminGrous" - ] + ], + [ + "title" => [ + "ru" => "Учетные записи почты", + "en" => "Mail accounts" + ], + "function" => "mailAccounts", + "pageKey" => "mailAccounts", + ], + [ + "title" => [ + "ru" => "Методы oAuth", + "en" => "oAuth methods" + ], + "function" => "oauth", + "pageKey" => "oauth", + ], + ] ] ], @@ -208,7 +226,7 @@ class modules implements externalCallable $mod->modPriority=$modPriority; } $mod->save(); - + static::log($request->instance,__FUNCTION__, "Module ".$mod->name." installed",$request->user,"module",$mod->id,null,logEntry::sevInfo); foxRequestResult::throw(201, "Created", $mod); } diff --git a/core/fox/oAuthProfile.php b/core/fox/oAuthProfile.php index b6e9ff3..90821ea 100644 --- a/core/fox/oAuthProfile.php +++ b/core/fox/oAuthProfile.php @@ -14,7 +14,7 @@ class oAuthProfile extends baseClass implements externalCallable { protected $id; public $name; - protected $hash; + public $hash; public $url; public $clientId; protected $__clientKey; @@ -45,6 +45,17 @@ class oAuthProfile extends baseClass implements externalCallable { return true; } + public function __set($key, $val) { + switch ($key) { + case "clientKey": + $this->__clientKey=$val; + break; + default: + parent::__set($key, $val); + break; + } + } + public function getClient($redirectUrl, $scope=null) : oAuthClient { return new oAuthClient($this->url, $this->clientId, $this->__clientKey, $redirectUrl."/".$this->hash,$scope,$this->config); } @@ -59,5 +70,81 @@ class oAuthProfile extends baseClass implements externalCallable { throw new foxException("Invalid hash"); } } + + public static function API_GET_list(request $request) { + if (! $request->user->checkAccess("adminAuthMethods", "core")) { + throw new foxException("Forbidden", 403); + } + return static::search(); + } + + public static function APIX_GET_disable(request $request) { + if (! $request->user->checkAccess("adminAuthMethods", "core")) { + throw new foxException("Forbidden", 403); + } + $m=new static(common::clearInput($request->function,"0-9")); + $m->enabled=false; + $m->save(); + static::log($request->instance,__FUNCTION__, "OAuth profile ".$m->name." disabled.",$request->user,"oAuthProfile",$m->id,null,logEntry::sevInfo); + + } + + public static function APIX_GET_enable(request $request) { + if (! $request->user->checkAccess("adminAuthMethods", "core")) { + throw new foxException("Forbidden", 403); + } + $m=new static(common::clearInput($request->function,"0-9")); + $m->enabled=true; + $m->save(); + static::log($request->instance,__FUNCTION__, "OAuth profile ".$m->name." enabled.",$request->user,"oAuthProfile",$m->id,null,logEntry::sevInfo); + } + + public static function API_GET(request $request) { + if (! $request->user->checkAccess("adminAuthMethods", "core")) { + throw new foxException("Forbidden", 403); + } + $m=new static(common::clearInput($request->function,"0-9")); + return $m; + } + + public static function API_DELETE(request $request) { + if (! $request->user->checkAccess("adminAuthMethods", "core")) { + throw new foxException("Forbidden", 403); + } + $m=new static(common::clearInput($request->function,"0-9")); + $m->delete(); + static::log($request->instance,__FUNCTION__, "OAuth profile ".$m->name." deleted.",$request->user,"oAuthProfile",$m->id,null,logEntry::sevInfo); + } + + public static function API_PUT(request $request) { + if (! $request->user->checkAccess("adminAuthMethods", "core")) { + throw new foxException("Forbidden", 403); + } + $m=new static(); + $m->name=common::clearInput($request->requestBody->name,"0-9A-Za-z._@-"); + $m->url=common::clearInput($request->requestBody->url,"0-9A-Za-z._:/-"); + $m->clientId=common::clearInput($request->requestBody->clientId,"0-9A-Za-z._:/-"); + $m->clientKey=common::clearInput($request->requestBody->clientKey,"0-9A-Za-z._@-"); + $m->config=$request->requestBody->config; + $m->hash=common::clearInput($request->requestBody->hash,"0-9A-Za-z._@-"); + $m->save(); + static::log($request->instance,__FUNCTION__, "OAuth profile ".$m->name." created.",$request->user,"oAuthProfile",$m->id,null,logEntry::sevInfo); + return $m; + } + + public static function API_PATCH(request $request) { + if (! $request->user->checkAccess("adminAuthMethods", "core")) { + throw new foxException("Forbidden", 403); + } + $m=new static(common::clearInput($request->function,"0-9")); + $m->name=common::clearInput($request->requestBody->name,"0-9A-Za-z._@-"); + $m->url=common::clearInput($request->requestBody->url,"0-9A-Za-z._:/-"); + if (!empty($request->requestBody->clientId)) { $m->clientId=common::clearInput($request->requestBody->clientId,"0-9A-Za-z._:/-"); } + if (!empty($request->requestBody->clientKey)) { $m->clientKey=common::clearInput($request->requestBody->clientKey,"0-9A-Za-z._@-"); } + $m->config=$request->requestBody->config; + $m->hash=common::clearInput($request->requestBody->hash,"0-9A-Za-z._@-"); + $m->save(); + static::log($request->instance,__FUNCTION__, "OAuth profile ".$m->name." updated.",$request->user,"oAuthProfile",$m->id,null,logEntry::sevInfo,["changelog"=>$m->changelog]); + } } ?> \ No newline at end of file diff --git a/core/fox/user.php b/core/fox/user.php index 6ed7727..3be9a64 100644 --- a/core/fox/user.php +++ b/core/fox/user.php @@ -325,14 +325,17 @@ class user extends baseClass implements externalCallable } $user = new static(common::clearInput($request->function,"0-9")); $user->sendEMailConfirmation(); + static::log($request->instance,__FUNCTION__, "mailConfirmation sent for user ".$user->login,$request->user,"user",$user->id,null,logEntry::sevInfo); } public static function API_GET_sendEMailConfirmation(request $request) { $request->user->sendEMailConfirmation(); + static::log($request->instance,__FUNCTION__, "mailConfirmation sent for user ".$user->login,$request->user,"user",$user->id,null,logEntry::sevInfo); } public static function API_POST_validateEMailCode(request $request) { if ($request->user->validateEMailConfirmation(common::clearInput($request->requestBody->code,"0-9"))) { + static::log($request->instance,__FUNCTION__, "Mail address confirmed for user ".$request->user->login,$request->user,"user",$request->user->id,null,logEntry::sevInfo); return; } else { foxException::throw("ERR", "Validation failed", 400); diff --git a/core/fox/userGroup.php b/core/fox/userGroup.php index 517a543..f1f6ad7 100644 --- a/core/fox/userGroup.php +++ b/core/fox/userGroup.php @@ -182,7 +182,7 @@ class userGroup extends baseClass implements externalCallable $group = new static(common::clearInput($request->requestBody->groupId,"0-9")); $group->dropAccessRule(common::clearInput($request->requestBody->rule), common::clearInput($request->requestBody->module)); $group->save(); - + static::log($request->instance,__FUNCTION__, "Rule ".common::clearInput($request->requestBody->rule)."@".common::clearInput($request->requestBody->module)." removed from group ".$group->name,$request->user,"userGroup",$group->id,null,logEntry::sevInfo); foxRequestResult::throw(200, "Deleted"); } @@ -194,6 +194,7 @@ class userGroup extends baseClass implements externalCallable } $group = new static(common::clearInput($request->requestBody->groupId,"0-9")); $group->addAccessRule(common::clearInput($request->requestBody->rule), ($request->requestBody->forAll=="1")?"":common::clearInput($request->requestBody->module)); + static::log($request->instance,__FUNCTION__, "Rule ".common::clearInput($request->requestBody->rule)."@".common::clearInput($request->requestBody->module)." added for group ".$group->name,$request->user,"userGroup",$group->id,null,logEntry::sevInfo); $group->save(); } @@ -233,6 +234,7 @@ class userGroup extends baseClass implements externalCallable $group->name=$grName; $group->isList=$request->requestBody->isList==1; $group->save(); + static::log($request->instance,__FUNCTION__, "UserGroup ".$group->name." created.",$request->user,"userGroup",$group->id,null,logEntry::sevInfo); foxRequestResult::throw(201, "Created",$group); break; case "DELETE": @@ -242,6 +244,8 @@ class userGroup extends baseClass implements externalCallable } $group->delete(); + static::log($request->instance,__FUNCTION__, "UserGroup ".$group->name." deleted.",$request->user,"userGroup",$group->id,null,logEntry::sevInfo); + throw new foxRequestResult("Deleted",200); break; default: diff --git a/core/fox/userGroupMembership.php b/core/fox/userGroupMembership.php index b0790ea..26eaa74 100644 --- a/core/fox/userGroupMembership.php +++ b/core/fox/userGroupMembership.php @@ -212,6 +212,7 @@ class userGroupMembership extends baseClass implements externalCallable $userGroupMembership->groupId=$userGroup->id; $userGroupMembership->save(); $user->flushACRCache(); + static::log($request->instance,__FUNCTION__, "User ".$user->login ." join group ".$userGroup->name,$request->user,"user",$user->id,null,logEntry::sevInfo); foxRequestResult::throw("201", "Created"); break; @@ -219,6 +220,8 @@ class userGroupMembership extends baseClass implements externalCallable if ($userGroupMembership==null) { foxException::throw("ERR", "Not found", 404, "UGN"); } + static::log($request->instance,__FUNCTION__, "User ".$userGroupMembership->user->login ." left group ".$userGroupMembership->group->name,$request->user,"user",$userGroupMembership->user->id,null,logEntry::sevInfo); + $userGroupMembership->delete(); $user->flushACRCache(); break; diff --git a/core/fox/userInvitation.php b/core/fox/userInvitation.php index c50f5ab..aa8932a 100644 --- a/core/fox/userInvitation.php +++ b/core/fox/userInvitation.php @@ -112,7 +112,7 @@ class userInvitation extends baseClass implements externalCallable { } catch (\Exception $e) { trigger_error($e->getMessage()); } - + static::log($request->instance,__FUNCTION__, "User invitation ".$inv->regCode." created.",$request->user,"userInvitation",$inv->id,null,logEntry::sevInfo); return $inv; } @@ -127,6 +127,7 @@ class userInvitation extends baseClass implements externalCallable { } $inv=new static(common::clearInput($request->function)); $inv->sendEmail(); + static::log($request->instance,__FUNCTION__, "User invitation ".$inv->regCode." email resent.",$request->user,"userInvitation",$inv->id,null,logEntry::sevInfo); } public static function API_DELETE(request $request) { @@ -135,6 +136,7 @@ class userInvitation extends baseClass implements externalCallable { } $inv=new static(common::clearInput($request->function)); $inv->delete(); + static::log($request->instance,__FUNCTION__, "User invitation ".$inv->regCode." deleted.",$request->user,"userInvitation",$inv->id,null,logEntry::sevInfo); } } diff --git a/static/js/core/coreGroups.js b/static/js/core/coreGroups.js index f6d6333..bb30d01 100644 --- a/static/js/core/coreGroups.js +++ b/static/js/core/coreGroups.js @@ -98,7 +98,7 @@ function btnGroupAdd_click() { } function btnGroupDel_click(ref) { - var gid=($(ref.currentTarget).attr("groupId")); + var gid=($(ref.target).closest("tr").attr("groupId")); var buttons={}; buttons[langPack.core.iface.dialodDelButton]=function() { $("#dialogInfo").dialog("close"); @@ -115,5 +115,5 @@ function btnGroupDel_click(ref) { buttons[langPack.core.iface.dialodCloseButton]=function() { $("#dialogInfo").dialog("close"); } - UI.showInfoDialog(langPack.core.iface.groups.delButtonTitle+" #"+gid+" "+$(ref.currentTarget).attr("xname"),langPack.core.iface.groups.delButtonTitle,buttons); + UI.showInfoDialog(langPack.core.iface.groups.delButtonTitle+" #"+gid+" "+$(ref.target).closest("tr").attr("xname"),langPack.core.iface.groups.delButtonTitle,buttons); } \ No newline at end of file diff --git a/static/js/core/coreMailAccounts.js b/static/js/core/coreMailAccounts.js new file mode 100644 index 0000000..8642621 --- /dev/null +++ b/static/js/core/coreMailAccounts.js @@ -0,0 +1,210 @@ +export function load() { + UI.breadcrumbsUpdate(langPack.core.breadcrumbs.modTitle+" / "+langPack.core.breadcrumbs.mailAccounts); + + UI.createTabsPanel({ + accounts:{"title":langPack.core.iface.mailAccounts.allTitle, preLoad: reloadList,buttons: UI.addButton({id: "btn_acctadd",icon: "fas fa-plus",title: langPack.core.iface.mailAccounts.addButtonTitle, onClick: btnAccAdd_click})}, + + }) +} + +function btnAccAdd_click(ref) { + var buttons={}; + buttons[langPack.core.iface.add]=async function() { + let fdata=UI.collectForm("addgrp", true,false, false,true); + if (fdata.validateErrCount==0) { + API.exec({ + errDict: langPack.core.iface.register.errors, + requestType: "PUT", + data: { + address: fdata.address.val, + rxURL: fdata.rxUrl.val, + txURL: fdata.txUrl.val, + login: fdata.login.val, + password: fdata.passwd.val, + rxFolder: fdata.rxInbox.val, + rxArchiveFolder: fdata.rxArchive.val, + }, + method: "core/mailAccount", + onSuccess: function(json) { + UI.closeDialog('addgrp'); + reloadList(); + }, + }); + + } + }; + buttons[langPack.core.iface.dialodCloseButton]=function() { UI.closeDialog('addgrp');} + + + UI.createDialog( + UI.addFieldGroup([ + UI.addField({title: langPack.core.iface.mailAccounts.address, type: "input", item: "acc_address", reqx:true}), + UI.addField({title: langPack.core.iface.mailAccounts.login, type: "input", item: "acc_login",reqx:false}), + UI.addField({title: langPack.core.iface.password, type: "passwordNew", item: "acc_passwd",reqx:false}), + UI.addField({title: langPack.core.iface.mailAccounts.rxURL, type: "input", item: "acc_rxUrl",reqx:true}), + UI.addField({title: langPack.core.iface.mailAccounts.txURL, type: "input", item: "acc_txUrl",reqx:true}), + UI.addField({title: langPack.core.iface.mailAccounts.rxFolder, type: "input", item: "reg_rxInbox",reqx: true, val: "INBOX" }), + UI.addField({title: langPack.core.iface.mailAccounts.archiveFolder, type: "input", item: "reg_rxArchive",reqx:true, val: "Archive"}), + + ]), + langPack.core.iface.add, + buttons, + 515,1,'addgrp'); + + UI.openDialog('addgrp'); +} + +function reloadList() { + $("div.widget.accounts").empty(); + API.exec("GET","core/mailAccount/list",{},function(json) { + let tx = $("",{class: "datatable sel"}); + $("",{}).bind('contextmenu', mliContextMenuOpen).addClass("contextMenu").attr("accId",acc.id).attr("accAddr",acc.address); + $("",{append: row}).appendTo(tx); + }); + + }) +} + +function mliContextMenuOpen(el) { + UI.contextMenuOpen(el,[ + {title: langPack.core.iface.mailAccounts.setDefault, onClick: function() { + accDefault_Click(el); + }}, + {title: langPack.core.iface.edit, onClick: function() { + accEdit_Click(el); + }}, + + {title: langPack.core.iface.delete, onClick: function() { + accDelete_Click(el); + }}, + + ],$(el.target).closest("tr").attr("accaddr")); + return false; +} + +function accDelete_Click(ref) { + var acid=($(ref.target).closest("tr").attr("accid")); + var buttons={}; + buttons[langPack.core.iface.dialodDelButton]=function() { + $("#dialogInfo").dialog("close"); + API.exec({ + requestType: "DELETE", + method: "core/mailAccount/"+acid, + onSuccess: function(json) { + reloadList(); + }, + errDict: langPack.core.iface.mailAccounts.errors, + }); + + }; + + buttons[langPack.core.iface.dialodCloseButton]=function() { $("#dialogInfo").dialog("close"); } + + UI.showInfoDialog(langPack.core.iface.mailAccounts.delDialogText+" #"+acid+" "+$(ref.target).closest("tr").attr("accAddr"),langPack.core.iface.delete,buttons); +} + +function accDefault_Click(ref) { + var acid=($(ref.target).closest("tr").attr("accid")); + var buttons={}; + buttons[langPack.core.iface.set]=function() { + $("#dialogInfo").dialog("close"); + API.exec({ + requestType: "GET", + method: "core/mailAccount/"+acid+"/setDefault", + onSuccess: function(json) { + reloadList(); + }, + errDict: langPack.core.iface.mailAccounts.errors, + }); + + }; + + buttons[langPack.core.iface.dialodCloseButton]=function() { $("#dialogInfo").dialog("close"); } + + UI.showInfoDialog(langPack.core.iface.mailAccounts.setDefaultDialogText+" #"+acid+" "+$(ref.target).closest("tr").attr("accAddr"),langPack.core.iface.set,buttons); +} + +function accEdit_Click(ref) { + var buttons={}; + var acid=($(ref.target).closest("tr").attr("accid")); + + buttons[langPack.core.iface.add]=async function() { + let fdata=UI.collectForm("addgrp", true,false, false,true); + if (fdata.validateErrCount==0) { + API.exec({ + errDict: langPack.core.iface.register.errors, + requestType: "PATCH", + data: { + address: fdata.address.val, + rxURL: fdata.rxUrl.val, + txURL: fdata.txUrl.val, + login: fdata.login.val, + password: fdata.passwd.val, + rxFolder: fdata.rxInbox.val, + rxArchiveFolder: fdata.rxArchive.val, + }, + method: "core/mailAccount/"+acid, + onSuccess: function(json) { + UI.closeDialog('addgrp'); + reloadList(); + }, + }); + + } + }; + buttons[langPack.core.iface.dialodCloseButton]=function() { UI.closeDialog('addgrp');} + + + UI.createDialog( + UI.addFieldGroup([ + UI.addField({title: langPack.core.iface.mailAccounts.address, type: "input", item: "acc_address", reqx:true}), + UI.addField({title: langPack.core.iface.mailAccounts.login, type: "input", item: "acc_login",reqx:false}), + UI.addField({title: langPack.core.iface.password, type: "passwordNew", item: "acc_passwd",reqx:false}), + UI.addField({title: langPack.core.iface.mailAccounts.rxURL, type: "input", item: "acc_rxUrl",reqx:true}), + UI.addField({title: langPack.core.iface.mailAccounts.txURL, type: "input", item: "acc_txUrl",reqx:true}), + UI.addField({title: langPack.core.iface.mailAccounts.rxFolder, type: "input", item: "reg_rxInbox",reqx: true, val: "INBOX" }), + UI.addField({title: langPack.core.iface.mailAccounts.archiveFolder, type: "input", item: "reg_rxArchive",reqx:true, val: "Archive"}), + + ]), + langPack.core.iface.add, + buttons, + 515,1,'addgrp'); + + API.exec({ + errDict: langPack.core.iface.register.errors, + requestType: "GET", + method: "core/mailAccount/"+acid, + onSuccess: function(json) { + $("#acc_address").val(json.data.address); + $("#acc_login").val(json.data.login); + $("#acc_rxUrl").val(json.data.rxProto+(json.data.rxSSL?"s":"")+"://"+json.data.rxServer+":"+json.data.rxPort); + $("#acc_txUrl").val(json.data.txProto+(json.data.txSSL?"s":"")+"://"+json.data.txServer+":"+json.data.txPort); + $("#reg_rxInbox").val(json.data.rxFolder); + $("#reg_rxArchive").val(json.data.rxArchiveFolder); + return false; + }, + }); + + UI.openDialog('addgrp'); +} + diff --git a/static/js/core/coreModule.js b/static/js/core/coreModule.js index fec834c..f2acbf6 100644 --- a/static/js/core/coreModule.js +++ b/static/js/core/coreModule.js @@ -10,6 +10,8 @@ export var menuSelector={ "module":"adminModules", "users":"adminUsers", "group":"adminGrous", + mailAccounts:"mailAccounts", + oauth: "oauth", }; export function load() { @@ -59,6 +61,16 @@ export function load() { mod.load(); }) break; + case "mailAccounts": + import("./coreMailAccounts.js").then(function(mod) { + mod.load(); + }) + break; + case "oauth": + import("./coreOAuthProfiles.js").then(function(mod) { + mod.load(); + }) + break; default: diff --git a/static/js/core/coreOAuthProfiles.js b/static/js/core/coreOAuthProfiles.js new file mode 100644 index 0000000..a0e6bac --- /dev/null +++ b/static/js/core/coreOAuthProfiles.js @@ -0,0 +1,228 @@ +export function load() { + UI.breadcrumbsUpdate(langPack.core.breadcrumbs.modTitle+" / "+langPack.core.breadcrumbs.oauth); + + UI.createTabsPanel({ + methods:{"title":langPack.core.iface.oauth.allTitle, preLoad: reloadList,buttons: UI.addButton({id: "btn_acctadd",icon: "fas fa-plus",title: langPack.core.iface.oauth.addButtonTitle, onClick: btnAdd_click})}, + + }) +} + +function reloadList(ref) { + $("div.widget.methods").empty(); + API.exec("GET","core/oAuthProfile/list",{},function(json) { + let tx = $("
",{class: "idx", text: "#"}).appendTo(tx); + $("",{class: "", text: langPack.core.iface.mailAccounts.address}).appendTo(tx); + $("",{class: "", text: langPack.core.iface.mailAccounts.login}).appendTo(tx); + $("",{class: "", text: langPack.core.iface.mailAccounts.module}).appendTo(tx); + $("",{class: "", text: langPack.core.iface.mailAccounts.default}).appendTo(tx); + $("",{class: "button", style: "text-align: center", append: $("",{class: "fas fa-ellipsis-h"})}).appendTo(tx); + + tx.appendTo("div.widget.accounts"); + + let i=0; + $.each(json.data, function(idx, acc) { + i++; + let row=$("
",{class: "idx", text: i}).appendTo(row); + $("",{class: "", text: acc.address}).appendTo(row); + $("",{class: "", text: acc.login}).appendTo(row); + $("",{class: "", text: acc.module}).appendTo(row); + $("",{class: "", text: acc.default?langPack.core.iface.yes:langPack.core.iface.no}).appendTo(row); + $("",{class: "button", style: "text-align: center", append: $("",{class: "fas fa-ellipsis-h"})}) + .click(mliContextMenuOpen) + .appendTo(row); + $("
",{class: "datatable sel"}); + $("",{}).bind('contextmenu', mliContextMenuOpen).addClass("contextMenu").attr("accActive",acc.enabled?1:0).attr("accHash",acc.hash).attr("accId",acc.id).attr("accName",acc.name); + $("",{append: row}).appendTo(tx); + }); + + }) +} + +function mliContextMenuOpen(el) { + + let menuItems=[]; + let menuOnOff; + if ($(el.target).closest("tr").attr("accActive")==1) { + menuOnOff= {title: langPack.core.iface.disable, onClick: function() { profileDisable_Click(el) ; }}; + } else { + menuOnOff= {title: langPack.core.iface.enable, onClick: function() { profileEnable_Click(el);}}; + } + menuItems.push( + {title: "CopyURL", onClick: function() { UI.copySelText(getCallbackUrl($(el.target).closest("tr").attr("accHash")));}}, + menuOnOff, + {title: langPack.core.iface.edit, onClick: function() { profileEdit_Click(el); }}, + {title: langPack.core.iface.delete, onClick: function() { profileDelete_Click(el);}}, + ); + UI.contextMenuOpen(el,menuItems,$(el.target).closest("tr").attr("accName")); + return false; +} + +function profileDisable_Click(ref) { + var acid=($(ref.target).closest("tr").attr("accId")); + var buttons={}; + buttons[langPack.core.iface.disable]=function() { + $("#dialogInfo").dialog("close"); + API.exec({ + requestType: "GET", + method: "core/oAuthProfile/"+acid+"/disable", + onSuccess: function(json) { + reloadList(); + }, + errDict: langPack.core.iface.oauth.errors, + }); + + }; + + buttons[langPack.core.iface.dialodCloseButton]=function() { $("#dialogInfo").dialog("close"); } + + UI.showInfoDialog(langPack.core.iface.disable+" #"+acid+" "+$(ref.target).closest("tr").attr("accName"),langPack.core.iface.disable,buttons); + +} + +function profileEnable_Click(ref) { + var acid=($(ref.target).closest("tr").attr("accid")); + var buttons={}; + buttons[langPack.core.iface.enable]=function() { + $("#dialogInfo").dialog("close"); + API.exec({ + requestType: "GET", + method: "core/oAuthProfile/"+acid+"/enable", + onSuccess: function(json) { + reloadList(); + }, + errDict: langPack.core.iface.oauth.errors, + }); + + }; + + buttons[langPack.core.iface.dialodCloseButton]=function() { $("#dialogInfo").dialog("close"); } + + UI.showInfoDialog(langPack.core.iface.enable+" #"+acid+" "+$(ref.target).closest("tr").attr("accName"),langPack.core.iface.enable,buttons); + +} + +function profileDelete_Click(ref) { + var acid=($(ref.target).closest("tr").attr("accid")); + var buttons={}; + buttons[langPack.core.iface.delete]=function() { + $("#dialogInfo").dialog("close"); + API.exec({ + requestType: "DELETE", + method: "core/oAuthProfile/"+acid, + onSuccess: function(json) { + reloadList(); + }, + errDict: langPack.core.iface.oauth.errors, + }); + + }; + + buttons[langPack.core.iface.dialodCloseButton]=function() { $("#dialogInfo").dialog("close"); } + + UI.showInfoDialog(langPack.core.iface.delete+" #"+acid+" "+$(ref.target).closest("tr").attr("accName"),langPack.core.iface.delete,buttons); + +} + +function btnAdd_click(ref) { + var buttons={}; + var hash=UI.genPassword("0123456789abcdef", 64); + buttons[langPack.core.iface.add]=async function() { + let fdata=UI.collectForm("addgrp", true,false, false,true); + if (fdata.validateErrCount==0) { + API.exec({ + errDict: langPack.core.iface.register.errors, + requestType: "PUT", + data: { + name: fdata.name.val, + url: fdata.url.val, + clientId: fdata.clientId.val, + clientKey: fdata.clientKey.val, + config: fdata.type.val, + hash: hash, + }, + method: "core/oAuthProfile", + onSuccess: function(json) { + UI.closeDialog('addgrp'); + reloadList(); + }, + }); + + } + }; + buttons[langPack.core.iface.dialodCloseButton]=function() { UI.closeDialog('addgrp');} + + + UI.createDialog( + UI.addFieldGroup([ + UI.addField({title: langPack.core.iface.title, type: "input", item: "acc_name", reqx:true}), + UI.addField({title: langPack.core.iface.url, type: "input", item: "acc_url",reqx:false}), + UI.addField({title: langPack.core.iface.template, type: "select", item: "acc_type",reqx:true, args: {"":"--"+langPack.core.iface.oauth.selectPreset+"--","vk":"VK","yandex":"Yandex", "gitea":"Gitea", "gitlab":"Gitlab"}}), + UI.addField({title: langPack.core.iface.oauth.callback, type: "multiline", args: {rows: 4}, item: "acc_callbackurl", disabled: true, val: "callback"}), + UI.addField({title: langPack.core.iface.oauth.clientId, type: "input", item: "acc_clientId",reqx:true}), + UI.addField({title: langPack.core.iface.oauth.clientKey, type: "input", item: "acc_clientKey",reqx:true}), + ]), + langPack.core.iface.add, + buttons, + 515,1,'addgrp'); + $("#acc_callbackurl").val(getCallbackUrl(hash)); + UI.openDialog('addgrp'); +} + +function getCallbackUrl(hash) { + return API.settings.get("sitePrefix")+"/auth/oauth/"+hash; +} + +function profileEdit_Click(ref) { + var acid=($(ref.target).closest("tr").attr("accid")); + var buttons={}; + buttons[langPack.core.iface.edit]=async function() { + let fdata=UI.collectForm("addgrp", true,false, false,true); + if (fdata.validateErrCount==0) { + API.exec({ + errDict: langPack.core.iface.register.errors, + requestType: "PATCH", + data: { + name: fdata.name.val, + url: fdata.url.val, + clientId: fdata.clientId.val, + clientKey: fdata.clientKey.val, + config: fdata.type.val, + }, + method: "core/oAuthProfile/"+acid, + onSuccess: function(json) { + UI.closeDialog('addgrp'); + reloadList(); + }, + }); + + } + }; + buttons[langPack.core.iface.dialodCloseButton]=function() { UI.closeDialog('addgrp');} + + + UI.createDialog( + UI.addFieldGroup([ + UI.addField({title: langPack.core.iface.title, type: "input", item: "acc_name", reqx:true}), + UI.addField({title: langPack.core.iface.url, type: "input", item: "acc_url",reqx:false}), + UI.addField({title: langPack.core.iface.template, type: "select", item: "acc_type",reqx:true, args: {"":"--"+langPack.core.iface.oauth.selectPreset+"--","vk":"VK","yandex":"Yandex", "gitea":"Gitea", "gitlab":"Gitlab"}}), + UI.addField({title: langPack.core.iface.oauth.clientId, type: "input", item: "acc_clientId",reqx:true}), + UI.addField({title: langPack.core.iface.oauth.clientKey, type: "input", item: "acc_clientKey",reqx:false}), + ]), + langPack.core.iface.add, + buttons, + 385,1,'addgrp'); + API.exec({ + errDict: langPack.core.iface.register.errors, + requestType: "GET", + method: "core/oAuthProfile/"+acid, + onSuccess: function(json) { + $("#acc_name").val(json.data.name); + $("#acc_url").val(json.data.url); + $("#acc_type").val(json.data.config); + $("#acc_clientId").val(json.data.clientId); + return false; + }, + }); + + UI.openDialog('addgrp'); + +} + diff --git a/static/js/core/lang_ru.js b/static/js/core/lang_ru.js index c3ea78d..d1c6240 100644 --- a/static/js/core/lang_ru.js +++ b/static/js/core/lang_ru.js @@ -8,6 +8,8 @@ export var langItem={ ok0: "Операция завершена успешно", dataEmpty: "Информация недоступна, скорее здесь ничего нет.", emptyInvCode: "-нет-", + enable: "Активировать", + disable: "Заблокировать", dataLoading: "Информация загружается", loginFailedDialogTitle: "Ошибка авторизации", loginFailed401text: "Неправильные имя пользователя или пароль\nУточните данные и попробуйте еще раз", @@ -17,10 +19,12 @@ export var langItem={ active: "Активен", version: "Версия", title: "Наименование", + url: "URL", desc: "Описание", installed: "Установлен", install: "Установить", set: "Установить", + edit: "Изменить", updated: "Обновлен", invCode: "invCode", dialodCloseButton: "Закрыть", @@ -132,6 +136,34 @@ export var langItem={ URNF: "Пользователь не найден либо восстановление пароля не предусмотрено спосбом авторизации", IVCC: "Некорректный код подтверждения. Проверьте правильность кода и повторите попытку.", } + }, + mailAccounts: { + allTitle: "Учетные записи электронной почты", + addButtonTitle: "Добавить УЗ", + address: "Адрес", + login: "Логин", + module: "Модуль", + rxURL: "RX URL", + txURL: "TX URL", + rxFolder: "Входящие", + archiveFolder: "Архив", + default: "Основной", + setDefault: "Сделать основным", + delDialogText: "Удалить аккаунт", + setDefaultDialogText: "Установить основным аккаунт", + errors: { + DDAX: "Нельзя удалить основной аккаунт", + } + }, + oauth: { + allTitle: "Методы oAuth", + addButtonTitle: "Добавить метод", + clientId: "ID Клиента", + clientKey: "Секретный ключ", + callback: "URL возврата", + selectPreset: "Выберите шаблон", + errors: { + } } }, @@ -140,7 +172,9 @@ export var langItem={ modules: "Модули", users: "Пользователи", groups: "Группы", - myprofile: "Мой профиль" + myprofile: "Мой профиль", + mailAccounts: "Учетные записи ЭП", + oauth: "oAuth", }, date: { diff --git a/static/js/core/login.js b/static/js/core/login.js index 8c5c290..728a03e 100644 --- a/static/js/core/login.js +++ b/static/js/core/login.js @@ -3,7 +3,8 @@ import * as UI from './ui.js'; import { langPack, lpCacheDrop } from './langpack.js'; export function load() { - $("body").empty(); + $("body").empty().addClass("login"); + $("html").addClass("login"); if (UI.parceURL().module=='auth' && UI.parceURL().function=='oauth') { let hash = UI.parceURL().id; diff --git a/static/js/core/ui.js b/static/js/core/ui.js index 3185f5b..e4b5fad 100644 --- a/static/js/core/ui.js +++ b/static/js/core/ui.js @@ -295,7 +295,7 @@ export function addField(ref)//title, item, blockstyle, fieldstyle, type, args, let password=genPassword(); $("#"+item_id).val(password); $("#"+item_id).change(); - if (typeof(ref.newPasswdGenCallback)) { + if (typeof(ref.newPasswdGenCallback)=="function") { ref.newPasswdGenCallback(password); } @@ -763,8 +763,8 @@ export function setTitle(title, desc) } export function initBody() { - - $("body").empty(); + $("html").removeClass("login"); + $("body").empty().removeClass("login"); $("
",{id: "contextMenu", class: "contextMenu"}).appendTo("body").hide(); $("
", { class: "t_global"}) .appendTo("body") diff --git a/static/theme/core/main.css b/static/theme/core/main.css index 9192a0e..53e709c 100644 --- a/static/theme/core/main.css +++ b/static/theme/core/main.css @@ -1518,6 +1518,10 @@ div.t_main width: 1681px; } +div.mainfame { + width: 1676px; +} + div.t_global { width: 1881px; @@ -1848,6 +1852,10 @@ div.t_main width: 1360px; } +div#mainframe { + width: 1343px !important; +} + div.t_global { width: 1560px; @@ -1926,6 +1934,10 @@ div.t_main margin-right: 0; } +div#mainframe { + width: 1022px !important; +} + div.t_global { width: 1239px; @@ -2039,6 +2051,10 @@ div.t_main } +div#mainframe { + width: 1022px !important; +} + div.t_global { width: 1035px; @@ -2135,6 +2151,10 @@ div.t_main width: 710px; } +div#mainframe { + width: 699px !important; +} + div.t_global { width: 710px;
",{class: "idx", text: "#"}).appendTo(tx); + $("",{class: "", text: langPack.core.iface.title}).appendTo(tx); + $("",{class: "", text: langPack.core.iface.url}).appendTo(tx); + $("",{class: "", text: langPack.core.iface.active}).appendTo(tx); + $("",{class: "button", style: "text-align: center", append: $("",{class: "fas fa-ellipsis-h"})}).appendTo(tx); + + tx.appendTo("div.widget.methods"); + + let i=0; + $.each(json.data, function(idx, acc) { + i++; + let row=$("
",{class: "idx", text: i}).appendTo(row); + $("",{class: "", text: acc.name}).appendTo(row); + $("",{class: "", text: acc.url}).appendTo(row); + $("",{class: "", text: acc.enabled?langPack.core.iface.yes:langPack.core.iface.no}).appendTo(row); + $("",{class: "button", style: "text-align: center", append: $("",{class: "fas fa-ellipsis-h"})}) + .click(mliContextMenuOpen) + .appendTo(row); + $("