Project first commit
67
.gitignore
vendored
Normal file
|
|
@ -0,0 +1,67 @@
|
||||||
|
#ignore target dir
|
||||||
|
target*/
|
||||||
|
|
||||||
|
#gradle files
|
||||||
|
.gradle*/
|
||||||
|
|
||||||
|
*.orig
|
||||||
|
#
|
||||||
|
# Eclipse project files
|
||||||
|
#
|
||||||
|
#.classpath
|
||||||
|
#.project
|
||||||
|
#.settings*/
|
||||||
|
.springBeans
|
||||||
|
.metadata/
|
||||||
|
war*/
|
||||||
|
|
||||||
|
#
|
||||||
|
# IntelliJ IDEA project files
|
||||||
|
#
|
||||||
|
.idea*/
|
||||||
|
.classes*/
|
||||||
|
*.ipr
|
||||||
|
*.iml
|
||||||
|
*.iws
|
||||||
|
*.ids
|
||||||
|
atlassian-ide-plugin.xml
|
||||||
|
|
||||||
|
#ignore NetBeans project files
|
||||||
|
nb-configuration.xml
|
||||||
|
profiles.xml
|
||||||
|
catalog.xml
|
||||||
|
nbactions.xml
|
||||||
|
|
||||||
|
#ignore some temporary files
|
||||||
|
*.vpp~*
|
||||||
|
|
||||||
|
# os meta files
|
||||||
|
Thumbs.db
|
||||||
|
.DS_Store
|
||||||
|
|
||||||
|
|
||||||
|
pom.xml.versionsBackup
|
||||||
|
*.jasper
|
||||||
|
|
||||||
|
#studio
|
||||||
|
.studio*/
|
||||||
|
|
||||||
|
resources/src/main/generated-resources*/
|
||||||
|
resources/src/main/resources/database/database_structure.xml
|
||||||
|
|
||||||
|
frontend/build*/
|
||||||
|
frontend/tmp*/
|
||||||
|
frontend/.angular*/
|
||||||
|
frontend/build_dev*/
|
||||||
|
frontend/dist*/
|
||||||
|
frontend/node_modules*/
|
||||||
|
frontend/src/ts/**/*.js
|
||||||
|
frontend/src/ts/**/*.js.map
|
||||||
|
frontend/src/ts/**/*.ngsummary.json
|
||||||
|
frontend/src/ts/aot*/
|
||||||
|
frontend/src/ts/generated*/
|
||||||
|
npm-debug.log
|
||||||
|
|
||||||
|
#Sublime project files
|
||||||
|
*.sublime-project
|
||||||
|
*.sublime-workspace
|
||||||
29
.studioignore
Normal file
|
|
@ -0,0 +1,29 @@
|
||||||
|
#Files for Webbpm-Studio to ignore
|
||||||
|
frontend/build/
|
||||||
|
frontend/build_dev/
|
||||||
|
frontend/dist/
|
||||||
|
frontend/node_modules/
|
||||||
|
frontend/src/ts/page.routing.ts
|
||||||
|
frontend/src/ts/generated-sources/
|
||||||
|
frontend/src/ts/generated/
|
||||||
|
frontend/target/
|
||||||
|
|
||||||
|
backend/target/
|
||||||
|
backend/src/main/generated-sources/
|
||||||
|
|
||||||
|
distribution/target/
|
||||||
|
|
||||||
|
resources/target/
|
||||||
|
|
||||||
|
test/
|
||||||
|
extensions/
|
||||||
|
|
||||||
|
config/
|
||||||
|
target/
|
||||||
|
themes/
|
||||||
|
|
||||||
|
.studio/
|
||||||
|
.git/
|
||||||
|
.idea/
|
||||||
|
.studioignore
|
||||||
|
**.js
|
||||||
224
README.md
Normal file
|
|
@ -0,0 +1,224 @@
|
||||||
|
# Создание БД проекта
|
||||||
|
|
||||||
|
Создание роли для основной схемы БД проекта
|
||||||
|
|
||||||
|
```
|
||||||
|
CREATE ROLE "<your-project-main-role>" WITH
|
||||||
|
LOGIN
|
||||||
|
NOSUPERUSER
|
||||||
|
INHERIT
|
||||||
|
NOCREATEDB
|
||||||
|
NOCREATEROLE
|
||||||
|
NOREPLICATION
|
||||||
|
PASSWORD '<your password>';
|
||||||
|
```
|
||||||
|
|
||||||
|
Создание роли для схемы безопасности БД проекта
|
||||||
|
|
||||||
|
```
|
||||||
|
CREATE ROLE "<your-project-security-role>" WITH
|
||||||
|
LOGIN
|
||||||
|
NOSUPERUSER
|
||||||
|
INHERIT
|
||||||
|
NOCREATEDB
|
||||||
|
NOCREATEROLE
|
||||||
|
NOREPLICATION
|
||||||
|
PASSWORD '<your password>';
|
||||||
|
```
|
||||||
|
|
||||||
|
Создание БД проекта
|
||||||
|
|
||||||
|
```
|
||||||
|
CREATE DATABASE "<your-project-db>"
|
||||||
|
WITH
|
||||||
|
OWNER = "<your-project-main-role>";
|
||||||
|
```
|
||||||
|
|
||||||
|
ВНИМАНИЕ: в общем случае, отдельную БД для безопасности создавать не нужно. В конфигурации источника данных security-ds в файле standalone.xml в качестве имени базы данных используйте базу данных приложения.
|
||||||
|
|
||||||
|
Предоставление необходимых прав для роли <your-project-security-role>
|
||||||
|
|
||||||
|
```
|
||||||
|
GRANT CREATE ON DATABASE "<your-project-db>" TO "<your-project-security-role>";
|
||||||
|
```
|
||||||
|
|
||||||
|
Создание таблицы shedlock для автосинхронизации
|
||||||
|
|
||||||
|
```
|
||||||
|
CREATE TABLE shedlock
|
||||||
|
(
|
||||||
|
name varchar not null
|
||||||
|
constraint tasks_lock_pkey
|
||||||
|
primary key,
|
||||||
|
lock_until timestamp,
|
||||||
|
locked_at timestamp,
|
||||||
|
locked_by varchar
|
||||||
|
);
|
||||||
|
|
||||||
|
comment on table shedlock is 'Таблица для синхронизации выполнения запланированных задач между нодами.';
|
||||||
|
|
||||||
|
ALTER TABLE shedlock
|
||||||
|
OWNER to "owner";
|
||||||
|
```
|
||||||
|
|
||||||
|
## Дополнительные ограничения базы секьюрити
|
||||||
|
|
||||||
|
Логин пользователя <user_account.username> и имена ролей <user_role.name> не должны совпадать, так как в ходе работы jbpm-а они сохраняются в одну и ту же таблицу.
|
||||||
|
Пример ошибки при совпадении: username = 'qa_test' и role_name = 'qa_test' (роль привязана к этому пользователю). Ошибка возникает при запуске любого процесса под этим пользователем.
|
||||||
|
|
||||||
|
```
|
||||||
|
ERROR [errorhandling.ExceptionHandlerController] (default task-5) [19usm9-bgyi63]
|
||||||
|
Organizational entity already exists with [GroupImpl:'qa_test'] id,
|
||||||
|
please check that there is no group and user with same id:
|
||||||
|
java.lang.RuntimeException: Organizational entity already exists with [GroupImpl:'qa_test'] id,
|
||||||
|
please check that there is no group and user with same id
|
||||||
|
```
|
||||||
|
|
||||||
|
## Создание нового администратора
|
||||||
|
|
||||||
|
Создайте группу <your-admin-group> и предоставьте ей права в модуль администрирования. Для этого выполните в БД проекта
|
||||||
|
|
||||||
|
```
|
||||||
|
INSERT INTO security.user_group(
|
||||||
|
user_group_id, name, access_level_id)
|
||||||
|
(SELECT uuid_in(md5(random()::text || clock_timestamp()::text)::cstring),
|
||||||
|
'<your-admin-group>', access_level_id FROM security.access_level where level=999);
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
INSERT INTO security.link_user_group_user_role(
|
||||||
|
link_user_group_user_role_id, user_group_id, user_role_id)
|
||||||
|
(SELECT uuid_in(md5(random()::text || clock_timestamp()::text)::cstring),
|
||||||
|
(SELECT user_group_id FROM security.user_group WHERE name = '<your-admin-group>'),
|
||||||
|
(SELECT user_role_id FROM security.user_role WHERE name = 'Security - User Admin'));
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
INSERT INTO security.link_user_group_user_role(
|
||||||
|
link_user_group_user_role_id, user_group_id, user_role_id)
|
||||||
|
(SELECT uuid_in(md5(random()::text || clock_timestamp()::text)::cstring),
|
||||||
|
(SELECT user_group_id FROM security.user_group WHERE name = '<your-admin-group>'),
|
||||||
|
(SELECT user_role_id FROM security.user_role WHERE name = 'Security - Group Admin'));
|
||||||
|
```
|
||||||
|
|
||||||
|
```
|
||||||
|
INSERT INTO security.link_user_group_user_role(
|
||||||
|
link_user_group_user_role_id, user_group_id, user_role_id)
|
||||||
|
(SELECT uuid_in(md5(random()::text || clock_timestamp()::text)::cstring),
|
||||||
|
(SELECT user_group_id FROM security.user_group WHERE name = '<your-admin-group>'),
|
||||||
|
(SELECT user_role_id FROM security.user_role WHERE name = 'Security - Role Admin'));
|
||||||
|
```
|
||||||
|
|
||||||
|
# Настройка браузера для входа в систему с помощью Kerberos
|
||||||
|
|
||||||
|
1. Запустите браузер firefox.
|
||||||
|
2. В адресной строке введите about:config, нажать кнопку "я принимаю на себя риск"
|
||||||
|
3. С помощью поиска найдите параметр network.negotiate-auth.trusted-uris и в качестве значения ввести домен(например для домена example.com надо ввести .example.com)
|
||||||
|
4. Откройте в браузере приложение. Пример http://app.example.com/ . Приложение должно открыться без запроса логина/пароля
|
||||||
|
|
||||||
|
# Восстановление структуры БД
|
||||||
|
|
||||||
|
На основе БД проекта с помощью jOOQ генерируются Java классы для каждого объекта БД. Это происходит по нажатию кнопки Обновить на панели БД в студии. При необходимости можно сформировать DDL на основе данных классов. Пример класса для генерации DDL
|
||||||
|
|
||||||
|
```
|
||||||
|
package ru.cg.webbpm.test_project.db_beans;
|
||||||
|
|
||||||
|
import org.jooq.*;
|
||||||
|
import org.jooq.impl.*;
|
||||||
|
|
||||||
|
public class Main {
|
||||||
|
public static void main (String args []) {
|
||||||
|
DefaultConfiguration defaultConfiguration = new DefaultConfiguration();
|
||||||
|
defaultConfiguration.setSQLDialect(SQLDialect.POSTGRES);
|
||||||
|
Queries ddl = DSL.using(defaultConfiguration).ddl(DefaultCatalog.DEFAULT_CATALOG);
|
||||||
|
|
||||||
|
for (Query query : ddl.queries()) {
|
||||||
|
System.out.println(query);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
** ВНИМАНИЕ: **
|
||||||
|
|
||||||
|
- этим способом нельзя восстановить функции/процедуры БД
|
||||||
|
|
||||||
|
см. также [https://www.jooq.org/doc/latest/manual/sql-building/ddl-statements/generating-ddl/](https://www.jooq.org/doc/latest/manual/sql-building/ddl-statements/generating-ddl/)
|
||||||
|
|
||||||
|
# Сборка проекта
|
||||||
|
## В dev режиме
|
||||||
|
```bash
|
||||||
|
mvn clean && mvn package
|
||||||
|
```
|
||||||
|
## В prod режиме
|
||||||
|
```bash
|
||||||
|
mvn clean && mvn package -Pprod -DngcCoreCount=4 -DpagePackSizeMb=24
|
||||||
|
```
|
||||||
|
ngcCoreCount - количество ядер, выделяемых процессу компиляции ngc. По умолчанию - количество ядер - 1
|
||||||
|
pagePackSizeMb - размер пачки в МБ. По умолчанию - количество ядер - 24 МБ.
|
||||||
|
|
||||||
|
## С обновлением database beans
|
||||||
|
```bash
|
||||||
|
mvn clean && mvn package -Dwebbpm.generate-db-beans
|
||||||
|
```
|
||||||
|
|
||||||
|
# Версия проекта
|
||||||
|
|
||||||
|
Если версия проекта содержит SNAPSHOT (например 1.0-SNAPSHOT), то при установке такой версии на сервере приложений будет запущена процедура остановки запущенных процессов данной версии. Этот режим удобен при отладке процесса на рабочем месте аналитика.
|
||||||
|
На боевом и тестовом стенде необходимо передавать дистрибутив проекта, с версией, которая не содержит SNAPSHOT. Например - 1.0
|
||||||
|
|
||||||
|
# Обновление платформы
|
||||||
|
|
||||||
|
## Обновления версии платформы
|
||||||
|
|
||||||
|
### С помощью студии
|
||||||
|
|
||||||
|
1. Откройте проект в студии. Версия платформы обновится автоматически
|
||||||
|
|
||||||
|
### Вручную
|
||||||
|
|
||||||
|
1. Обновите значение webbpm-platform.version в pom.xml. Пример
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<webbpm-platform.version>3.164.0-SNAPSHOT</webbpm-platform.version>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Обновление базового пакета компонент
|
||||||
|
|
||||||
|
### С помощью студии
|
||||||
|
|
||||||
|
1. Откройте проект в студии.
|
||||||
|
|
||||||
|
2. Откройте меню "Проект - Пакеты"
|
||||||
|
|
||||||
|
3. Нажмите обновить.
|
||||||
|
|
||||||
|
|
||||||
|
### Вручную
|
||||||
|
|
||||||
|
#### Из удаленного репозитория
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mvn webbpm:update-package -DpackageVersion="3.158.8"
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Из файла
|
||||||
|
|
||||||
|
```bash
|
||||||
|
mvn webbpm:update-package -DexecuteNpmInstall=false -Dpath=resources-<your-version>.jar
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Руками
|
||||||
|
|
||||||
|
1. Измените версию платформы и backend модуля в файле [pom.xml](pom.xml) вашего проекта на нужную версию
|
||||||
|
2. Скопируйте ресурсы
|
||||||
|
```
|
||||||
|
из директории: webbpm-platform\components\resources\target\classes\
|
||||||
|
в директорию: {your-project}\packages\ru.cg.webbpm.packages.base.resources\
|
||||||
|
```
|
||||||
|
3. Скопируйте фронт
|
||||||
|
```
|
||||||
|
из директории: webbpm-platform\components\frontend\dist
|
||||||
|
в директорию: {your-project}\frontend\node_modules\@webbpm\base-package\
|
||||||
|
```
|
||||||
|
4. Запретите выполнение npm install при запуске студии. Для этого добавьте параметр `-DexecuteNpmInstall=false` в настройках Run/Debug Configurations студии
|
||||||
394
backend/pom.xml
Normal file
|
|
@ -0,0 +1,394 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>ervu_lkrp_ul</groupId>
|
||||||
|
<artifactId>ervu_lkrp_ul</artifactId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<groupId>ervu_lkrp_ul.ervu_lkrp_ul</groupId>
|
||||||
|
<artifactId>backend</artifactId>
|
||||||
|
<packaging>war</packaging>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<!--JWT-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.security</groupId>
|
||||||
|
<artifactId>spring-security-jwt</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>io.jsonwebtoken</groupId>
|
||||||
|
<artifactId>jjwt-impl</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>ervu_lkrp_ul.ervu_lkrp_ul</groupId>
|
||||||
|
<artifactId>resources</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>ru.cg.webbpm.modules.reporting.reporting-jasper</groupId>
|
||||||
|
<artifactId>reporting-jasper-fonts</artifactId>
|
||||||
|
<scope>runtime</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.ocpsoft.prettytime</groupId>
|
||||||
|
<artifactId>prettytime</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.jooq</groupId>
|
||||||
|
<artifactId>jooq</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.santuario</groupId>
|
||||||
|
<artifactId>xmlsec</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>javax.servlet</groupId>
|
||||||
|
<artifactId>javax.servlet-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.slf4j</groupId>
|
||||||
|
<artifactId>slf4j-api</artifactId>
|
||||||
|
<scope>provided</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-context</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-beans</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-aop</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-jdbc</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-tx</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-aspects</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework</groupId>
|
||||||
|
<artifactId>spring-webmvc</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- security -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.security</groupId>
|
||||||
|
<artifactId>spring-security-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.security</groupId>
|
||||||
|
<artifactId>spring-security-config</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>ru.cg.webbpm.modules</groupId>
|
||||||
|
<artifactId>inject</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- bpmn -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>ru.cg.webbpm.modules.bpmn</groupId>
|
||||||
|
<artifactId>bpmn-workflow-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ru.cg.webbpm.modules.bpmn</groupId>
|
||||||
|
<artifactId>bpmn-workflow-jbpm-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ru.cg.webbpm.modules.bpmn</groupId>
|
||||||
|
<artifactId>bpmn-workflow-jbpm</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ru.cg.webbpm.modules.bpmn</groupId>
|
||||||
|
<artifactId>bpmn-beans</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ru.cg.webbpm.modules.bpmn</groupId>
|
||||||
|
<artifactId>bpmn-variable-condition-adapter</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ru.cg.webbpm.modules.bpmn</groupId>
|
||||||
|
<artifactId>bpmn-deploy</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>ru.cg.webbpm.modules</groupId>
|
||||||
|
<artifactId>webkit-rpc</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ru.cg.webbpm.modules</groupId>
|
||||||
|
<artifactId>webkit-beans</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>ru.cg.webbpm.modules.core</groupId>
|
||||||
|
<artifactId>core-runtime-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ru.cg.webbpm.modules.resources</groupId>
|
||||||
|
<artifactId>resources-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ru.cg.webbpm.modules.core</groupId>
|
||||||
|
<artifactId>error-handling-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- database asset-->
|
||||||
|
<dependency>
|
||||||
|
<groupId>ru.cg.webbpm.modules.database</groupId>
|
||||||
|
<artifactId>database-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ru.cg.webbpm.modules.database</groupId>
|
||||||
|
<artifactId>database-impl</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>ru.cg.webbpm.modules.jndi</groupId>
|
||||||
|
<artifactId>jndi-beans</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ru.cg.webbpm.modules.jndi</groupId>
|
||||||
|
<artifactId>jndi-inject</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- packaged -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.sun.mail</groupId>
|
||||||
|
<artifactId>javax.mail</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>ru.cg.webbpm.modules.database</groupId>
|
||||||
|
<artifactId>database-test</artifactId>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>ru.cg.webbpm.modules</groupId>
|
||||||
|
<artifactId>standard-annotations</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ru.cg.webbpm.modules.core</groupId>
|
||||||
|
<artifactId>metrics</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ru.cg.webbpm.modules.webkit</groupId>
|
||||||
|
<artifactId>active-users-tracker</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ru.cg.webbpm.modules.security</groupId>
|
||||||
|
<artifactId>security-beans</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ru.cg.webbpm.modules.security</groupId>
|
||||||
|
<artifactId>security-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ru.cg.webbpm.modules.security</groupId>
|
||||||
|
<artifactId>security-esia</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<!-- reporting -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>ru.cg.webbpm.modules.reporting</groupId>
|
||||||
|
<artifactId>reporting-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ru.cg.webbpm.modules.reporting</groupId>
|
||||||
|
<artifactId>reporting-runtime-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ru.cg.webbpm.modules.reporting</groupId>
|
||||||
|
<artifactId>reporting-runtime-impl</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ru.cg.webbpm.modules.reporting.reporting-jasper</groupId>
|
||||||
|
<artifactId>reporting-jasper-impl</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ru.cg.webbpm.modules.reporting.reporting-jasper</groupId>
|
||||||
|
<artifactId>reporting-jasper-runtime-impl</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ru.cg.webbpm.modules.reporting.reporting-xdoc</groupId>
|
||||||
|
<artifactId>reporting-xdoc-impl</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ru.cg.webbpm.modules.reporting.reporting-xdoc</groupId>
|
||||||
|
<artifactId>reporting-xdoc-runtime-impl</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.liquibase</groupId>
|
||||||
|
<artifactId>liquibase-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ru.cg.webbpm.modules</groupId>
|
||||||
|
<artifactId>webkit-base</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>ru.cg.webbpm.modules.security</groupId>
|
||||||
|
<artifactId>security-db-synchronization-api</artifactId>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ru.cg.webbpm.modules.security</groupId>
|
||||||
|
<artifactId>security-db-synchronization-ldap-impl</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>xerces</groupId>
|
||||||
|
<artifactId>xercesImpl</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>com.google.guava</groupId>
|
||||||
|
<artifactId>guava</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>ru.micord.fias</groupId>
|
||||||
|
<artifactId>client</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.tika</groupId>
|
||||||
|
<artifactId>tika-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.security.kerberos</groupId>
|
||||||
|
<artifactId>spring-security-kerberos-core</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.springframework.security.kerberos</groupId>
|
||||||
|
<artifactId>spring-security-kerberos-web</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.bouncycastle</groupId>
|
||||||
|
<artifactId>bcprov-jdk15on</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.bouncycastle</groupId>
|
||||||
|
<artifactId>bcpkix-jdk15on</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.mnode.ical4j</groupId>
|
||||||
|
<artifactId>ical4j</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.javacrumbs.shedlock</groupId>
|
||||||
|
<artifactId>shedlock-spring</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
|
<dependency>
|
||||||
|
<groupId>net.javacrumbs.shedlock</groupId>
|
||||||
|
<artifactId>shedlock-provider-jdbc-template</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<!-- false means true due https://issues.apache.org/jira/browse/MCOMPILER-209 -->
|
||||||
|
<useIncrementalCompilation>false</useIncrementalCompilation>
|
||||||
|
<forceJavacCompilerUse>true</forceJavacCompilerUse>
|
||||||
|
<release>17</release>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.codehaus.mojo</groupId>
|
||||||
|
<artifactId>build-helper-maven-plugin</artifactId>
|
||||||
|
<version>3.2.0</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>add-source</id>
|
||||||
|
<phase>generate-sources</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>add-source</goal>
|
||||||
|
</goals>
|
||||||
|
<configuration>
|
||||||
|
<sources>
|
||||||
|
<source>${project.basedir}/target/generated-sources/java</source>
|
||||||
|
</sources>
|
||||||
|
</configuration>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<profiles>
|
||||||
|
<profile>
|
||||||
|
<id>studio</id>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ru.cg.webbpm.modules.resources</groupId>
|
||||||
|
<artifactId>resources-impl-development</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-war-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<warName>${project.artifactId}</warName>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</profile>
|
||||||
|
<profile>
|
||||||
|
<id>dev</id>
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.postgresql</groupId>
|
||||||
|
<artifactId>postgresql</artifactId>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</profile>
|
||||||
|
</profiles>
|
||||||
|
</project>
|
||||||
65
backend/src/main/java/AppConfig.java
Normal file
|
|
@ -0,0 +1,65 @@
|
||||||
|
import java.time.Duration;
|
||||||
|
import javax.sql.DataSource;
|
||||||
|
|
||||||
|
import net.javacrumbs.shedlock.core.LockProvider;
|
||||||
|
import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider;
|
||||||
|
import net.javacrumbs.shedlock.spring.ScheduledLockConfiguration;
|
||||||
|
import net.javacrumbs.shedlock.spring.ScheduledLockConfigurationBuilder;
|
||||||
|
|
||||||
|
import org.springframework.beans.factory.annotation.Qualifier;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.ComponentScan;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
||||||
|
import org.springframework.context.support.PropertySourcesPlaceholderConfigurer;
|
||||||
|
import org.springframework.scheduling.annotation.EnableScheduling;
|
||||||
|
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
|
||||||
|
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Root application context
|
||||||
|
* This context imports XML configs from all the other jars, and is created by {@link WebAppInitializer}
|
||||||
|
* NB: modules are excluded from component scan since spring-context.xml sometimes holds important parameters and / or annotations
|
||||||
|
* @author krylov
|
||||||
|
*/
|
||||||
|
@Configuration
|
||||||
|
@ComponentScan(basePackages = {
|
||||||
|
"service",
|
||||||
|
"dao",
|
||||||
|
"bpmn",
|
||||||
|
"i18n",
|
||||||
|
"errorhandling",
|
||||||
|
"database",
|
||||||
|
"security",
|
||||||
|
"component.addresses",
|
||||||
|
"gen",
|
||||||
|
"ru.cg",
|
||||||
|
"ru.micord"
|
||||||
|
})
|
||||||
|
@EnableAspectJAutoProxy(proxyTargetClass = true)
|
||||||
|
@EnableWebMvc
|
||||||
|
@EnableScheduling
|
||||||
|
public class AppConfig {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public PropertySourcesPlaceholderConfigurer propertySourcesPlaceholderConfigurer(){
|
||||||
|
return new PropertySourcesPlaceholderConfigurer();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public ScheduledLockConfiguration taskScheduler(LockProvider lockProvider) {
|
||||||
|
ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler();
|
||||||
|
scheduler.setPoolSize(12);
|
||||||
|
scheduler.initialize();
|
||||||
|
return ScheduledLockConfigurationBuilder
|
||||||
|
.withLockProvider(lockProvider)
|
||||||
|
.withTaskScheduler(scheduler)
|
||||||
|
.withDefaultLockAtMostFor(Duration.ofHours(4))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public LockProvider lockProvider(@Qualifier("datasource") DataSource dataSource) {
|
||||||
|
return new JdbcTemplateLockProvider(dataSource);
|
||||||
|
}
|
||||||
|
}
|
||||||
31
backend/src/main/java/WebAppInitializer.java
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
import javax.servlet.ServletContext;
|
||||||
|
import javax.servlet.ServletException;
|
||||||
|
|
||||||
|
import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;
|
||||||
|
import org.springframework.web.util.IntrospectorCleanupListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This initializer creates root context and registers dispatcher servlet
|
||||||
|
* Spring scans for initializers automatically
|
||||||
|
*/
|
||||||
|
public class WebAppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
|
||||||
|
|
||||||
|
public void onStartup(ServletContext servletContext) throws ServletException {
|
||||||
|
super.onStartup(servletContext);
|
||||||
|
servletContext.addListener(new IntrospectorCleanupListener());
|
||||||
|
}
|
||||||
|
|
||||||
|
protected String[] getServletMappings() {
|
||||||
|
return new String[]{"/"};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Class<?>[] getRootConfigClasses() {
|
||||||
|
return new Class[]{AppConfig.class};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected Class<?>[] getServletConfigClasses() {
|
||||||
|
return new Class[0];
|
||||||
|
}
|
||||||
|
}
|
||||||
34
backend/src/main/java/controller/ProfileController.java
Normal file
|
|
@ -0,0 +1,34 @@
|
||||||
|
package controller;
|
||||||
|
|
||||||
|
import dto.jivoprofile.JivoProfileDto;
|
||||||
|
import org.hsqldb.lib.StringUtil;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import ru.cg.webbpm.modules.security.api.model.User;
|
||||||
|
import ru.cg.webbpm.modules.security.api.service.UserService;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
public class ProfileController {
|
||||||
|
|
||||||
|
private UserService userService;
|
||||||
|
|
||||||
|
public ProfileController(UserService userService) {
|
||||||
|
this.userService = userService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequestMapping(value = "/profile/jivo/{userId}", method = RequestMethod.GET)
|
||||||
|
public JivoProfileDto profile(@PathVariable("userId") String userId) {
|
||||||
|
|
||||||
|
if (StringUtil.isEmpty(userId)) {
|
||||||
|
return new JivoProfileDto();
|
||||||
|
}
|
||||||
|
|
||||||
|
User user = userService.get(userId);
|
||||||
|
|
||||||
|
JivoProfileDto profileDto = new JivoProfileDto();
|
||||||
|
profileDto.setUsername(user.firstName());
|
||||||
|
profileDto.setEmail(user.email());
|
||||||
|
profileDto.setPhone(user.phone());
|
||||||
|
return profileDto;
|
||||||
|
}
|
||||||
|
}
|
||||||
35
backend/src/main/java/dto/jivoprofile/JivoProfileDto.java
Normal file
|
|
@ -0,0 +1,35 @@
|
||||||
|
package dto.jivoprofile;
|
||||||
|
|
||||||
|
import ru.cg.webbpm.modules.webkit.annotations.Model;
|
||||||
|
|
||||||
|
@Model
|
||||||
|
public class JivoProfileDto {
|
||||||
|
|
||||||
|
public String username;
|
||||||
|
public String email;
|
||||||
|
public String phone;
|
||||||
|
|
||||||
|
public String getUsername() {
|
||||||
|
return username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setUsername(String username) {
|
||||||
|
this.username = username;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getEmail() {
|
||||||
|
return email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setEmail(String email) {
|
||||||
|
this.email = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
public String getPhone() {
|
||||||
|
return phone;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setPhone(String phone) {
|
||||||
|
this.phone = phone;
|
||||||
|
}
|
||||||
|
}
|
||||||
0
backend/src/main/resources/.gitkeep
Normal file
749
config.md
Normal file
|
|
@ -0,0 +1,749 @@
|
||||||
|
# Описание параметров конфигурации сервера приложений
|
||||||
|
|
||||||
|
Файл /standalone/configuration/standalone.xml
|
||||||
|
|
||||||
|
## Общие
|
||||||
|
|
||||||
|
- `webbpm.security.login.case_sensitive`. Default value = true. Параметр отвечающий за чувствительность к регистру при обработке логина пользователя.
|
||||||
|
- true - Login и login, обрабатываются как разные логины.
|
||||||
|
- false - Login и login, обрабатывается как один и тот же логин.
|
||||||
|
- `webbpm.db.pool_size`. Нужен только для `webbpm.mode` = development.
|
||||||
|
- `webbpm.mode`. Режим работы приложения. Значения - development, production.
|
||||||
|
- development - в этом режиме используется соединение к БД проекта, заданное в Studio.
|
||||||
|
- production - в этом режиме используется соединение к БД проекта, заданное с помощью jndi имени.
|
||||||
|
|
||||||
|
## jBPM
|
||||||
|
|
||||||
|
- `com.arjuna.ats.arjuna.allowMultipleLastResources`
|
||||||
|
- `webbpm.jbpm.audit-log.disabled` - флаг, отвечающий за включение/отключение аудита jBPM
|
||||||
|
- `webbpm.jbpm.cleaner_cron` - cron расписание автоматической очистки БД JBPM, по умолчанию "-" т.е. выключен. Очищаются незавершенные процессы.
|
||||||
|
- `webbpm.jbpm.cleaner_timeout` - время, спустя которое процесс считается устаревшим. По умолчанию - 10 часов.
|
||||||
|
- `webbpm.jbpm.finished_process_cleaner_cron` - cron расписание автоматической очистки аудита процессов в БД JBPM, по умолчанию "-"
|
||||||
|
т.е. выключен
|
||||||
|
- `webbpm.jbpm.finished_process_cleaner_timeout` - время, спустя которое процесс считается устаревшим. По умолчанию - 10 часов.
|
||||||
|
|
||||||
|
Пример:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<property name="com.arjuna.ats.arjuna.allowMultipleLastResources" value="true"/>
|
||||||
|
<property name="webbpm.db.pool_size" value="5"/>
|
||||||
|
<property name="webbpm.mode" value="development"/>
|
||||||
|
```
|
||||||
|
|
||||||
|
### jBPM Runtime Strategy
|
||||||
|
|
||||||
|
Возможные варианты runtime strategy:
|
||||||
|
|
||||||
|
- SINGLETON
|
||||||
|
- PER_REQUEST (значение по умолчанию)
|
||||||
|
- PER_PROCESS_INSTANCE
|
||||||
|
- PER_CASE
|
||||||
|
|
||||||
|
Пример:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<property name="webbpm.jbpm.runtime_strategy" value="PER_PROCESS_INSTANCE"/>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Способ аутентификации
|
||||||
|
|
||||||
|
- authentication.method - способ аутентификации. Поддерживаемые способы аутентификации: form, kerberos, cert_over_db, cert_over_ldap
|
||||||
|
|
||||||
|
### По логину и паролю
|
||||||
|
|
||||||
|
Пример конфигурации:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<property name="authentication.method" value="form"/>
|
||||||
|
```
|
||||||
|
|
||||||
|
### По сертификату
|
||||||
|
|
||||||
|
- cert_over_db - проверка наличия пользователя в базе данных безопасности
|
||||||
|
- cert_over_ldap - проверка наличия пользователя в базе данных безопасности и в LDAP
|
||||||
|
|
||||||
|
Примеры:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<property name="authentication.method" value="cert_over_db"/>
|
||||||
|
<property name="authentication.method" value="cert_over_ldap"/>
|
||||||
|
```
|
||||||
|
|
||||||
|
Параметр способа аутентификации authentication.method должен быть также установлен на клиентской части приложения в app-config.json
|
||||||
|
|
||||||
|
Также для аутентификации по сертификату нужны свойства для хранилища сертификатов:
|
||||||
|
|
||||||
|
- certificate.keystore.location - путь до java key store. Key store - это хранилище доверенных сертификатов, с помощью которых можно проверить корневой сертификат. Сертификат устанавливается с помощью команды:
|
||||||
|
|
||||||
|
```text
|
||||||
|
keytool -importcert -alias myAlias -file Example.cer -keystore exampleKeyStore
|
||||||
|
```
|
||||||
|
|
||||||
|
- certificate.keystore.password - пароль для keystore, установленный при импорте сертификата
|
||||||
|
|
||||||
|
Примеры:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<property name="certificate.keystore.location" value="${jboss.home.dir}/mfc"/>
|
||||||
|
<property name="certificate.keystore.password" value="mfc_auth"/>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Kerberos
|
||||||
|
|
||||||
|
Получите от администратора Kerberos .keytab файл, из которого командой `klist -k http.keytab` можно получить список principal-ов
|
||||||
|
Проверить успешность авторизации principal-а можно командой `kinit -t -i http.keytab %principal%`.
|
||||||
|
В
|
||||||
|
случае успешной авторизации команда `klist` в качестве default principal которым проводилась авторизация.
|
||||||
|
После этого в standalone.xml поправить параметр `app.service-principal` на principal, которым успешно авторизовались. principal имеет формат: `HTTP/%hostname%@%REALM%`
|
||||||
|
|
||||||
|
Пример конфигурации:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<property name="app.service-principal" value="HTTP/oleg-rxserver.alt.dom@ALT.DOM"/>
|
||||||
|
<property name="app.keytab-location" value="${jboss.home.dir}/http.keytab"/>
|
||||||
|
<property name="authentication.method" value="kerberos"/>
|
||||||
|
```
|
||||||
|
|
||||||
|
- app.service-principal. Пример - HTTP/oleg-rxserver.alt.dom@ALT.DOM
|
||||||
|
- app.keytab-location - расположение keytab файла. Пример - ${jboss.home.dir}/
|
||||||
|
- http.keytab.
|
||||||
|
|
||||||
|
Также необходимо в настройках браузера на клиенте задать параметр `network.negotiate-auth.trusted-uris` задать значение `.%domain%`.
|
||||||
|
Пример:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
.ALT.DOM
|
||||||
|
```
|
||||||
|
|
||||||
|
### Ldap
|
||||||
|
|
||||||
|
Аутентификация происходит посредством логина и пароля синхронизированного пользователя Ldap.
|
||||||
|
Логин и пароль введенные в форму входа, будут проверены сервисом Ldap.
|
||||||
|
|
||||||
|
Пример конфигурации:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<property name="ldap.url" value="ldap://localhost:389"/>
|
||||||
|
<property name="ldap.base" value="dc=example,dc=org"/>
|
||||||
|
<property name="ldap.username" value="cn=admin,dc=example,dc=org"/>
|
||||||
|
<property name="ldap.password" value="admin"/>
|
||||||
|
<property name="authentication.method" value="form"/>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Настройка сервера приложений для работы с Kerberos
|
||||||
|
|
||||||
|
1. создать учетные записи в домене:
|
||||||
|
|
||||||
|
- тестовые для проверки работоспособности функционала (пользователи домена, почтовые ящики):
|
||||||
|
|
||||||
|
`User1 pass1 user1@example.com`
|
||||||
|
|
||||||
|
`User2 pass2 user2@example.com`
|
||||||
|
|
||||||
|
- сервисную для доступа сервиса(приложения) к MS AD - (пользователь домена - снять устаревание пароля, ограничение по времени действия, почтовый ящик):
|
||||||
|
|
||||||
|
`serviceUser servicePass serviceuser@example.com`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
2. завести учетную запись машины `appserver.machine.name` (fqdn-имя сервиса) в AD (вручную, как запись в computer) и в DNS (A-запись)
|
||||||
|
|
||||||
|
`appserver.machine.name 10.250.216.91`
|
||||||
|
|
||||||
|
3. сгенерировать keytab (утилита ktpass) для аутентификации сервисов(приложения) для единой точки входа - `serviceUser` - с именем test.file.name.keytab - привязав к ней пользователя serviceUser
|
||||||
|
|
||||||
|
например, так:
|
||||||
|
|
||||||
|
```
|
||||||
|
ktpass -princ http/appserverMachineName.example.com@example.com -mapuser example.com\serviceUser -pass "пароль_уз_serviceUser" -crypto All -ptype KRB5_NT_PRINCIPAL -out "путь_к_директории_выгрузки_файла\test.file.name.keytab"
|
||||||
|
```
|
||||||
|
|
||||||
|
##### Kerberos FAQ
|
||||||
|
|
||||||
|
- В случае, если авторизация не проходит и в логах сервера приложений присутствует следующий вывод:
|
||||||
|
|
||||||
|
```
|
||||||
|
2019-05-14 05:33:36,588 INFO [security.controller.KerberosAuthenticationController] (default task-3) Authentication request header Authorization not exists
|
||||||
|
2019-05-14 05:33:36,588 INFO [security.controller.KerberosAuthenticationController] (default task-3) Authentication object is not presented
|
||||||
|
```
|
||||||
|
|
||||||
|
необходимо проверить настройку браузера firefox `network.negotiate-auth.trusted-uris`, она должна соответствовать домену из principal-а.
|
||||||
|
Для этого в поисковую строку браузера вводим "about:config", в открывшемся окне нажимаем "accept with risk and continue", в поисковой строке открывшейся страницы ввести `network.negotiate-auth.trusted-uris`.
|
||||||
|
Пример: для principal-а `HTTP/oleg-rxserver.alt.dom@ALT.DOM` настройка в браузере должна быть `.alt.dom`, приложение в браузере должно открываться по `http:\\oleg-rxserver.alt.dom:8080\...`
|
||||||
|
|
||||||
|
- если в логах сервера приложений есть ошибка:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
2019-05-13 14:13:07,095 WARN [org.springframework.security.kerberos.web.authentication.SpnegoAuthenticationProcessingFilter] (default task-1) Negotiate Header was invalid: Negotiate TlRMTVNTUAABAAAAl4II4gAAAAAAAAAAAAAAAAAAAAAKAGNFAAAADw==: org.springframework.security.authentication.BadCredentialsException: Kerberos validation not successful
|
||||||
|
...
|
||||||
|
|
||||||
|
Caused by: java.security.PrivilegedActionException: GSSException: Defective token detected (Mechanism level: GSSHeader did not find the right tag)
|
||||||
|
at java.security.AccessController.doPrivileged(Native Method) [rt.jar:1.8.0_211]
|
||||||
|
at javax.security.auth.Subject.doAs(Subject.java:422) [rt.jar:1.8.0_211]
|
||||||
|
at org.springframework.security.kerberos.authentication.sun.SunJaasKerberosTicketValidator.validateTicket(SunJaasKerberosTicketValidator.java:68) [spring-security-kerberos-core-1.0.1.RELEASE.jar:1.0.1.RELEASE]
|
||||||
|
... 66 more
|
||||||
|
Caused by: GSSException: Defective token detected (Mechanism level: GSSHeader did not find the right tag)
|
||||||
|
at sun.security.jgss.GSSHeader.(GSSHeader.java:97) [rt.jar:1.8.0_211]
|
||||||
|
at sun.security.jgss.GSSContextImpl.acceptSecContext(GSSContextImpl.java:306) [rt.jar:1.8.0_211]
|
||||||
|
at sun.security.jgss.GSSContextImpl.acceptSecContext(GSSContextImpl.java:285) [rt.jar:1.8.0_211]
|
||||||
|
at org.springframework.security.kerberos.authentication.sun.SunJaasKerberosTicketValidatorKerberosValidateAction.run(SunJaasKerberosTicketValidator.java:170) [spring-security-kerberos-core-1.0.1.RELEASE.jar:1.0.1.RELEASE]
|
||||||
|
at org.springframework.security.kerberos.authentication.sun.SunJaasKerberosTicketValidatorKerberosValidateAction.run(SunJaasKerberosTicketValidator.java:153) [spring-security-kerberos-core-1.0.1.RELEASE.jar:1.0.1.RELEASE]
|
||||||
|
... 69 more
|
||||||
|
```
|
||||||
|
|
||||||
|
необходимо проверить правильность указанного в standalone.xml principal-а.
|
||||||
|
|
||||||
|
### Комбинации нескольких способов аутентификации
|
||||||
|
|
||||||
|
Приложение может обрабатывать запросы на несколько способов аутентификации. Для этого необходимо переичислить нужные профили через запятую.
|
||||||
|
Примеры:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<property name="authentication.method" value="form,cert_over_db"/>
|
||||||
|
<property name="authentication.method" value="form,cert_over_ldap"/>
|
||||||
|
<property name="authentication.method" value="kerberos,cert_over_db"/>
|
||||||
|
<property name="authentication.method" value="kerberos,cert_over_ldap"/>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Время жизни токенов аутентификации
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<property name="webbpm.security.access_token.duration.minutes" value="60"/>
|
||||||
|
<property name="webbpm.security.refresh_token.duration.days" value="30"/>
|
||||||
|
<property name="webbpm.security.session.active.count" value="2"/>
|
||||||
|
```
|
||||||
|
|
||||||
|
`webbpm.security.access_token.duration.minutes` - опциональный параметр (значение по умолчанию 60), время жизни в минутах, сколько будет действителен токен, после истечения этого времени токен будет обновлён `webbpm.security.refresh_token.duration.days` - опциональный параметр (значение по умолчанию 30), время жизни в днях, после истечения этого времени с последнего обновления, пользователю будет необходимо повторно войти `webbpm.security.session.active.count` - опциональный параметр (значение по умолчанию 1), количество сохраняемых в базу токенов обновления (количество активных сессий)
|
||||||
|
|
||||||
|
Примечания.
|
||||||
|
|
||||||
|
- Нельзя использовать одновременно профили cert_over_db c cert_over_ldap и kerberos с form.
|
||||||
|
|
||||||
|
## Синхронизация пользователей с LDAP
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<property name="ldap.auto.sync.enabled" value="true"/>
|
||||||
|
<property name="ldap.synchronizer.cron" value="0 0 * * * *"/>
|
||||||
|
<property name="ldap.url" value="ldap://localhost:389"/>
|
||||||
|
<property name="ldap.base" value="dc=alt,dc=dom"/>
|
||||||
|
<property name="ldap.username" value="uid=test,ou=People,dc=alt,dc=dom"/>
|
||||||
|
<property name="ldap.password" value="password"/>
|
||||||
|
<property name="webbpm.ldap.implementation" value="open-ldap"/>
|
||||||
|
```
|
||||||
|
|
||||||
|
Настройки подключения к LDAP:
|
||||||
|
|
||||||
|
- `ldap.auto.sync.enabled` - включает/отключает автоматическую синхронизацию с LDAP
|
||||||
|
- `ldap.synchronizer.cron` - cron расписание автоматической синхронизации с LDAP
|
||||||
|
- `ldap.url`. Пример - ldap://localhost:389
|
||||||
|
- `ldap.base`. Пример - dc=alt,dc=dom
|
||||||
|
- `ldap.username`. Пример - uid=test,ou=People,dc=alt,dc=dom
|
||||||
|
- `ldap.password`
|
||||||
|
- `webbpm.ldap.implementation`. Допускается два значения: open-ldap и active-directory.
|
||||||
|
|
||||||
|
## WEBGUARD
|
||||||
|
|
||||||
|
Для синхронизации пользователей в WEBGUARD и для корректной работы админки необходимо указать настройки соединения к REST API WEBGUARD
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<property name="webguard.url" value="http://wg-host:8081/security-manager"/>
|
||||||
|
<property name="webguard.user.login" value="wg-user"/>
|
||||||
|
<property name="webguard.user.password" value="wg-password"/>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Статистика
|
||||||
|
|
||||||
|
### jbpm hibernate statistics
|
||||||
|
|
||||||
|
Статистика hibernate jbpm доступна по jmx по пути `org.hibernate:type=Stats,name=jbpm`.
|
||||||
|
Полный список параметров можно посмотреть через jconsole.
|
||||||
|
|
||||||
|
По умолчанию включена, отключить можно настройкой `webbpm.jbpm.hibernate_statistics.enabled`:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<property name="webbpm.jbpm.hibernate_statistics.enabled" value="false"/>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Schedulers по очистке базы *jbpm*.
|
||||||
|
|
||||||
|
- Очистка таблиц аудита от завершенных процессов по истечении таймаута
|
||||||
|
|
||||||
|
- `webbpm.jbpm.finished_process_cleaner_cron` - задает расписание (*spring cron*), если настройка не задана - джоб не запускается
|
||||||
|
- `webbpm.jbpm.finished_process_cleaner_timeout` - таймаут в часах (*целое числовое значение*)
|
||||||
|
|
||||||
|
Пример:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<property name="webbpm.jbpm.finished_process_cleaner_cron" value="0 * * * * *"/>
|
||||||
|
<property name="webbpm.jbpm.finished_process_cleaner_timeout" value="2"/>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Ограничения для запросов БД.
|
||||||
|
|
||||||
|
- `webbpm.db.query_limit_enabled` - флаг, отвечающий за вывод сообщений о превышении лимитов на количество возвращаемых записей. По умолчанию - false.
|
||||||
|
|
||||||
|
- `webbpm.db.select_records_max_limit` - максимальный лимит возвращаемых строк для запросов в БД, при превышении/равенстве данного лимита будет выброшена ошибка (*целое числовое значение*). По умолчанию - 100000
|
||||||
|
|
||||||
|
- `webbpm.db.select_records_min_limit` - минимальный лимит возвращаемых строк для запросов в БД, при превышении/равенстве данного лимита в логи будет выведен warning (*целое числовое значение*). По умолчанию - 1000
|
||||||
|
|
||||||
|
- `webbpm.db.execution_time_threshold`. The threshold for time of executing a statement. Система выводит сообщение в логи при превышении. Действует для запросов, созданных в jOOQ. По умолчанию - 1000 миллисекунд
|
||||||
|
|
||||||
|
- `webbpm.db.result_read_time_threshold`. The threshold for time of fetching a set of records from a ResultSet.
|
||||||
|
. Система выводит сообщение в логи при превышении. Действует для запросов, созданных в jOOQ. По умолчанию - 50 миллисекунд
|
||||||
|
|
||||||
|
- `webbpm.db.results_count_threshold`. Система выводит сообщение в логи при превышении. Действует для запросов, созданных в jOOQ. По умолчанию - 1000 записей
|
||||||
|
|
||||||
|
- `webbpm.db.full_time_threshold`. Ограничение на полное время выполнения запроса. Система выводит сообщение в логи при превышении. Действует для запросов, созданных в jOOQ. По умолчанию sum(webbpm.db.result_read_time_threshold, webbpm.db.execution_time_threshold) миллисекунд
|
||||||
|
|
||||||
|
- `webbpm.db.query_timeout`. Ограничение на время выполнения запроса. При превышении запрос будет отклонен. Действует для запросов, созданных в jOOQ в dev режиме. По умолчанию 120 секунд.
|
||||||
|
|
||||||
|
Пример:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<property name="webbpm.db.query_limit_enabled" value="false"/>
|
||||||
|
<property name="webbpm.db.select_records_max_limit" value="100000"/>
|
||||||
|
<property name="webbpm.db.select_records_min_limit" value="1000"/>
|
||||||
|
<property name="webbpm.db.execution_time_threshold" value="1000"/>
|
||||||
|
<property name="webbpm.db.results_count_threhold" value="50"/>
|
||||||
|
<property name="webbpm.db.result_read_time_threshold" value="50"/>
|
||||||
|
<property name="webbpm.db.full_time_threshold" value="300"/>
|
||||||
|
<property name="webbpm.db.query_timeout" value="120"/>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Добавление версии приложения в URL при запросах к backend-у
|
||||||
|
|
||||||
|
При сборке приложения с профилем enable-version-in-url в URL будет добавляться версия приложения, указанная в pom.xml.
|
||||||
|
Шаблон URL:
|
||||||
|
|
||||||
|
```
|
||||||
|
<protocol>//<hostname>:<port>/backend<app-version>/<some-controller>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Включение регистрации пользователя
|
||||||
|
|
||||||
|
1. Укажите конфигурацию почтового сервера для отправки писем с подтверждением регистрации.
|
||||||
|
Для этого в файле проекта *jndi-resources.xml* добавьте ресурс SmtpConfiguration.
|
||||||
|
Шаблон:
|
||||||
|
```
|
||||||
|
<jndi-resource name="java:comp/env/webbpm/testResource" type="bpmn.handler.common.SmtpConfiguration">{"host":"host","port":1234,"login":"user","password":"password","from":"email_from","senderName":"sender_name","isSecured":true}</jndi-resource>
|
||||||
|
```
|
||||||
|
Почтовый сервер - зарегистрированный актуальный почтовый адрес. В поле password нужно указывать не пароль для входа в почту, а создать пароль для приложений в учетке почты и указать его.
|
||||||
|
2. Для включения регистрации добавьте в *standalone.xml* свойство
|
||||||
|
```
|
||||||
|
<property name="registration.enabled" value="true"/>
|
||||||
|
```
|
||||||
|
3. Также в *standalone.xml* укажите ресурс для отправки писем для подтверждения регистрации (из п.1)
|
||||||
|
```
|
||||||
|
<property name="mail.jndi.resource.name" value="java:comp/env/webbpm/testResource"/>
|
||||||
|
```
|
||||||
|
4. При необходимости, отредактируйте шаблон письма для подтверждения регистрации
|
||||||
|
(resources/src/main/resources/mail/confirmation.html)
|
||||||
|
|
||||||
|
5. При необходимости, отредактируйте шаблон письма для восстановления пароля
|
||||||
|
(resources/src/main/resources/mail/reset_password.html)
|
||||||
|
|
||||||
|
|
||||||
|
#### Настройка браузера для входа в систему с помощью Kerberos
|
||||||
|
|
||||||
|
1. Запустите браузер firefox.
|
||||||
|
2. В адресной строке введите about:config, нажать кнопку "я принимаю на себя риск"
|
||||||
|
3. С помощью поиска найдите параметр network.negotiate-auth.trusted-uris и в качестве значения ввести домен(например для домена example.com надо ввести .example.com)
|
||||||
|
4. Откройте в браузере приложение. Пример [http://app.example.com/](http://app.example.com/) . Приложение должно открыться без запроса логина/пароля
|
||||||
|
|
||||||
|
## Восстановление структуры БД
|
||||||
|
|
||||||
|
На основе БД проекта с помощью jOOQ генерируются Java классы для каждого объекта БД. Это происходит по нажатию кнопки Обновить на панели БД в студии. При необходимости можно сформировать DDL на основе данных классов. Пример класса для генерации DDL
|
||||||
|
|
||||||
|
```
|
||||||
|
package ru.cg.webbpm.test_project.db_beans;
|
||||||
|
|
||||||
|
import org.jooq.*;
|
||||||
|
import org.jooq.impl.*;
|
||||||
|
|
||||||
|
public class Main {
|
||||||
|
public static void main (String args []) {
|
||||||
|
DefaultConfiguration defaultConfiguration = new DefaultConfiguration();
|
||||||
|
defaultConfiguration.setSQLDialect(SQLDialect.POSTGRES);
|
||||||
|
Queries ddl = DSL.using(defaultConfiguration).ddl(DefaultCatalog.DEFAULT_CATALOG);
|
||||||
|
|
||||||
|
for (Query query : ddl.queries()) {
|
||||||
|
System.out.println(query);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
** ВНИМАНИЕ: **
|
||||||
|
|
||||||
|
- этим способом нельзя восстановить функции/процедуры БД
|
||||||
|
|
||||||
|
см. также [Generating DDL from objects](https://www.jooq.org/doc/latest/manual/sql-building/ddl-statements/generating-ddl/)
|
||||||
|
|
||||||
|
## Распределенный кэш (Hazelcast)
|
||||||
|
|
||||||
|
В платформе подключен кэш. Он используется для подсчёта количества пользователей и кэширования подсистемы безопасности
|
||||||
|
|
||||||
|
- `webbpm.cache.hazelcast.port` - входящий порт hazelcast. по дефолту 5701.
|
||||||
|
- Обязательное задать одно из двух следующих параметров
|
||||||
|
- `webbpm.cache.hazelcast.hosts` - список хостов серверов приложений.
|
||||||
|
webbpm.cache.hazelcast.hosts = hostname1,hostname2,hostname3
|
||||||
|
- `webbpm.cache.hazelcast.kubernetes.service_name` - имя сервиса в среде kubernetes,
|
||||||
|
используемый для обнаружения других подов. Подходит как стратегия обнаружения, если используется
|
||||||
|
kubernetes
|
||||||
|
- `webbpm.cache.hazelcast.outbound_port_definitions` - исходящие порты hazelcast. по дефолту не задано, система сама выбирает свободные порты. Задать диапазон 5801 - 5820
|
||||||
|
- `webbpm.cache.hazelcast.backup_count`. Нужны чтобы когда сервер выключается был доступен бекап. Если предполагается выключать несколько серверов за раз, то нужно увеличить.
|
||||||
|
Данный бекап делает копии синхронно с основной записью и операция записи ждет пока будет записана везде. Можно делать бекап асинхронно, настраивается через async_backup_count
|
||||||
|
подробнее про бекапы [Hazelcast IMDG Reference Manual](https://docs.hazelcast.org/docs/3.11/manual/html-single/index.html#backing-up-maps) . по дефолту 1
|
||||||
|
- `webbpm.cache.hazelcast.async_backup_count`
|
||||||
|
- `webbpm.cache.hazelcast.public_address`
|
||||||
|
- `webbpm.cache.hazelcast.interfaces`
|
||||||
|
|
||||||
|
Пример конфигурации:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<property name="webbpm.cache.hazelcast.hosts" value="app1,app2"/>
|
||||||
|
<property name="webbpm.cache.hazelcast.outbound_port_definitions" value="5801-5820"/>`
|
||||||
|
```
|
||||||
|
|
||||||
|
## Подключение компоненты адреса в режиме ГАР (Государственный адресный реестр)
|
||||||
|
|
||||||
|
Необходимо задать параметры:
|
||||||
|
|
||||||
|
- `gar.enable` - флаг, который включает/отключает сервис для работы с ГАР. Должен быть задан для работы компоненты в режиме ГАР. По умолчанию true, для отключения задать false.
|
||||||
|
- `gar.elastic.url.host` - хост на котором развернут elasticsearch.
|
||||||
|
- `gar.elastic.password` - пароль для аутентификации elasticsearch.
|
||||||
|
|
||||||
|
Дополнительные параметры:
|
||||||
|
|
||||||
|
- `gar.elastic.url.port` - порт на котором развернут elasticsearch.
|
||||||
|
- `gar.elastic.username` - логин для аутентификации elasticsearch.
|
||||||
|
|
||||||
|
Пример конфигурации:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<property name="gar.enable" value="true"/>
|
||||||
|
<property name="gar.elastic.url.host" value="localhost"/>
|
||||||
|
<property name="gar.elastic.password" value="password"/>
|
||||||
|
```
|
||||||
|
|
||||||
|
## Метрики
|
||||||
|
|
||||||
|
Отчет
|
||||||
|
|
||||||
|
Отчет собирается раз в 30 секунд по дефолту, меняется параметром `webbpm.metrics.report_period_ms`.
|
||||||
|
Все метрики идут за отчетный период, после сбора отчета они сбрасываются.
|
||||||
|
|
||||||
|
Получить json со всеми метриками можно по урлу `backend/metrics/v1/all` - метрики будут с последнего собранного отчета, запрос не триггерит сбор отчета - это независимые операции. Отчет содержит только метрики по которым был совершен хотя бы один вызов в отчетный период. То есть, если какая-то операция не была совершена в отчетный период, то соответствующая ей метрика не попадет в отчет - ее не будет в json.
|
||||||
|
|
||||||
|
### Значения метрик
|
||||||
|
|
||||||
|
Все метрики идут за отчетный период, после сбора отчета они сбрасываются.
|
||||||
|
Нас в основном интересуют `callsCountSum`, `latencyMin`, `latencyAvg`, `latencyMax`
|
||||||
|
|
||||||
|
- `callsCountSum` - количество завершенных вызовов
|
||||||
|
- `latencyMin` - минимальное время выполнения
|
||||||
|
- `latencyAvg` - среднее время выполнения
|
||||||
|
- `latencyMax` - максимальное время выполнения
|
||||||
|
- `activeCallsCountMax` - количество начатых, но еще не завершенных вызовов.
|
||||||
|
- `activeCallsLatencyMax` - длительность самого долгого еще не завершенного вызова
|
||||||
|
|
||||||
|
### Текущие метрики приложения
|
||||||
|
|
||||||
|
- Получение коннекта из пула
|
||||||
|
|
||||||
|
- `webbpm.jbpm.db.connection.acquire`
|
||||||
|
- `webbpm.security.db.connection.acquire`
|
||||||
|
- `webbpm.db.connection.acquire`
|
||||||
|
|
||||||
|
- Время с момента получения коннекта до возврата его в пул
|
||||||
|
|
||||||
|
- `webbpm.jbpm.db.connection.in_use`
|
||||||
|
- `webbpm.security.db.connection.in_use`
|
||||||
|
- `webbpm.db.connection.in_use`
|
||||||
|
|
||||||
|
- Время выполнения запроса на бд проекта
|
||||||
|
|
||||||
|
- `webbpm.db.query.success.execution_time`
|
||||||
|
|
||||||
|
- Время выполнения запроса на бд проекта + время получения коннекта из пула
|
||||||
|
|
||||||
|
- `webbpm.db.query.success.full_time`
|
||||||
|
|
||||||
|
- active-users-count.indicatorMax
|
||||||
|
|
||||||
|
- active-users-count-ttl.indicatorMax
|
||||||
|
|
||||||
|
# Количество пользователей
|
||||||
|
|
||||||
|
- `webbpm.active_users_counter.enabled` - включает подсчет пользователей, нужно чтобы не запускать hazelcast на дев машинах. по дефолту false. На боевых серверах необходимо установить в true.
|
||||||
|
|
||||||
|
- `webbpm.active_users_counter.max_time_between_operations_in_seconds` - время, которое пользователь считается активным после действия. по дефолту 15 минут.
|
||||||
|
|
||||||
|
- `webbpm.active_users_counter.hazelcast.app_pool_size`. Запись в hazelcast производится асинхронно в отдельном пуле, не блокируя обработку http запроса. Это размер этого пула. по дефолту 4. Можно пока оставить 4 и последить за метриками pool.hazelcast-executor.queue.indicatorMax и pool.hazelcast-executor.activeThreads.indicatorMax. Если очередь будет сильно копиться, то увеличить.
|
||||||
|
|
||||||
|
## Настройка логов
|
||||||
|
|
||||||
|
Все настройки делаются в файле `standalone.xml`, если не указано иначе.
|
||||||
|
|
||||||
|
### Общие настройки
|
||||||
|
|
||||||
|
Платформа Web-bpm использует корневую категорию логирования `ru.cg.webbpm`, рекомендуется выставлять ее в уровень `info`. todo check prod config
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<logger category="ru.cg.webbpm">
|
||||||
|
<level name="INFO"/>
|
||||||
|
</logger>
|
||||||
|
```
|
||||||
|
|
||||||
|
При этом компоненты используемые в проекте могут использовать другие категории.
|
||||||
|
|
||||||
|
### Параметры конфигурации
|
||||||
|
|
||||||
|
**Рекомендованное использование:** всегда в `info`.
|
||||||
|
|
||||||
|
Все параметры конфигурации загружаемые платформой web-bpm и пользовательским приложением через api webbpm логируются категорией `ru.cg.webbpm.modules.core.app_info.api.property.BaseProperty`. Она всегда должна быть выставлена в `info`.
|
||||||
|
|
||||||
|
Пример вывода:
|
||||||
|
|
||||||
|
```
|
||||||
|
2017-12-04 16:02:19,074 INFO [ru.cg.webbpm.modules.core.app_info.api.property.BaseProperty] (EclipseGeminiBlueprintExtenderThread-1) System property [webbpm.active_users_counter.enabled] not set. Using default value [false]
|
||||||
|
2017-12-04 16:02:19,074 INFO [ru.cg.webbpm.modules.core.app_info.api.property.BaseProperty] (EclipseGeminiBlueprintExtenderThread-1) System property [webbpm.active_users_counter.hazelcast.hosts] set to [127.0.0.1]
|
||||||
|
```
|
||||||
|
|
||||||
|
### БД проекта
|
||||||
|
|
||||||
|
#### Логирование запросов в бд security и бд проекта
|
||||||
|
|
||||||
|
**Рекомендованное использование:** только при разработке.
|
||||||
|
|
||||||
|
Использовать только при разработке. Категория `org.jooq.tools.LoggerListener` в `debug` уровень.
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<logger category="org.jooq.tools.LoggerListener">
|
||||||
|
<level name="DEBUG"/>
|
||||||
|
</logger>
|
||||||
|
```
|
||||||
|
|
||||||
|
Пример вывода:
|
||||||
|
|
||||||
|
```
|
||||||
|
2017-12-04 18:21:10,391 DEBUG [org.jooq.tools.LoggerListener] (default task-19) Executing query : select "department"."department_name", "department"."department_id", "department"."parent_department_id", "department"."parent_department_id", "department"."department_id", (select (count(*) <> ?) from "public"."department" as "$$child" where "$$child"."parent_department_id" = "department"."department_id") as "$$hasChildren" from "public"."department" as "department" limit ?
|
||||||
|
2017-12-04 18:21:10,395 DEBUG [org.jooq.tools.LoggerListener] (default task-19) -> with bind values : select "department"."department_name", "department"."department_id", "department"."parent_department_id", "department"."parent_department_id", "department"."department_id", (select (count(*) <> 0) from "public"."department" as "$$child" where "$$child"."parent_department_id" = "department"."department_id") as "$$hasChildren" from "public"."department" as "department" limit 2147483647
|
||||||
|
2017-12-04 18:21:10,568 DEBUG [org.jooq.tools.LoggerListener] (default task-19) Fetched result : +-----------------+-------------+--------------------+--------------------+-------------+-------------+
|
||||||
|
2017-12-04 18:21:10,568 DEBUG [org.jooq.tools.LoggerListener] (default task-19) : |department_name |department_id|parent_department_id|parent_department_id|department_id|$$hasChildren|
|
||||||
|
2017-12-04 18:21:10,568 DEBUG [org.jooq.tools.LoggerListener] (default task-19) : +-----------------+-------------+--------------------+--------------------+-------------+-------------+
|
||||||
|
2017-12-04 18:21:10,568 DEBUG [org.jooq.tools.LoggerListener] (default task-19) : |Головной офис 1 | 8| {null}| {null}| 8|true |
|
||||||
|
2017-12-04 18:21:10,568 DEBUG [org.jooq.tools.LoggerListener] (default task-19) : |Головной офис 2 | 9| {null}| {null}| 9|true |
|
||||||
|
2017-12-04 18:21:10,568 DEBUG [org.jooq.tools.LoggerListener] (default task-19) : |Головной офис 3 | 10| {null}| {null}| 10|true |
|
||||||
|
2017-12-04 18:21:10,568 DEBUG [org.jooq.tools.LoggerListener] (default task-19) : |Подразделение 1.1| 11| 8| 8| 11|true |
|
||||||
|
2017-12-04 18:21:10,568 DEBUG [org.jooq.tools.LoggerListener] (default task-19) : |Подразделение 1.2| 12| 8| 8| 12|true |
|
||||||
|
2017-12-04 18:21:10,568 DEBUG [org.jooq.tools.LoggerListener] (default task-19) : +-----------------+-------------+--------------------+--------------------+-------------+-------------+
|
||||||
|
2017-12-04 18:21:10,568 DEBUG [org.jooq.tools.LoggerListener] (default task-19) : |...55 record(s) truncated...
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Логирование больших запросов
|
||||||
|
|
||||||
|
**Рекомендованное использование:** всегда в `info`, с подобранными для проекта значениями. В проде значения должны быть проверены что они не вызывают излишнее логирование.
|
||||||
|
|
||||||
|
1. Для отслеживания больших запросов в пользовательскую базу нужно задать параметры что считать большими запросами, логироваться будут запросы больше заданных показателей.
|
||||||
|
Чтобы логировать все запросы можно задать значение `-1`. Добавляются как property в раздел `system-properties`:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<property name="webbpm.db.full_time_threshold" value="1050"/>
|
||||||
|
<property name="webbpm.db.results_count_threshold" value="1000"/>
|
||||||
|
```
|
||||||
|
|
||||||
|
В `info` режиме работают:
|
||||||
|
|
||||||
|
- `webbpm.db.full_time_threshold` - полное время выполнения запроса в миллисекундах. Включает построение запроса библиотекой, выполнение и загрузку результатов. Пример сообщения <br>`2023-01-11 13:09:38,361 WARN [ru.cg.webbpm.modules.database.impl.analytics.PerformanceListener] (default task-33) Query full time threshold exceeded. full_time=[6565ms] execution_time=[6565ms] read_time=[0ms] results_count=[4] query=[<your query>]`
|
||||||
|
- `webbpm.db.results_count_threshold` - количество записей, которое вернул запрос. Пример сообщения <br>`2023-01-11 13:10:34,088 WARN [ru.cg.webbpm.modules.database.impl.analytics.PerformanceListener] (default task-41) Query results count threshold exceeded. results_count=[11177] query=[<your query>]`
|
||||||
|
|
||||||
|
В `debug` режиме дополнительно к работающим в info:
|
||||||
|
|
||||||
|
- `webbpm.db.execution_time_threshold` - время выполнения запроса + построения запроса библиотекой.
|
||||||
|
- `webbpm.db.result_read_time_threshold` - время чтения результатов запроса
|
||||||
|
|
||||||
|
2. После задания настроек нужно настроить логирование - категория `ru.cg.webbpm.modules.database.impl.analytics.PerformanceListener`, уровень `info` или `debug`.
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<logger category="ru.cg.webbpm.modules.database.impl.analytics.PerformanceListener">
|
||||||
|
<level name="INFO"/>
|
||||||
|
</logger>
|
||||||
|
```
|
||||||
|
|
||||||
|
### JBPM
|
||||||
|
|
||||||
|
#### Основные настройки
|
||||||
|
|
||||||
|
JBPM использует 3 корневых категории логирования – `org.jbpm`, `org.drools`, `org.kie`. Все они должны быть выставлены в `warn`.
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<logger category="org.jbpm">
|
||||||
|
<level name="WARN"/>
|
||||||
|
</logger>
|
||||||
|
<logger category="org.drools">
|
||||||
|
<level name="WARN"/>
|
||||||
|
</logger>
|
||||||
|
<logger category="org.kie">
|
||||||
|
<level name="WARN"/>
|
||||||
|
</logger>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Логирование запросов в БД
|
||||||
|
|
||||||
|
**Рекомендованное использование:** только при разработке.
|
||||||
|
|
||||||
|
Для логирования sql запросов нужно включить категорию `org.hibernate.SQL` в `debug`:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<logger category="org.hibernate.SQL">
|
||||||
|
<level name="DEBUG"/>
|
||||||
|
</logger>
|
||||||
|
```
|
||||||
|
|
||||||
|
Чтобы вывести параметры запросов и результат выполнения – категорию `org.hibernate.type.descriptor.sql` в `trace`:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<logger category="org.hibernate.type.descriptor.sql">
|
||||||
|
<level name="TRACE"/>
|
||||||
|
</logger>
|
||||||
|
```
|
||||||
|
|
||||||
|
Пример вывода:
|
||||||
|
|
||||||
|
```
|
||||||
|
18:21:06,938 DEBUG [org.hibernate.SQL] (default task-47) select names0_.Task_Names_Id as Task_Nam7_16_0_, names0_.id as id1_16_0_, names0_.id as id1_16_1_, names0_.language as language2_16_1_, names0_.shortText as shortTex3_16_1_, names0_.text as text4_16_1_ from I18NText names0_ where names0_.Task_Names_Id=?
|
||||||
|
18:21:06,939 TRACE [org.hibernate.type.descriptor.sql.BasicBinder] (default task-47) binding parameter [1] as [BIGINT] - [180]
|
||||||
|
18:21:06,940 TRACE [org.hibernate.type.descriptor.sql.BasicExtractor] (default task-47) extracted value ([id1_16_1_] : [BIGINT]) - [539]
|
||||||
|
18:21:06,940 TRACE [org.hibernate.type.descriptor.sql.BasicExtractor] (default task-47) extracted value ([language2_16_1_] : [VARCHAR]) - [en-UK]
|
||||||
|
18:21:06,940 TRACE [org.hibernate.type.descriptor.sql.BasicExtractor] (default task-47) extracted value ([shortTex3_16_1_] : [VARCHAR]) - [Список записей]
|
||||||
|
18:21:06,941 TRACE [org.hibernate.type.descriptor.sql.BasicExtractor] (default task-47) extracted value ([text4_16_1_] : [CLOB]) - [Список записей]
|
||||||
|
18:21:06,941 TRACE [org.hibernate.type.descriptor.sql.BasicExtractor] (default task-47) extracted value ([Task_Nam7_16_0_] : [BIGINT]) - [180]
|
||||||
|
18:21:06,942 TRACE [org.hibernate.type.descriptor.sql.BasicExtractor] (default task-47) extracted value ([id1_16_0_] : [BIGINT]) - [539]
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Дополнительные логи hibernate
|
||||||
|
|
||||||
|
**Рекомендованное использование:** только при разработке в случае необходимости.
|
||||||
|
|
||||||
|
1. Время выполнения запроса и количество результатов.
|
||||||
|
|
||||||
|
Включаются категорией `org.hibernate.stat` в `debug`. При этом в hibernate должен быть включен сбор статистики.
|
||||||
|
Похоже что логируется только hql select запросов.
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<logger category="org.hibernate.stat">
|
||||||
|
<level name="DEBUG"/>
|
||||||
|
</logger>
|
||||||
|
```
|
||||||
|
|
||||||
|
Пример вывода:
|
||||||
|
|
||||||
|
```
|
||||||
|
18:21:06,858 DEBUG [org.hibernate.stat.internal.ConcurrentStatisticsImpl] (default task-41) HHH000117: HQL: select t from AuditTaskImpl t where t.taskId = :taskId, time: 6ms, rows: 1
|
||||||
|
```
|
||||||
|
|
||||||
|
2. Показатели hibernate сессий
|
||||||
|
|
||||||
|
Включаются категорией `org.hibernate.engine.internal.StatisticalLoggingSessionEventListener` в `info`.
|
||||||
|
При этом в hibernate должен быть включен сбор статистики. Тут может быть интересно количество запросов, флашей и общее время на все запросы сессией.
|
||||||
|
|
||||||
|
Пример вывода:
|
||||||
|
|
||||||
|
```
|
||||||
|
2017-12-04 17:25:58,493 INFO [org.hibernate.engine.internal.StatisticalLoggingSessionEventListener] (default task-21) Session Metrics {
|
||||||
|
63408365 nanoseconds spent acquiring 17 JDBC connections;
|
||||||
|
521509 nanoseconds spent releasing 17 JDBC connections;
|
||||||
|
65732621 nanoseconds spent preparing 17 JDBC statements;
|
||||||
|
31471897 nanoseconds spent executing 17 JDBC statements;
|
||||||
|
0 nanoseconds spent executing 0 JDBC batches;
|
||||||
|
0 nanoseconds spent performing 0 L2C puts;
|
||||||
|
0 nanoseconds spent performing 0 L2C hits;
|
||||||
|
0 nanoseconds spent performing 0 L2C misses;
|
||||||
|
297556 nanoseconds spent executing 1 flushes (flushing a total of 16 entities and 0 collections);
|
||||||
|
430168 nanoseconds spent executing 1 partial-flushes (flushing a total of 16 entities and 16 collections)
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Логирование hbm2ddl
|
||||||
|
|
||||||
|
**Рекомендованное использование:** всегда в `trace`.
|
||||||
|
|
||||||
|
Должно быть включено всегда. Позволяет убедиться что hibernate не накатывал никакие миграции на базу. Этот функционал отключен у нас в коде.
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<logger category="org.hibernate.tool.hbm2ddl">
|
||||||
|
<level name="TRACE"/>
|
||||||
|
</logger>
|
||||||
|
```
|
||||||
|
|
||||||
|
# Описание параметров конфигурации клиентской части
|
||||||
|
|
||||||
|
Свойства задаются в файле frontend/src/resources/app-config.json или frontend.war/src/resources/app-config.json
|
||||||
|
|
||||||
|
## Общие
|
||||||
|
|
||||||
|
- `dev_mode` - настройка задающая dev_mode для просмотра логов (true/false). При отсутствие оставляет значение при сборке
|
||||||
|
- `guard.confirm_exit` - выводить или нет диалог подтверждения, если обнаружены несохраненные данные в форме. Значение по умолчанию - false.
|
||||||
|
- `password.pattern` - Регулярное выражение для валидации пароля.
|
||||||
|
- `password_pattern_error` - Сообщение об ошибке валидации.
|
||||||
|
- `show.client.errors` - отвечает за отображение ошибок javascript-a пользователю (должна использоваться только в тестовых контурах) по умолчанию выключена
|
||||||
|
- 'available_task.single_fetch' - Отвечает за количество запросов available_task при завершении процесса. true - одиночный запрос, false/не указано - 10 запросов(старая реализация).
|
||||||
|
|
||||||
|
## Вывод сообщений
|
||||||
|
|
||||||
|
- `message_service_error_timeout` время в мс, в течение которого будет отображено сообщение об ошибке. Значение по умолчанию - таймаут не задан (окно не закрывается).
|
||||||
|
- `message_service_warning_timeout` время в мс, в течение которого будет отображено предупреждающее сообщение. Значение по умолчанию - таймаут не задан (окно не закрывается).
|
||||||
|
- `message_service_success_timeout` время в мс, в течение которого будет отображено сообщение об успехе. Значение по умолчанию - таймаут не задан (окно не закрывается).
|
||||||
|
- `message_service_info_timeout` время в мс, в течение которого будет отображено информационное сообщение. Значение по умолчанию - таймаут не задан (окно не закрывается).
|
||||||
|
|
||||||
|
## Электронная подпись
|
||||||
|
|
||||||
|
### Esmart
|
||||||
|
|
||||||
|
- `electronic_sign.esmart_extension_url` - url для создания расширенной подписи. Подробная информация по ссылке [http://demo.esmart.ru](http://demo.esmart.ru)
|
||||||
|
- `electronic_sign.tsp_address` - адрес сервера службы штампов времени
|
||||||
|
|
||||||
|
Пример:
|
||||||
|
|
||||||
|
```text
|
||||||
|
"electronic_sign.esmart_extension_url": "http://dsig.ibsdemo.ru/ibs_dsig/ibs_dSig.asmx"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Способ аутентификации
|
||||||
|
|
||||||
|
- `auth_method` - способ аутентификации. Может принимать одно значение из списка: form, kerberos, cert_over_db, cert_over_ldap
|
||||||
|
|
||||||
|
## Таймер очистки закешированных значений фильтров
|
||||||
|
|
||||||
|
- `filter_cleanup_interval_hours` - время жизни закешированного значения фильтра в часах. По умолчанию - 720 часов,
|
||||||
|
- `filter_cleanup_check_period_minutes` - период проверки наличия просроченных закешированных значений в минутах. По умолчанию - 30 минут
|
||||||
|
|
||||||
|
## Добавление версии приложения в URL при запросах к frontend-у
|
||||||
|
|
||||||
|
В модуле frontend в src/resources/app-config.json добавлены 2 переменные
|
||||||
|
|
||||||
|
- `"enable.version.in.url": "%enable.version.in.url%"` - подставлять ли версию в URL приложения. По умолчанию false. Если сборка произведена
|
||||||
|
- `"app.version": "%project.version%"` - версия приложения.
|
||||||
|
с профилем `enable-version-in-url`, то значение будет true.
|
||||||
|
|
||||||
|
## Добавление Jivo чат в проект
|
||||||
|
|
||||||
|
Свойства задаются в файле frontend/src/resources/app-config.json или frontend.war/src/resources/app-config.json
|
||||||
|
|
||||||
|
- `jivo_chat_widget_api_url` - API url для работы Jivo чата. Необходимо заменить {WIDGET_ID} на реальный Widget API ID
|
||||||
|
- `jivo_chat_widget_enabled` - параметр отвечающий за активацию Jivo чата. По дефолту false, для активации задать true.
|
||||||
|
|
||||||
|
Пример:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"jivo_chat_widget_api_url": "https://code.jivo.ru/widget/{WIDGET_ID}",
|
||||||
|
"jivo_chat_widget_enabled": false
|
||||||
|
```
|
||||||
|
|
||||||
|
# Прочее
|
||||||
|
|
||||||
|
## Смена удалённого репозитория
|
||||||
|
|
||||||
|
1. Смените адрес NPM registry в файле frontend.npmrc. Пример - registry=https://repo.example.com/repository/npm-all/
|
||||||
|
2. Поменяйте ссылки в блоке , файла pom.xml
|
||||||
2
config/.gitignore
vendored
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
/*.ear
|
||||||
|
/*.jar
|
||||||
41
config/Dockerfile
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
FROM quay.io/wildfly/wildfly:26.1.3.Final-jdk17
|
||||||
|
USER root
|
||||||
|
ARG POSTGRES_DRIVER_VERSION=42.7.3
|
||||||
|
RUN yum -y install sudo && chown -R jboss: /opt/jboss/
|
||||||
|
|
||||||
|
USER jboss
|
||||||
|
WORKDIR $JBOSS_HOME
|
||||||
|
ENV JAVA_ARGS=-Xmx3g
|
||||||
|
RUN echo 'JAVA_OPTS="$JAVA_OPTS -agentlib:jdwp=transport=dt_socket,address=*:8787,server=y,suspend=n"' >> bin/standalone.conf && \
|
||||||
|
echo 'JAVA_OPTS="$JAVA_OPTS -XX:MaxMetaspaceSize=1g"' >> bin/standalone.conf && \
|
||||||
|
echo 'JAVA_OPTS="$JAVA_OPTS -XX:+UseStringDeduplication -XX:+ParallelRefProcEnabled -XX:+ExplicitGCInvokesConcurrent"' >> bin/standalone.conf && \
|
||||||
|
echo 'JAVA_OPTS="$JAVA_OPTS -XX:+UnlockDiagnosticVMOptions -XX:G1SummarizeRSetStatsPeriod=1"' >> bin/standalone.conf && \
|
||||||
|
echo 'JAVA_OPTS="$JAVA_OPTS -Dfile.encoding=UTF-8"' >> bin/standalone.conf && \
|
||||||
|
echo 'JAVA_OPTS="$JAVA_OPTS -XX:-OmitStackTraceInFastThrow"' >> bin/standalone.conf && \
|
||||||
|
echo 'JAVA_OPTS="$JAVA_OPTS -Dorg.jboss.logmanager.nocolor=true"' >> bin/standalone.conf && \
|
||||||
|
echo 'JAVA_OPTS="$JAVA_OPTS $JAVA_ARGS"' >> bin/standalone.conf
|
||||||
|
|
||||||
|
### Locale support ru_RU ###
|
||||||
|
USER root
|
||||||
|
RUN localedef -i ru_RU -f UTF-8 ru_RU.UTF-8
|
||||||
|
RUN echo "LANG=\"ru_RU.UTF-8\"" > /etc/locale.conf
|
||||||
|
USER jboss
|
||||||
|
ENV LANG ru_RU.UTF-8
|
||||||
|
ENV LANGUAGE ru_RU.UTF-8
|
||||||
|
ENV LC_ALL ru_RU.UTF-8
|
||||||
|
### Locale Support END ###
|
||||||
|
|
||||||
|
COPY --chown=jboss entrypoint.sh .
|
||||||
|
COPY --chown=jboss patches patches
|
||||||
|
RUN (cd patches && wget https://repo.micord.ru/repository/libs-releases-local/org/jboss/ironjacamar/ironjacamar-core-impl/1.5.3.Final/ironjacamar-core-impl-1.5.3.Final.jar)
|
||||||
|
RUN (cd patches/system && wget https://repo1.maven.org/maven2/org/postgresql/postgresql/$POSTGRES_DRIVER_VERSION/postgresql-$POSTGRES_DRIVER_VERSION.jar -O postgresql-driver.jar)
|
||||||
|
|
||||||
|
RUN chmod -R +x patches && \
|
||||||
|
chmod +x entrypoint.sh && \
|
||||||
|
./entrypoint.sh && \
|
||||||
|
rm -rf patches
|
||||||
|
|
||||||
|
ENV SERVER_START=true
|
||||||
|
COPY --chown=jboss *.ear $JBOSS_HOME/standalone/deployments/
|
||||||
|
|
||||||
|
HEALTHCHECK --timeout=3s --start-period=3600s CMD curl --fail 127.0.0.1:8080/backend/version || exit 1
|
||||||
10
config/Dockerfile.build
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
FROM maven:3-jdk-11-slim
|
||||||
|
RUN apt update \
|
||||||
|
&& apt upgrade -y \
|
||||||
|
&& curl -fsSL https://deb.nodesource.com/setup_14.x | bash - \
|
||||||
|
&& apt install -y git nodejs \
|
||||||
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY ../ .
|
||||||
|
RUN mvn clean -T4C && mvn package -T4C
|
||||||
5
config/Dockerfile.secdb
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
FROM postgres:11-alpine
|
||||||
|
|
||||||
|
COPY secdb.sql .
|
||||||
|
|
||||||
|
CMD psql "postgresql://${DB_SEC_USERNAME:-security}:${DB_SEC_PASSWORD:-secpassword}@${DB_SEC_HOST:-db}/${DB_SEC_NAME:-security}" < secdb.sql
|
||||||
1
config/JDK_version
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
17
|
||||||
31
config/browsers.json
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
{
|
||||||
|
"firefox": {
|
||||||
|
"default": "112.0",
|
||||||
|
"versions": {
|
||||||
|
"112.0": {
|
||||||
|
"image": "selenoid/firefox:112.0",
|
||||||
|
"port": "4444",
|
||||||
|
"path": "/wd/hub",
|
||||||
|
"tmpfs": {
|
||||||
|
"/tmp": "size=512m"
|
||||||
|
},
|
||||||
|
"env" : ["LANG=ru_RU.UTF-8", "LANGUAGE=ru:en", "LC_ALL=ru_RU.UTF-8"],
|
||||||
|
"shmSize": 1073741824
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"chrome": {
|
||||||
|
"default": "121.0",
|
||||||
|
"versions": {
|
||||||
|
"121.0": {
|
||||||
|
"image": "selenoid/chrome:121.0",
|
||||||
|
"port": "4444",
|
||||||
|
"tmpfs": {
|
||||||
|
"/tmp": "size=512m"
|
||||||
|
},
|
||||||
|
"env" : ["LANG=ru_RU.UTF-8", "LANGUAGE=ru:en", "LC_ALL=ru_RU.UTF-8"],
|
||||||
|
"shmSize": 1073741824
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
12
config/create-databases.sh
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
#!/bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
IFS=','
|
||||||
|
for databases in $WILDFLY_DATABASES
|
||||||
|
do
|
||||||
|
IFS=':' read name user password <<< $databases
|
||||||
|
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname "$POSTGRES_DB" <<-EOSQL
|
||||||
|
CREATE USER $user WITH PASSWORD '$password';
|
||||||
|
CREATE DATABASE $name WITH OWNER $user;
|
||||||
|
EOSQL
|
||||||
|
done
|
||||||
12
config/docker-compose.secdb.yaml
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
version: "3"
|
||||||
|
services:
|
||||||
|
secdb:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile.secdb
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
env_file:
|
||||||
|
- testing.env
|
||||||
|
labels:
|
||||||
|
- "tmp=true"
|
||||||
28
config/docker-compose.selenoid.yaml
Normal file
|
|
@ -0,0 +1,28 @@
|
||||||
|
version: "3"
|
||||||
|
services:
|
||||||
|
selenoid:
|
||||||
|
network_mode: bridge
|
||||||
|
image: aerokube/selenoid:latest-release
|
||||||
|
container_name: "selenoid"
|
||||||
|
environment:
|
||||||
|
- OVERRIDE_VIDEO_OUTPUT_DIR=$PWD/video
|
||||||
|
volumes:
|
||||||
|
- ".:/etc/selenoid/:ro"
|
||||||
|
- "/var/run/docker.sock:/var/run/docker.sock"
|
||||||
|
- "./logs:/opt/selenoid/logs/"
|
||||||
|
- "./video/:/opt/selenoid/video/"
|
||||||
|
command: -session-attempt-timeout 1m -retry-count 3 -limit ${LIMIT:-4} -save-all-logs -log-output-dir /opt/selenoid/logs -video-output-dir /opt/selenoid/video
|
||||||
|
ports:
|
||||||
|
- "4444:4444"
|
||||||
|
|
||||||
|
selenoid-ui:
|
||||||
|
image: aerokube/selenoid-ui
|
||||||
|
container_name: "selenoid-ui"
|
||||||
|
network_mode: bridge
|
||||||
|
depends_on:
|
||||||
|
- selenoid
|
||||||
|
links:
|
||||||
|
- "selenoid:selenoid"
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
command: --selenoid-uri http://selenoid:4444`
|
||||||
41
config/docker-compose.tests.yaml
Normal file
|
|
@ -0,0 +1,41 @@
|
||||||
|
version: "3"
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
ports:
|
||||||
|
- 5432:5432
|
||||||
|
environment:
|
||||||
|
- WILDFLY_DATABASES=app:app_user:apppassword,security:security_user:secpassword,jbpm:jbpm:jbpmpassword
|
||||||
|
|
||||||
|
webbpm-app:
|
||||||
|
env_file:
|
||||||
|
- testing.env
|
||||||
|
|
||||||
|
selenoid:
|
||||||
|
network_mode: bridge
|
||||||
|
image: aerokube/selenoid:latest-release
|
||||||
|
container_name: "selenoid"
|
||||||
|
environment:
|
||||||
|
- OVERRIDE_VIDEO_OUTPUT_DIR=$PWD/video
|
||||||
|
volumes:
|
||||||
|
- "$PWD:/etc/selenoid/:ro"
|
||||||
|
- "/var/run/docker.sock:/var/run/docker.sock"
|
||||||
|
- "$PWD/logs:/opt/selenoid/logs/"
|
||||||
|
- "$PWD/video/:/opt/selenoid/video/"
|
||||||
|
command: -session-attempt-timeout 1m -retry-count 3 -limit ${LIMIT:-4} -save-all-logs -log-output-dir /opt/selenoid/logs -video-output-dir /opt/selenoid/video
|
||||||
|
ports:
|
||||||
|
- "4444:4444"
|
||||||
|
depends_on:
|
||||||
|
- webbpm-app
|
||||||
|
|
||||||
|
selenoid-ui:
|
||||||
|
image: aerokube/selenoid-ui
|
||||||
|
container_name: "selenoid-ui"
|
||||||
|
network_mode: bridge
|
||||||
|
depends_on:
|
||||||
|
- selenoid
|
||||||
|
links:
|
||||||
|
- "selenoid:selenoid"
|
||||||
|
ports:
|
||||||
|
- "8080:8080"
|
||||||
|
command: --selenoid-uri http://selenoid:4444
|
||||||
|
|
||||||
31
config/docker-compose.yaml
Normal file
|
|
@ -0,0 +1,31 @@
|
||||||
|
version: "3"
|
||||||
|
services:
|
||||||
|
db:
|
||||||
|
image: postgres:15
|
||||||
|
volumes:
|
||||||
|
- ./create-databases.sh:/docker-entrypoint-initdb.d/create-databases.sh
|
||||||
|
command:
|
||||||
|
- "--max_prepared_transactions=100"
|
||||||
|
ports:
|
||||||
|
- 5432
|
||||||
|
environment:
|
||||||
|
- WILDFLY_DATABASES=security:security_user:secpassword,jbpm:jbpm:jbpmpassword
|
||||||
|
- POSTGRES_PASSWORD=supersecretpassword
|
||||||
|
labels:
|
||||||
|
- "tmp=true"
|
||||||
|
|
||||||
|
webbpm-app:
|
||||||
|
build:
|
||||||
|
context: .
|
||||||
|
dockerfile: Dockerfile
|
||||||
|
depends_on:
|
||||||
|
- db
|
||||||
|
ports:
|
||||||
|
- 9990
|
||||||
|
- 8080
|
||||||
|
- 8787
|
||||||
|
- 12345
|
||||||
|
env_file:
|
||||||
|
- testing.env
|
||||||
|
labels:
|
||||||
|
- "tmp=true"
|
||||||
48
config/entrypoint.sh
Normal file
|
|
@ -0,0 +1,48 @@
|
||||||
|
#! /bin/bash
|
||||||
|
set -e
|
||||||
|
|
||||||
|
function wait_for_server() {
|
||||||
|
until `$JBOSS_HOME/bin/jboss-cli.sh -c ":read-attribute(name=server-state)" 2> /dev/null | grep -q running`; do
|
||||||
|
echo "Retry ..."
|
||||||
|
done
|
||||||
|
}
|
||||||
|
|
||||||
|
echo "dump environment variables to env.properties file"
|
||||||
|
printenv > env.properties
|
||||||
|
|
||||||
|
echo "starting JBoss"
|
||||||
|
nohup $JBOSS_HOME/bin/standalone.sh --admin-only 1>&2 2>/dev/null &
|
||||||
|
|
||||||
|
# running system patches
|
||||||
|
wait_for_server
|
||||||
|
$JBOSS_HOME/bin/jboss-cli.sh --connect --file="./patches/system/init.cli" --properties=env.properties
|
||||||
|
$JBOSS_HOME/bin/jboss-cli.sh --connect --file="./patches/system/add-postgresql-driver.cli" --properties=env.properties
|
||||||
|
bash "./patches/system/add-demo-user.sh"
|
||||||
|
|
||||||
|
# running project patches
|
||||||
|
find ./patches/ -type f -name '*.cli' -not -path './patches/system/*' -print0 |
|
||||||
|
while IFS= read -r -d '' f; do
|
||||||
|
wait_for_server
|
||||||
|
echo "running $f"
|
||||||
|
$JBOSS_HOME/bin/jboss-cli.sh --connect --file="$f" --properties=env.properties
|
||||||
|
done;
|
||||||
|
|
||||||
|
find ./patches/ -type f -name '*.sh' -not -path './patches/system/*' -print0 |
|
||||||
|
while IFS= read -r -d '' f; do
|
||||||
|
wait_for_server
|
||||||
|
echo "running $f"
|
||||||
|
bash "$f"
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "stopping JBoss"
|
||||||
|
wait_for_server
|
||||||
|
$JBOSS_HOME/bin/jboss-cli.sh --connect --command=:shutdown
|
||||||
|
|
||||||
|
if ! [[ -z $SERVER_START ]]; then
|
||||||
|
echo "starting JBoss in standalone"
|
||||||
|
sleep 10 # without this occurs error "address already in use"
|
||||||
|
/opt/jboss/wildfly/bin/standalone.sh -c standalone.xml -b 0.0.0.0 -bmanagement 0.0.0.0
|
||||||
|
else
|
||||||
|
echo "cleaning up JBoss logs"
|
||||||
|
rm -rf $JBOSS_HOME/standalone/log
|
||||||
|
fi
|
||||||
3
config/patches/add-logger-database.cli
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
/subsystem=logging/logger=org.jooq.tools:add()
|
||||||
|
/subsystem=logging/logger=org.jooq.tools:write-attribute(name=level, value=DEBUG)
|
||||||
|
/subsystem=logging/logger=org.jooq.tools:add-handler(name=CONSOLE)
|
||||||
64
config/patches/default.cli
Normal file
|
|
@ -0,0 +1,64 @@
|
||||||
|
xa-data-source add \
|
||||||
|
--name=AppDS \
|
||||||
|
--enabled=true \
|
||||||
|
--driver-name=postgresql \
|
||||||
|
--jndi-name=java:/webbpm/AppDS \
|
||||||
|
--user-name=${env.DB_APP_USERNAME:app_user} \
|
||||||
|
--password=${env.DB_APP_PASSWORD:apppassword} \
|
||||||
|
--use-ccm=true \
|
||||||
|
--valid-connection-checker-class-name=org.jboss.jca.adapters.jdbc.extensions.postgres.PostgreSQLValidConnectionChecker \
|
||||||
|
--validate-on-match=false \
|
||||||
|
--background-validation=true \
|
||||||
|
--background-validation-millis=5000 \
|
||||||
|
--exception-sorter-class-name=org.jboss.jca.adapters.jdbc.extensions.postgres.PostgreSQLExceptionSorter \
|
||||||
|
--statistics-enabled=true \
|
||||||
|
--max-pool-size=50 \
|
||||||
|
--query-timeout=300 \
|
||||||
|
--xa-datasource-properties=ServerName=${env.DB_APP_HOST:db},PortNumber=${env.DB_APP_PORT:5432},DatabaseName=${env.DB_APP_NAME:app}
|
||||||
|
|
||||||
|
xa-data-source add \
|
||||||
|
--name=JBPMDS \
|
||||||
|
--enabled=true \
|
||||||
|
--driver-name=postgresql \
|
||||||
|
--jndi-name=java:jboss/datasources/jbpmDS \
|
||||||
|
--user-name=${env.DB_JBPM_USERNAME:jbpm} \
|
||||||
|
--password=${env.DB_JBPM_PASSWORD:jbpmpassword} \
|
||||||
|
--use-ccm=true \
|
||||||
|
--valid-connection-checker-class-name=org.jboss.jca.adapters.jdbc.extensions.postgres.PostgreSQLValidConnectionChecker \
|
||||||
|
--validate-on-match=false \
|
||||||
|
--background-validation=true \
|
||||||
|
--background-validation-millis=5000 \
|
||||||
|
--exception-sorter-class-name=org.jboss.jca.adapters.jdbc.extensions.postgres.PostgreSQLExceptionSorter \
|
||||||
|
--statistics-enabled=true \
|
||||||
|
--max-pool-size=50 \
|
||||||
|
--query-timeout=300 \
|
||||||
|
--xa-datasource-properties=ServerName=${env.DB_JBPM_HOST:db},PortNumber=${env.DB_JBPM_PORT:5432},DatabaseName=${env.DB_JBPM_NAME:jbpm}
|
||||||
|
|
||||||
|
xa-data-source add \
|
||||||
|
--name=SECURITYDS \
|
||||||
|
--enabled=true \
|
||||||
|
--driver-name=postgresql \
|
||||||
|
--jndi-name=java:/webbpm/security-ds \
|
||||||
|
--user-name=${env.DB_SEC_USERNAME:security_user} \
|
||||||
|
--password=${env.DB_SEC_PASSWORD:secpassword} \
|
||||||
|
--max-pool-size=70 \
|
||||||
|
--valid-connection-checker-class-name=org.jboss.jca.adapters.jdbc.extensions.postgres.PostgreSQLValidConnectionChecker \
|
||||||
|
--validate-on-match=false \
|
||||||
|
--background-validation=true \
|
||||||
|
--background-validation-millis=5000 \
|
||||||
|
--exception-sorter-class-name=org.jboss.jca.adapters.jdbc.extensions.postgres.PostgreSQLExceptionSorter \
|
||||||
|
--statistics-enabled=true \
|
||||||
|
--query-timeout=300 \
|
||||||
|
--xa-datasource-properties=ServerName=${env.DB_SEC_HOST:db},PortNumber=${env.DB_SEC_PORT:5432},DatabaseName=${env.DB_SEC_NAME:app}
|
||||||
|
|
||||||
|
/system-property=ldap.mapping.login.param:add(value=${env.WEBBPM_LDAP_LOGIN_ATTR:uid})
|
||||||
|
/system-property=ldap.mapping.org.code.param:add(value=${env.WEBBPM_LDAP_ORGANIZATION_ATTR:ou})
|
||||||
|
/system-property=jboss.as.management.blocking.timeout:add(value=900)
|
||||||
|
/subsystem=undertow/server=default-server/http-listener=default/:write-attribute(name=record-request-start-time,value=true)
|
||||||
|
/subsystem=undertow/server=default-server/host=default-host/setting=access-log:add(pattern=%h %t "%r" %s %b %D)
|
||||||
|
/system-property=webbpm.cache.hazelcast.hosts:add(value="127.0.0.1")
|
||||||
|
/system-property=webbpm.cache.hazelcast.outbound_port_definitions:add(value="5801-5820")
|
||||||
|
/system-property=webbpm.security.session.active.count:add(value="20")
|
||||||
|
/system-property=gar.enable:add(value=false)
|
||||||
|
/system-property=security.password.regex:add(value="^((?=(.*\\d){1,})(?=.*[a-zа-яё])(?=.*[A-ZА-ЯЁ]).{8,})$")
|
||||||
|
/system-property=fias.enable:add(value=false)
|
||||||
1
config/patches/system/add-demo-user.sh
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
$JBOSS_HOME/bin/add-user.sh demo@example.com demo
|
||||||
5
config/patches/system/add-postgresql-driver.cli
Normal file
|
|
@ -0,0 +1,5 @@
|
||||||
|
/subsystem=datasources/jdbc-driver=postgresql:add( \
|
||||||
|
driver-name="postgresql", \
|
||||||
|
driver-module-name="org.postgresql", \
|
||||||
|
driver-xa-datasource-class-name="org.postgresql.xa.PGXADataSource" \
|
||||||
|
)
|
||||||
14
config/patches/system/init.cli
Normal file
|
|
@ -0,0 +1,14 @@
|
||||||
|
/system-property=webbpm.mode:add(value=production)
|
||||||
|
/system-property=authentication.method:add(value=form)
|
||||||
|
/subsystem=undertow/configuration=filter/gzip=gzipFilter:add()
|
||||||
|
/subsystem=undertow/server=default-server/host=default-host/\
|
||||||
|
filter-ref=gzipFilter:add(predicate="exists('%{o,Content-Type}') and regex(pattern='(?:application/javascript|text/css|text/html|text/xml|application/json)(;.*)?', value=%{o,Content-Type}, full-match=true)")
|
||||||
|
/subsystem=undertow/configuration=filter/response-header=vary-header:add(header-name="Vary", header-value="Accept-Encoding")
|
||||||
|
/subsystem=undertow/server=default-server/host=default-host/filter-ref=vary-header:add()
|
||||||
|
/subsystem=undertow/server=default-server/http-listener=default/:write-attribute(name=max-post-size,value=${env.MAX_POST_SIZE:104857600})
|
||||||
|
data-source remove --name=ExampleDS
|
||||||
|
/subsystem=ee/service=default-bindings:remove
|
||||||
|
/system-property=jboss.bind.address.management:add(value=0.0.0.0)
|
||||||
|
/system-property=jboss.bind.address:add(value=0.0.0.0)
|
||||||
|
module add --name=org.postgresql --resources=./patches/system/postgresql-driver.jar --dependencies=javax.api,javax.transaction.api
|
||||||
|
shutdown --restart
|
||||||
25
config/secdb.sql
Normal file
|
|
@ -0,0 +1,25 @@
|
||||||
|
INSERT INTO security.user_group(user_group_id, name, created, updated, access_level_id) VALUES (uuid_in(md5(random()::text || now()::text)::cstring), 'Example', '2019-07-04 11:12:01.263+03', '2019-08-26 16:40:11.953+03', (SELECT access_level_id FROM security.access_level where level = 0)) ON CONFLICT DO NOTHING;
|
||||||
|
INSERT INTO security.link_user_group_user_role(link_user_group_user_role_id, user_group_id, user_role_id) SELECT uuid_in(md5(random()::text || now()::text)::cstring), (SELECT user_group_id FROM security.user_group where name = 'Example'), user_role_id FROM security.user_role WHERE name = 'Example' ON CONFLICT DO NOTHING;
|
||||||
|
INSERT INTO security.link_user_group_user_role(link_user_group_user_role_id, user_group_id, user_role_id) SELECT uuid_in(md5(random()::text || now()::text)::cstring), (SELECT user_group_id FROM security.user_group where name = 'Demo'), user_role_id FROM security.user_role WHERE name = 'Example' ON CONFLICT DO NOTHING;
|
||||||
|
|
||||||
|
INSERT INTO security.user_group(user_group_id, name, created, updated, access_level_id) VALUES (uuid_in(md5(random()::text || now()::text)::cstring), 'Example', '2019-07-04 11:12:01.263+03', '2019-08-26 16:40:11.953+03', (SELECT access_level_id FROM security.access_level where level = 0)) ON CONFLICT DO NOTHING;
|
||||||
|
INSERT INTO security.link_user_group_user_role(link_user_group_user_role_id, user_group_id, user_role_id) SELECT uuid_in(md5(random()::text || now()::text)::cstring), (SELECT user_group_id FROM security.user_group where name = 'Example'), user_role_id FROM security.user_role WHERE name = 'Example' ON CONFLICT DO NOTHING;
|
||||||
|
INSERT INTO security.link_user_group_user_role(link_user_group_user_role_id, user_group_id, user_role_id) SELECT uuid_in(md5(random()::text || now()::text)::cstring), (SELECT user_group_id FROM security.user_group where name = 'Demo'), user_role_id FROM security.user_role WHERE name = 'Example' ON CONFLICT DO NOTHING;
|
||||||
|
|
||||||
|
INSERT INTO security.user_account(user_account_id, email, first_name, last_name, middle_name, created, updated, locked, org_unit_id, username) VALUES (uuid_in(md5(random()::text || now()::text)::cstring), 'qa_test@micord.ru', 'Пользователь', 'Основной-Тестовый', NULL, '2020-01-20 08:41:25.526+03', '2020-01-20 09:57:07.25+03', false, (SELECT id from security.org_unit where code = 'DEFAULT'), 'qa_test') ON CONFLICT DO NOTHING;
|
||||||
|
INSERT INTO security.simple_credentials( user_account_id, password, password_expires, password_updated) VALUES ((SELECT user_account_id from security.user_account where username = 'qa_test'), '$2a$11$uTzaeewDp2NehwjmfRe/euxP.cy.4ecl5nYA.E5TV9AGjjDKdJwI2', NULL, '2020-01-20 05:41:25.746') ON CONFLICT DO NOTHING;
|
||||||
|
INSERT INTO security.link_user_account_user_group(link_user_account_user_group_id, user_account_id, user_group_id) VALUES (uuid_in(md5(random()::text || now()::text)::cstring), (SELECT user_account_id from security.user_account where username = 'qa_test'), (SELECT user_group_id FROM security.user_group where name = 'Security Admin')) ON CONFLICT DO NOTHING;
|
||||||
|
INSERT INTO security.link_user_account_user_group(link_user_account_user_group_id, user_account_id, user_group_id) VALUES (uuid_in(md5(random()::text || now()::text)::cstring), (SELECT user_account_id from security.user_account where username = 'qa_test'), (SELECT user_group_id FROM security.user_group where name = 'BPMN Superuser')) ON CONFLICT DO NOTHING;
|
||||||
|
INSERT INTO security.link_user_account_user_group(link_user_account_user_group_id, user_account_id, user_group_id) VALUES (uuid_in(md5(random()::text || now()::text)::cstring), (SELECT user_account_id from security.user_account where username = 'qa_test'), (SELECT user_group_id FROM security.user_group where name = 'BPMN Admin')) ON CONFLICT DO NOTHING;
|
||||||
|
INSERT INTO security.link_user_account_user_group(link_user_account_user_group_id, user_account_id, user_group_id) VALUES (uuid_in(md5(random()::text || now()::text)::cstring), (SELECT user_account_id from security.user_account where username = 'qa_test'), (SELECT user_group_id FROM security.user_group where name = 'Example')) ON CONFLICT DO NOTHING;
|
||||||
|
INSERT INTO security.link_user_account_user_group(link_user_account_user_group_id, user_account_id, user_group_id) VALUES (uuid_in(md5(random()::text || now()::text)::cstring), (SELECT user_account_id from security.user_account where username = 'qa_test'), (SELECT user_group_id FROM security.user_group where name = 'BPMN User')) ON CONFLICT DO NOTHING;
|
||||||
|
INSERT INTO security.link_user_account_user_group(link_user_account_user_group_id, user_account_id, user_group_id) VALUES (uuid_in(md5(random()::text || now()::text)::cstring), (SELECT user_account_id from security.user_account where username = 'qa_test'), (SELECT user_group_id FROM security.user_group where name = 'Demo')) ON CONFLICT DO NOTHING;
|
||||||
|
|
||||||
|
INSERT INTO security.user_account(user_account_id, email, first_name, last_name, middle_name, created, updated, locked, org_unit_id, username) VALUES (uuid_in(md5(random()::text || now()::text)::cstring), 'qa_admin@micord.ru', 'qa_admin', 'qa_admin', NULL, '2020-01-20 08:41:25.526+03', '2020-01-20 09:57:07.25+03', false, (SELECT id from security.org_unit where code = 'DEFAULT'), 'qa_admin') ON CONFLICT DO NOTHING;
|
||||||
|
INSERT INTO security.simple_credentials( user_account_id, password, password_expires, password_updated) VALUES ((SELECT user_account_id from security.user_account where username = 'qa_admin'), '$2a$11$Zzmuga/xw1c1UnUe9nP9c.aqJ2O.OAI/AbmphOm2jurxMI0S6VAcy', NULL, '2020-01-20 05:41:25.746') ON CONFLICT DO NOTHING;
|
||||||
|
INSERT INTO security.link_user_account_user_group(link_user_account_user_group_id, user_account_id, user_group_id) VALUES (uuid_in(md5(random()::text || now()::text)::cstring), (SELECT user_account_id from security.user_account where username = 'qa_admin'), (SELECT user_group_id FROM security.user_group where name = 'Security Admin')) ON CONFLICT DO NOTHING;
|
||||||
|
INSERT INTO security.link_user_account_user_group(link_user_account_user_group_id, user_account_id, user_group_id) VALUES (uuid_in(md5(random()::text || now()::text)::cstring), (SELECT user_account_id from security.user_account where username = 'qa_admin'), (SELECT user_group_id FROM security.user_group where name = 'BPMN Superuser')) ON CONFLICT DO NOTHING;
|
||||||
|
INSERT INTO security.link_user_account_user_group(link_user_account_user_group_id, user_account_id, user_group_id) VALUES (uuid_in(md5(random()::text || now()::text)::cstring), (SELECT user_account_id from security.user_account where username = 'qa_admin'), (SELECT user_group_id FROM security.user_group where name = 'BPMN Admin')) ON CONFLICT DO NOTHING;
|
||||||
|
INSERT INTO security.link_user_account_user_group(link_user_account_user_group_id, user_account_id, user_group_id) VALUES (uuid_in(md5(random()::text || now()::text)::cstring), (SELECT user_account_id from security.user_account where username = 'qa_admin'), (SELECT user_group_id FROM security.user_group where name = 'Example')) ON CONFLICT DO NOTHING;
|
||||||
|
INSERT INTO security.link_user_account_user_group(link_user_account_user_group_id, user_account_id, user_group_id) VALUES (uuid_in(md5(random()::text || now()::text)::cstring), (SELECT user_account_id from security.user_account where username = 'qa_admin'), (SELECT user_group_id FROM security.user_group where name = 'BPMN User')) ON CONFLICT DO NOTHING;
|
||||||
|
INSERT INTO security.link_user_account_user_group(link_user_account_user_group_id, user_account_id, user_group_id) VALUES (uuid_in(md5(random()::text || now()::text)::cstring), (SELECT user_account_id from security.user_account where username = 'qa_admin'), (SELECT user_group_id FROM security.user_group where name = 'Demo')) ON CONFLICT DO NOTHING;
|
||||||
37
config/testing.env
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
# Security datasource
|
||||||
|
DB_SEC_USERNAME=security_user
|
||||||
|
DB_SEC_PASSWORD=secpassword
|
||||||
|
DB_SEC_HOST=db
|
||||||
|
DB_SEC_PORT=5432
|
||||||
|
DB_SEC_NAME=security
|
||||||
|
# App datasource
|
||||||
|
DB_APP_USERNAME=app_user
|
||||||
|
DB_APP_PASSWORD=apppassword
|
||||||
|
DB_APP_HOST=db
|
||||||
|
DB_APP_PORT=5432
|
||||||
|
DB_APP_NAME=app
|
||||||
|
# jBPM datasorce
|
||||||
|
DB_JBPM_USERNAME=jbpm
|
||||||
|
DB_JBPM_PASSWORD=jbpmpassword
|
||||||
|
DB_JBPM_HOST=db
|
||||||
|
DB_JBPM_PORT=5432
|
||||||
|
DB_JBPM_NAME=jbpm
|
||||||
|
# Elasticsearch
|
||||||
|
WEBBPM_ELASTIC_HOST=
|
||||||
|
WEBBPM_ELASTIC_USER_PASSWORD=
|
||||||
|
# LDAP
|
||||||
|
WEBBPM_LDAP_URL=
|
||||||
|
WEBBPM_LDAP_BASE=
|
||||||
|
WEBBPM_LDAP_USER=
|
||||||
|
WEBBPM_LDAP_PASSWORD=
|
||||||
|
WEBBPM_LDAP_SYNC_ENABLED=false
|
||||||
|
WEBBPM_LDAP_SYNC_CRON=0 0 * * * *
|
||||||
|
WEBBPM_LDAP_LOGIN_ATTR=uid
|
||||||
|
WEBBPM_LDAP_ORGANIZATION_ATTR=ou
|
||||||
|
# JIRA
|
||||||
|
WEBBPM_JIRA_URL=https:/jira.com
|
||||||
|
WEBBPM_JIRA_USER=jiraUser
|
||||||
|
WEBBPM_JIRA_PASSWORD=jiraPass
|
||||||
|
TZ=Europe/Moscow
|
||||||
|
WEBBPM_TELEGRAM_BOT_TOKEN=
|
||||||
|
WEBBPM_TELEGRAM_BOT_NAME=
|
||||||
66
distribution/pom.xml
Normal file
|
|
@ -0,0 +1,66 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>ervu_lkrp_ul</groupId>
|
||||||
|
<artifactId>ervu_lkrp_ul</artifactId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<groupId>ervu_lkrp_ul.ervu_lkrp_ul</groupId>
|
||||||
|
<artifactId>distribution</artifactId>
|
||||||
|
<packaging>ear</packaging>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<backendContext>/backend</backendContext>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ervu_lkrp_ul.ervu_lkrp_ul</groupId>
|
||||||
|
<artifactId>backend</artifactId>
|
||||||
|
<type>war</type>
|
||||||
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>ervu_lkrp_ul.ervu_lkrp_ul</groupId>
|
||||||
|
<artifactId>frontend</artifactId>
|
||||||
|
<type>war</type>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-ear-plugin</artifactId>
|
||||||
|
<version>3.2.0</version>
|
||||||
|
<configuration>
|
||||||
|
<modules>
|
||||||
|
<webModule>
|
||||||
|
<groupId>ervu_lkrp_ul.ervu_lkrp_ul</groupId>
|
||||||
|
<artifactId>frontend</artifactId>
|
||||||
|
<contextRoot>/</contextRoot>
|
||||||
|
<bundleFileName>frontend.war</bundleFileName>
|
||||||
|
</webModule>
|
||||||
|
<webModule>
|
||||||
|
<groupId>ervu_lkrp_ul.ervu_lkrp_ul</groupId>
|
||||||
|
<artifactId>backend</artifactId>
|
||||||
|
<contextRoot>${backendContext}</contextRoot>
|
||||||
|
<bundleFileName>backend.war</bundleFileName>
|
||||||
|
</webModule>
|
||||||
|
</modules>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
<finalName>${project.parent.artifactId}</finalName>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<profiles>
|
||||||
|
<profile>
|
||||||
|
<id>enable-version-in-url</id>
|
||||||
|
<properties>
|
||||||
|
<backendContext>/backend-${project.version}</backendContext>
|
||||||
|
</properties>
|
||||||
|
</profile>
|
||||||
|
</profiles>
|
||||||
|
</project>
|
||||||
1
frontend/.npmrc
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
registry=https://repo.micord.ru/repository/npm-all/
|
||||||
71
frontend/angular.json
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
{
|
||||||
|
"$schema": "./node_modules/@angular/cli/lib/config/schema.json",
|
||||||
|
"version": 1,
|
||||||
|
"newProjectRoot": "projects",
|
||||||
|
"projects": {
|
||||||
|
"webbpm-frontend": {
|
||||||
|
"root": "",
|
||||||
|
"sourceRoot": "src",
|
||||||
|
"projectType": "application",
|
||||||
|
"architect": {
|
||||||
|
"build": {
|
||||||
|
"builder": "@angular-devkit/build-angular:browser",
|
||||||
|
"options": {
|
||||||
|
"outputPath": "dist",
|
||||||
|
"index": "src/index.html",
|
||||||
|
"main": "src/ts/main.ts",
|
||||||
|
"tsConfig": "src/tsconfig.json",
|
||||||
|
"polyfills": "src/ts/polyfills.ts",
|
||||||
|
"assets": [
|
||||||
|
"src/resources"
|
||||||
|
],
|
||||||
|
"styles": [
|
||||||
|
],
|
||||||
|
"scripts": [
|
||||||
|
"node_modules/jquery/dist/jquery.min.js",
|
||||||
|
"node_modules/moment/min/moment-with-locales.js",
|
||||||
|
"node_modules/moment-timezone/builds/moment-timezone-with-data.min.js",
|
||||||
|
"node_modules/eonasdan-bootstrap-datetimepicker/build/js/bootstrap-datetimepicker.min.js",
|
||||||
|
"node_modules/selectize/dist/js/standalone/selectize.min.js",
|
||||||
|
"node_modules/downloadjs/download.min.js"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"configurations": {
|
||||||
|
"production": {
|
||||||
|
"optimization": true,
|
||||||
|
"outputHashing": "all",
|
||||||
|
"sourceMap": false,
|
||||||
|
"extractCss": true,
|
||||||
|
"namedChunks": false,
|
||||||
|
"aot": true,
|
||||||
|
"extractLicenses": true,
|
||||||
|
"vendorChunk": false,
|
||||||
|
"buildOptimizer": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"serve": {
|
||||||
|
"builder": "@angular-devkit/build-angular:dev-server",
|
||||||
|
"options": {
|
||||||
|
"browserTarget": "webbpm-frontend:build"
|
||||||
|
},
|
||||||
|
"configurations": {}
|
||||||
|
},
|
||||||
|
"extract-i18n": {
|
||||||
|
"builder": "@angular-devkit/build-angular:extract-i18n",
|
||||||
|
"options": {
|
||||||
|
"browserTarget": "webbpm-frontend:build"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"lint": {
|
||||||
|
"builder": "@angular-devkit/build-angular:tslint",
|
||||||
|
"options": {
|
||||||
|
"tsConfig": [],
|
||||||
|
"exclude": []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"defaultProject": "webbpm-frontend"
|
||||||
|
}
|
||||||
10
frontend/bs-config.json
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
{
|
||||||
|
"port": 8000,
|
||||||
|
"open": false,
|
||||||
|
"files": [
|
||||||
|
"./**/*.{html,htm,css,js}"
|
||||||
|
],
|
||||||
|
"server": {
|
||||||
|
"baseDir": "./"
|
||||||
|
}
|
||||||
|
}
|
||||||
23
frontend/index.html
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>ervu_lkrp_ul</title>
|
||||||
|
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
||||||
|
<link rel="icon" type="image/png" href="src/resources/img/logo.png"/>
|
||||||
|
<link rel="stylesheet" href="src/resources/css/style.css"/>
|
||||||
|
|
||||||
|
<script src="node_modules/core-js/client/shim.min.js"></script>
|
||||||
|
<script src="node_modules/zone.js/dist/zone.js"></script>
|
||||||
|
<script src="node_modules/reflect-metadata/Reflect.js"></script>
|
||||||
|
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
||||||
|
<script src="systemjs.config.js"></script>
|
||||||
|
<script>
|
||||||
|
System.import('webbpm').catch(function (err) {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body webbpm class="webbpm ervu_lkrp_ul">
|
||||||
|
<div class="progress"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
11
frontend/index.webpack.html
Normal file
|
|
@ -0,0 +1,11 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>ervu_lkrp_ul</title>
|
||||||
|
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
||||||
|
<link rel="icon" type="image/png" href="src/resources/img/logo.png"/>
|
||||||
|
</head>
|
||||||
|
<body webbpm class="webbpm ervu_lkrp_ul">
|
||||||
|
<div class="progress"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
100
frontend/package.json
Normal file
|
|
@ -0,0 +1,100 @@
|
||||||
|
{
|
||||||
|
"name": "webbpm-frontend",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"scripts": {
|
||||||
|
"lite": "node ./node_modules/lite-server/bin/lite-server",
|
||||||
|
"cleanup": "npm run cleanup-ngc && node ./node_modules/rimraf/bin ./build ./dist",
|
||||||
|
"cleanup-ngc": "node ./node_modules/rimraf/bin ./src/ts/**/*.js ./src/ts/**/*.json ./src/ts/page.routing.ts",
|
||||||
|
"cleanup-and-ngc": "npm run cleanup && npm run ngc",
|
||||||
|
"ngc": "node --max-old-space-size=14336 ./node_modules/@angular/compiler-cli/src/main -p tsconfig.aot.json",
|
||||||
|
"build-webpack": "node --max-old-space-size=14336 ./node_modules/webpack/bin/webpack --config webpack.aot.config.js --progress --profile",
|
||||||
|
"save-ts-metadata": "node save.ts.metadata.js",
|
||||||
|
"tsc": "node ./node_modules/typescript/bin/tsc",
|
||||||
|
"tsc-watch": "node ./node_modules/typescript/bin/tsc --watch",
|
||||||
|
"ts-watch": "node node_modules/cross-env/dist/bin/cross-env.js TSC_NONPOLLING_WATCHER=true npm run tsc-watch",
|
||||||
|
"ts": "npm install && npm run tsc",
|
||||||
|
"compile": "npm run ts-watch",
|
||||||
|
"install-compile": "npm install && npm run ts-watch"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@angular/animations": "7.2.15",
|
||||||
|
"@angular/common": "7.2.15",
|
||||||
|
"@angular/compiler": "7.2.15",
|
||||||
|
"@angular/core": "7.2.15",
|
||||||
|
"@angular/forms": "7.2.15",
|
||||||
|
"@angular/http": "7.2.15",
|
||||||
|
"@angular/platform-browser": "7.2.15",
|
||||||
|
"@angular/platform-browser-dynamic": "7.2.15",
|
||||||
|
"@angular/router": "7.2.15",
|
||||||
|
"@ng-bootstrap/ng-bootstrap": "4.1.1",
|
||||||
|
"ag-grid-angular": "29.0.0-micord.4",
|
||||||
|
"ag-grid-community": "29.0.0-micord.4",
|
||||||
|
"angular-calendar": "0.28.28",
|
||||||
|
"autonumeric": "4.5.10-cg",
|
||||||
|
"bootstrap": "4.3.1",
|
||||||
|
"bootstrap-icons": "1.10.3",
|
||||||
|
"cadesplugin_api": "2.0.4-micord.1",
|
||||||
|
"chart.js": "3.8.0-cg.1",
|
||||||
|
"chartjs-adapter-moment": "1.0.0",
|
||||||
|
"core-js": "2.4.1",
|
||||||
|
"date-fns": "2.29.3",
|
||||||
|
"downloadjs": "1.4.8",
|
||||||
|
"eonasdan-bootstrap-datetimepicker": "4.17.47-micord.4",
|
||||||
|
"esmarttokenjs": "2.2.1-cg",
|
||||||
|
"font-awesome": "4.7.0",
|
||||||
|
"google-libphonenumber": "3.0.9",
|
||||||
|
"inputmask": "5.0.5-cg.2",
|
||||||
|
"jquery": "3.3.1",
|
||||||
|
"js-year-calendar": "1.0.0-cg.2",
|
||||||
|
"jsgantt-improved": "2.0.10-cg",
|
||||||
|
"moment": "2.17.1",
|
||||||
|
"moment-timezone": "0.5.11",
|
||||||
|
"ngx-cookie": "3.0.1",
|
||||||
|
"ngx-international-phone-number": "1.0.6",
|
||||||
|
"ngx-toastr": "10.2.0-cg",
|
||||||
|
"popper.js": "1.14.7",
|
||||||
|
"reflect-metadata": "0.1.13",
|
||||||
|
"rxjs": "6.4.0",
|
||||||
|
"rxjs-compat": "6.4.0",
|
||||||
|
"selectize": "0.12.4-cg.10",
|
||||||
|
"systemjs": "0.21.4",
|
||||||
|
"systemjs-plugin-babel": "0.0.25",
|
||||||
|
"tslib": "1.9.3",
|
||||||
|
"zone.js": "0.8.29"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@angular-devkit/build-optimizer": "0.13.9",
|
||||||
|
"@angular-devkit/core": "7.3.9",
|
||||||
|
"@angular/cli": "7.3.9",
|
||||||
|
"@angular/compiler-cli": "7.2.15",
|
||||||
|
"@angular/platform-server": "7.2.15",
|
||||||
|
"@babel/core": "7.9.6",
|
||||||
|
"@babel/preset-env": "7.9.6",
|
||||||
|
"@types/bootstrap": "3.3.39",
|
||||||
|
"@types/jquery": "2.0.49",
|
||||||
|
"@types/node": "7.0.5",
|
||||||
|
"@types/selectize": "0.12.33",
|
||||||
|
"angular-router-loader": "0.8.5",
|
||||||
|
"angular2-template-loader": "0.6.2",
|
||||||
|
"babel-loader": "8.1.0",
|
||||||
|
"codelyzer": "5.2.1",
|
||||||
|
"copy-webpack-plugin": "5.0.3",
|
||||||
|
"cross-env": "5.2.1",
|
||||||
|
"css-loader": "2.1.0",
|
||||||
|
"del": "2.2.2",
|
||||||
|
"file-loader": "3.0.1",
|
||||||
|
"html-webpack-plugin": "4.5.2",
|
||||||
|
"lite-server": "2.3.0",
|
||||||
|
"mini-css-extract-plugin": "0.6.0",
|
||||||
|
"mkdirp": "0.5.1",
|
||||||
|
"raw-loader": "1.0.0",
|
||||||
|
"style-loader": "0.23.1",
|
||||||
|
"terser-webpack-plugin": "1.2.4",
|
||||||
|
"tslint": "5.13.1",
|
||||||
|
"typescript": "3.2.4",
|
||||||
|
"typescript-parser": "2.6.1-cg-fork",
|
||||||
|
"webpack": "4.32.2",
|
||||||
|
"webpack-bundle-analyzer": "3.3.2",
|
||||||
|
"webpack-cli": "3.3.2"
|
||||||
|
}
|
||||||
|
}
|
||||||
113
frontend/pom.xml
Normal file
|
|
@ -0,0 +1,113 @@
|
||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
<parent>
|
||||||
|
<groupId>ervu_lkrp_ul</groupId>
|
||||||
|
<artifactId>ervu_lkrp_ul</artifactId>
|
||||||
|
<version>1.0.0-SNAPSHOT</version>
|
||||||
|
</parent>
|
||||||
|
|
||||||
|
<groupId>ervu_lkrp_ul.ervu_lkrp_ul</groupId>
|
||||||
|
<artifactId>frontend</artifactId>
|
||||||
|
<packaging>war</packaging>
|
||||||
|
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>com.google.code.maven-replacer-plugin</groupId>
|
||||||
|
<artifactId>replacer</artifactId>
|
||||||
|
<version>1.5.3</version>
|
||||||
|
<executions>
|
||||||
|
<execution>
|
||||||
|
<id>replace-version-in-url</id>
|
||||||
|
<phase>process-resources</phase>
|
||||||
|
<goals>
|
||||||
|
<goal>replace</goal>
|
||||||
|
</goals>
|
||||||
|
</execution>
|
||||||
|
</executions>
|
||||||
|
<configuration>
|
||||||
|
<includes>
|
||||||
|
<include>${basedir}/src/resources/app-config.json</include>
|
||||||
|
<include>${basedir}/dist/src/resources/app-config.json</include>
|
||||||
|
<include>${basedir}/src/resources/app.version</include>
|
||||||
|
<include>${basedir}/dist/src/resources/app.version</include>
|
||||||
|
</includes>
|
||||||
|
<replacements>
|
||||||
|
<replacement>
|
||||||
|
<token>%project.version%</token>
|
||||||
|
<value>${project.version}</value>
|
||||||
|
</replacement>
|
||||||
|
<replacement>
|
||||||
|
<token>%enable.version.in.url%</token>
|
||||||
|
<value>${enable.version.in.url}</value>
|
||||||
|
</replacement>
|
||||||
|
</replacements>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
|
||||||
|
<profiles>
|
||||||
|
<profile>
|
||||||
|
<id>dev</id>
|
||||||
|
<activation>
|
||||||
|
<activeByDefault>true</activeByDefault>
|
||||||
|
</activation>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-war-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<copyWebResources>false</copyWebResources>
|
||||||
|
<webResources>
|
||||||
|
<resource>
|
||||||
|
<directory>${basedir}</directory>
|
||||||
|
<includes>
|
||||||
|
<include>src/resources/**/*</include>
|
||||||
|
<include>build_dev/**/*</include>
|
||||||
|
<include>node_modules/**/*</include>
|
||||||
|
<include>index.html</include>
|
||||||
|
<include>systemjs.config.js</include>
|
||||||
|
</includes>
|
||||||
|
</resource>
|
||||||
|
</webResources>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</profile>
|
||||||
|
<profile>
|
||||||
|
<id>compile-ts</id>
|
||||||
|
<activation>
|
||||||
|
<activeByDefault>false</activeByDefault>
|
||||||
|
</activation>
|
||||||
|
</profile>
|
||||||
|
<profile>
|
||||||
|
<id>prod</id>
|
||||||
|
<build>
|
||||||
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-war-plugin</artifactId>
|
||||||
|
<configuration>
|
||||||
|
<copyWebResources>false</copyWebResources>
|
||||||
|
<webResources>
|
||||||
|
<resource>
|
||||||
|
<directory>${basedir}/dist</directory>
|
||||||
|
</resource>
|
||||||
|
</webResources>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
|
</plugins>
|
||||||
|
</build>
|
||||||
|
</profile>
|
||||||
|
<profile>
|
||||||
|
<id>enable-version-in-url</id>
|
||||||
|
<properties>
|
||||||
|
<enable.version.in.url>true</enable.version.in.url>
|
||||||
|
</properties>
|
||||||
|
</profile>
|
||||||
|
</profiles>
|
||||||
|
</project>
|
||||||
23
frontend/preview.html
Normal file
|
|
@ -0,0 +1,23 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>Web BPM</title>
|
||||||
|
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
|
||||||
|
<link rel="icon" type="image/png" href="src/resources/img/logo.png"/>
|
||||||
|
<link rel="stylesheet" href="src/resources/css/style.css"/>
|
||||||
|
|
||||||
|
<script src="node_modules/core-js/client/shim.min.js"></script>
|
||||||
|
<script src="node_modules/zone.js/dist/zone.js"></script>
|
||||||
|
<script src="node_modules/reflect-metadata/Reflect.js"></script>
|
||||||
|
<script src="node_modules/systemjs/dist/system.src.js"></script>
|
||||||
|
<script src="systemjs.preview.config.js"></script>
|
||||||
|
<script>
|
||||||
|
System.import('preview').catch(function (err) {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
</head>
|
||||||
|
<body preview-container id="webbpm-angular-application-container ervu_lkrp_ul" class="webbpm">
|
||||||
|
<div class="progress"></div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
72
frontend/save.ts.metadata.js
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
#!/usr/bin/env node
|
||||||
|
var tsp = require("typescript-parser");
|
||||||
|
var fs = require('fs');
|
||||||
|
var path = require('path');
|
||||||
|
var ts = require("typescript");
|
||||||
|
|
||||||
|
var parser = new tsp.TypescriptParser();
|
||||||
|
var excludedDirs = [
|
||||||
|
'generated-sources'
|
||||||
|
];
|
||||||
|
|
||||||
|
var walkFileTree = function (dir, action) {
|
||||||
|
if (typeof action !== "function") {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs.readdirSync(dir).forEach(function (file) {
|
||||||
|
var path = dir + "/" + file;
|
||||||
|
var stat = fs.statSync(path);
|
||||||
|
var extension = ".ts";
|
||||||
|
if (stat && stat.isDirectory() && excludedDirs.indexOf(file) === -1) {
|
||||||
|
walkFileTree(path, action);
|
||||||
|
}
|
||||||
|
else if (path.indexOf(extension, path.length - extension.length) !== -1) {
|
||||||
|
action(null, path);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
var dateInLong = Date.now();
|
||||||
|
var arr = [];
|
||||||
|
|
||||||
|
var basePath = path.resolve(__dirname, "src/ts/");
|
||||||
|
walkFileTree(basePath, function (err, file) {
|
||||||
|
var content = fs.readFileSync(file).toString();
|
||||||
|
var jsonStructure = parser.parseTypescript(ts.createSourceFile(
|
||||||
|
file,
|
||||||
|
content,
|
||||||
|
ts.ScriptTarget.Latest,
|
||||||
|
true,
|
||||||
|
ts.ScriptKind.TS
|
||||||
|
),
|
||||||
|
'/');
|
||||||
|
jsonStructure['packageName'] = path.relative(path.resolve(__dirname, "src/ts/"),jsonStructure['filePath']);
|
||||||
|
jsonStructure['imports'].forEach( function (val) {
|
||||||
|
if (val.libraryName.startsWith(".")) {
|
||||||
|
val['libraryName'] = path.resolve(path.dirname(jsonStructure['filePath']), val['libraryName']);
|
||||||
|
val['libraryName'] = path.relative(path.resolve(__dirname, "src/ts/"), val['libraryName']);
|
||||||
|
val['libraryName'] = path.dirname(val['libraryName']).split(path.sep).join(".");
|
||||||
|
}
|
||||||
|
});
|
||||||
|
delete jsonStructure['filePath'];
|
||||||
|
jsonStructure['packageName'] = path.dirname(jsonStructure['packageName']).split(path.sep).join( ".");
|
||||||
|
arr.push(jsonStructure);
|
||||||
|
});
|
||||||
|
var cache = [];
|
||||||
|
|
||||||
|
fs.writeFileSync("./../.studio/typescript.metadata.json",
|
||||||
|
JSON.stringify(arr, function (key, value) {
|
||||||
|
if (typeof value === 'object' && value !== null) {
|
||||||
|
if (cache.indexOf(value) !== -1) {
|
||||||
|
// Circular reference found, discard key
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Store value in our collection
|
||||||
|
cache.push(value);
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}));
|
||||||
|
|
||||||
|
cache = null;
|
||||||
|
console.log("typescript parse time = " + (Date.now() - dateInLong));
|
||||||
19
frontend/src/resources/app-config.json
Normal file
|
|
@ -0,0 +1,19 @@
|
||||||
|
{
|
||||||
|
"electronic_sign.esmart_extension_url": "",
|
||||||
|
"electronic_sign.tsp_address": "",
|
||||||
|
"filter_cleanup_interval_hours": 720,
|
||||||
|
"filter_cleanup_check_period_minutes": 30,
|
||||||
|
"auth_method": "form",
|
||||||
|
"enable.version.in.url": "%enable.version.in.url%",
|
||||||
|
"guard.confirm_exit": false,
|
||||||
|
"message_service_error_timeout": "",
|
||||||
|
"message_service_warning_timeout": "",
|
||||||
|
"message_service_success_timeout": "",
|
||||||
|
"message_service_info_timeout": "",
|
||||||
|
"jivo_chat_widget_api_url": "https://code.jivo.ru/widget/{ID}",
|
||||||
|
"jivo_chat_widget_enabled": false,
|
||||||
|
"password_pattern": "^((?=(.*\\d){1,})(?=.*[a-zа-яё])(?=.*[A-ZА-ЯЁ]).{8,})$",
|
||||||
|
"password_pattern_error": "Пароль должен содержать заглавные или прописные буквы и как минимум 1 цифру",
|
||||||
|
"show.client.errors": false,
|
||||||
|
"available_task.single_fetch": true
|
||||||
|
}
|
||||||
1
frontend/src/resources/app.version
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
%project.version%
|
||||||
1569
frontend/src/resources/css/components-app.css
Normal file
351
frontend/src/resources/css/inbox-app.css
Normal file
|
|
@ -0,0 +1,351 @@
|
||||||
|
@font-face {
|
||||||
|
font-family: 'Segoe';
|
||||||
|
src: url('../fonts/Segoe.ttf');
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'SegoeSL';
|
||||||
|
src: url('../fonts/SegoeSL.ttf');
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'SegoeSB';
|
||||||
|
src: url('../fonts/SegoeSB.ttf');
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'SegoeB';
|
||||||
|
src: url('../fonts/SegoeB.ttf');
|
||||||
|
}
|
||||||
|
@font-face {
|
||||||
|
font-family: 'SegoeBL';
|
||||||
|
src: url('../fonts/SegoeBL.ttf');
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm a {
|
||||||
|
color: #1c92ea;
|
||||||
|
}
|
||||||
|
.webbpm a:hover,
|
||||||
|
.webbpm a:focus,
|
||||||
|
.webbpm a:active {
|
||||||
|
color: #1b84d2;
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.webbpm {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
color: #404954;
|
||||||
|
font-family: 'Segoe';
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .container {
|
||||||
|
padding: 70px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.webbpm [id="page"],
|
||||||
|
.webbpm .container .container-inside {
|
||||||
|
font-family: 'Segoe';
|
||||||
|
font-size: 16px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .logo {
|
||||||
|
height: auto;
|
||||||
|
width: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .header {
|
||||||
|
display: flex;
|
||||||
|
font-family: 'Segoe';
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
min-height: 70px;
|
||||||
|
border-bottom: 1px solid #f5f7fa;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0px 15px 20px 0px rgb(0 0 0 / 4%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .header-logo {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
margin-left: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .header-logo .logo a {
|
||||||
|
background: url('../../../src/resources/img/logo-full.png') no-repeat 0 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .header-menu {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .header > div > * {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .header-menu > * {
|
||||||
|
margin-right: 20px;
|
||||||
|
}
|
||||||
|
.webbpm .header-menu > *:last-child {
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .header-menu .nav-link {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: #fff;
|
||||||
|
font-size: 16px;
|
||||||
|
width: 30px;
|
||||||
|
height: 30px;
|
||||||
|
padding: 0;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 15px;
|
||||||
|
background-color: #404954;
|
||||||
|
outline: transparent;
|
||||||
|
}
|
||||||
|
.webbpm .header-menu .nav-link::after {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .header-menu .nav-link:hover,
|
||||||
|
.webbpm .header-menu .nav-link:focus,
|
||||||
|
.webbpm .header-menu .nav-link:active {
|
||||||
|
background-color: #1c92ea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .header-menu .logout .user-info {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
color: #000;
|
||||||
|
padding: 4px 20px;
|
||||||
|
background: transparent;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .header-menu .user-info > * {
|
||||||
|
display: flex;
|
||||||
|
padding-bottom: 10px;
|
||||||
|
margin: 0 0 10px 0;
|
||||||
|
border-bottom: 1px solid #f1f5f9;
|
||||||
|
}
|
||||||
|
.webbpm .header-menu .user-info > *:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .header-menu .user-info .user-fio {
|
||||||
|
padding-bottom: 0;
|
||||||
|
margin-bottom: 0;
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .header-menu .user-info .user-department {
|
||||||
|
color: #a0b1bc;
|
||||||
|
line-height: 1.2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .header .dropdown-menu.show {
|
||||||
|
top: 69px !important;
|
||||||
|
right: 0px !important;
|
||||||
|
left: auto !important;
|
||||||
|
transform: none !important;
|
||||||
|
margin: 0;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 0 0 10px 10px;
|
||||||
|
background-color: #fff;
|
||||||
|
box-shadow: 0 8px 12px rgb(77 72 91 / 5%), 0 6px 10px rgb(77 72 91 / 0%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .header .nav-item .dropdown-menu.show .dropdown-menu-inner {
|
||||||
|
max-height: calc(100vh - 140px);
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .header process .dropdown-menu.show,
|
||||||
|
.webbpm .header admin-menu .dropdown-menu.show {
|
||||||
|
top: 49px !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .header .logout .dropdown-menu.show {
|
||||||
|
width: 300px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .dropdown-menu-inner:hover {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .dropdown-item {
|
||||||
|
padding: 4px 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .dropdown-item:hover,
|
||||||
|
.webbpm .dropdown-item:focus,
|
||||||
|
.webbpm .dropdown-item:active {
|
||||||
|
color: #1c92ea;
|
||||||
|
background-color: transparent;
|
||||||
|
outline: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm footer {
|
||||||
|
color: #404954;
|
||||||
|
font-size: 14px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
padding: 15px 40px;
|
||||||
|
border-top: 1px solid #e3e6ed;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm footer a {
|
||||||
|
color: #404954;
|
||||||
|
}
|
||||||
|
.webbpm footer a:hover {
|
||||||
|
color: #1c92ea;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*-------------- Menu tasks -------------- */
|
||||||
|
.webbpm .task-list-tree-panel {
|
||||||
|
padding: 0 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .task-list-tree-panel .task-list-filter {
|
||||||
|
font-family: 'Segoe';
|
||||||
|
box-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .task-list-workplace {
|
||||||
|
padding: 20px 40px 0 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .task-list-tree-panel .task-list-filter li:first-of-type {
|
||||||
|
font-family: 'SegoeSB';
|
||||||
|
font-weight: normal;
|
||||||
|
padding-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .task-tbl .tr.task-ontime > .td.task::before,
|
||||||
|
.webbpm .task-tbl .tr.task-overdue > .td.task::before {
|
||||||
|
top: 24px;
|
||||||
|
background-color: #31c980;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .task-list-tree-panel .task-list-filter li.ontime label div {
|
||||||
|
background-color: #31c980;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .task-tbl .tr.task-overdue > .td.task::before,
|
||||||
|
.webbpm .task-list-tree-panel .task-list-filter li.overdue label div {
|
||||||
|
background-color: #1c92ea;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .task-tbl .thead {
|
||||||
|
display: table-header-group;
|
||||||
|
border: 0;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .task-tbl .th {
|
||||||
|
color: #404954;
|
||||||
|
font-family: 'SegoeSB';
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: normal;
|
||||||
|
padding: 9px 12px;
|
||||||
|
border: 0;
|
||||||
|
background: #f5f7fa;
|
||||||
|
}
|
||||||
|
.webbpm .task-tbl .th:first-child {
|
||||||
|
border-top-left-radius: 12px;
|
||||||
|
border-bottom-left-radius: 12px;
|
||||||
|
}
|
||||||
|
.webbpm .task-tbl .th:last-child {
|
||||||
|
border-top-right-radius: 12px;
|
||||||
|
border-bottom-right-radius: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .task-tbl .td {
|
||||||
|
color: #404954;
|
||||||
|
font-size: 16px;
|
||||||
|
padding: 16px 12px;
|
||||||
|
border: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .task-tbl .thead ~ .tr {
|
||||||
|
border-color: #e3e6ed;
|
||||||
|
border-width: 1px 0 0 0;
|
||||||
|
border-style: solid;
|
||||||
|
}
|
||||||
|
.webbpm .task-tbl .thead + .tr {
|
||||||
|
border-top-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .task-tbl .thead ~ .tr:hover {
|
||||||
|
border-top-color: transparent;
|
||||||
|
border-bottom-color: transparent;
|
||||||
|
}
|
||||||
|
.webbpm .task-tbl .thead ~ .tr:hover +.tr {
|
||||||
|
border-top-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .task-tbl .thead ~ .tr:hover .td {
|
||||||
|
background-color: #f9f9f9;
|
||||||
|
}
|
||||||
|
.webbpm .task-tbl .thead ~ .tr:hover .td:first-child {
|
||||||
|
border-top-left-radius: 12px;
|
||||||
|
border-bottom-left-radius: 12px;
|
||||||
|
}
|
||||||
|
.webbpm .task-tbl .thead ~ .tr:hover .td:last-child {
|
||||||
|
border-top-right-radius: 12px;
|
||||||
|
border-bottom-right-radius: 12px;
|
||||||
|
}
|
||||||
|
/*------------- end Menu tasks ----------- */
|
||||||
|
|
||||||
|
/*----------------- Login ---------------- */
|
||||||
|
.webbpm .form-signin,
|
||||||
|
.webbpm .form-signup,
|
||||||
|
.webbpm .confirm {
|
||||||
|
color: #404954;
|
||||||
|
width: 560px;
|
||||||
|
padding: 60px 80px;
|
||||||
|
margin: 30px auto;
|
||||||
|
border: 0;
|
||||||
|
border-radius: 15px;
|
||||||
|
box-shadow: 0px 0px 30px 2px rgb(77 72 91 / 12%);
|
||||||
|
background-color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .form-signin .row.title,
|
||||||
|
.webbpm .form-signup .row.title {
|
||||||
|
position: relative;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .form-signin h1,
|
||||||
|
.webbpm .form-signin h2,
|
||||||
|
.webbpm .form-signup h2,
|
||||||
|
.webbpm .confirm h2 {
|
||||||
|
font-family: 'SegoeB';
|
||||||
|
font-size: 32px;
|
||||||
|
text-align: left;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .form-signin .logo,
|
||||||
|
.webbpm .form-signup .logo,
|
||||||
|
.webbpm .confirm .logo {
|
||||||
|
position: absolute;
|
||||||
|
top: -10px;
|
||||||
|
right: 0;
|
||||||
|
width: 145px;
|
||||||
|
height: 40px;
|
||||||
|
background: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .form-signin .row.registration > * + *,
|
||||||
|
.webbpm .form-signin .login-btn-box .password,
|
||||||
|
.webbpm .form-signin .login-btn-box .btn + .btn {
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
/*--------------- end Login -------------- */
|
||||||
859
frontend/src/resources/css/structure.css
Normal file
|
|
@ -0,0 +1,859 @@
|
||||||
|
* {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
*, *:before, *:after {
|
||||||
|
-webkit-box-sizing: border-box;
|
||||||
|
-moz-box-sizing: border-box;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
body.webbpm {
|
||||||
|
color: #000;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.webbpm .form-signin label {
|
||||||
|
width: 160px;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .progress {
|
||||||
|
position: absolute;
|
||||||
|
width: 64px;
|
||||||
|
height: 64px;
|
||||||
|
margin-bottom: 0 !important;
|
||||||
|
background: url("../img/progress.gif") no-repeat 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm > .progress {
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
margin-top: -30px;
|
||||||
|
margin-left: -30px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .search-task-progress-bar {
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .modal-body .progress,
|
||||||
|
.webbpm .search-task-progress-bar .progress {
|
||||||
|
position: relative;
|
||||||
|
top: 0;
|
||||||
|
margin-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*-- common class --*/
|
||||||
|
.webbpm a {
|
||||||
|
color: #428bca;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .fl-left {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .fl-right {
|
||||||
|
float: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .anchor {
|
||||||
|
float: none;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm ul li, .webbpm ol li {
|
||||||
|
list-style: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm h1, .webbpm h2, .webbpm h3 {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm h1 {
|
||||||
|
font-size: 2.33em;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm h2 {
|
||||||
|
font-size: 2em;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm h3 {
|
||||||
|
font-size: 1.5em;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .table {
|
||||||
|
display: table;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .tr {
|
||||||
|
display: table-row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .td, .webbpm .th {
|
||||||
|
display: table-cell;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*-- layout --*/
|
||||||
|
html, body.webbpm {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
body.webbpm {
|
||||||
|
background-color: #f9f9fa;
|
||||||
|
font-family: Arial;
|
||||||
|
font-size: 14px;
|
||||||
|
min-height: 0;
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .wrapper {
|
||||||
|
height: 100%;
|
||||||
|
min-height: 100%;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .container {
|
||||||
|
width: 100%;
|
||||||
|
max-width: 100%;
|
||||||
|
height: auto;
|
||||||
|
margin: 0;
|
||||||
|
padding: 67px 0 0;
|
||||||
|
position: absolute;
|
||||||
|
top: 0px;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 50px;
|
||||||
|
border: 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .container [ng-include="taskPageFile"] {
|
||||||
|
position: relative;
|
||||||
|
min-height: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .container .container-inside {
|
||||||
|
font-family: Arial;
|
||||||
|
font-size: 14px;
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
overflow: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm footer {
|
||||||
|
position: absolute;
|
||||||
|
color: #000;
|
||||||
|
font-size: 12px;
|
||||||
|
bottom: 0;
|
||||||
|
left: 15px;
|
||||||
|
right: 15px;
|
||||||
|
height: 50px;
|
||||||
|
padding: 15px 0;
|
||||||
|
border-top: 1px solid #c1c1c1;
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm footer span + span {
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*--------- TOP MENU ----------*/
|
||||||
|
.webbpm .header {
|
||||||
|
position: absolute;
|
||||||
|
color: #fff;
|
||||||
|
font-family: Corbel;
|
||||||
|
font-size: 14px;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 67px;
|
||||||
|
min-height: 67px;
|
||||||
|
line-height: normal;
|
||||||
|
border: 0;
|
||||||
|
padding: 0;
|
||||||
|
background: #b9c0ca;
|
||||||
|
z-index: 997;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .logo {
|
||||||
|
display: inline-block;
|
||||||
|
margin: 4px 10px;
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .logo,
|
||||||
|
.webbpm .logo a {
|
||||||
|
margin: 0;
|
||||||
|
width: 200px;
|
||||||
|
height: 67px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .logo a {
|
||||||
|
position: absolute;
|
||||||
|
background: url("../img/logo.png") no-repeat 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .header .nav .nav-link {
|
||||||
|
color: #fff;
|
||||||
|
float: none;
|
||||||
|
display: block;
|
||||||
|
line-height: 60px;
|
||||||
|
padding: 0 15px 0 60px;
|
||||||
|
text-shadow: none;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .header .nav .nav-link:hover {
|
||||||
|
text-decoration: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .dropdown-menu {
|
||||||
|
background-color: #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .nav .nav-item .dropdown-menu:after {
|
||||||
|
border-bottom: 6px solid #eee;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .inner {
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (min-width: 768px) {
|
||||||
|
.webbpm .navbar .nav > * {
|
||||||
|
float: left;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 767px) {
|
||||||
|
.webbpm .dropdown-menu > div > button {
|
||||||
|
color: #d1dbe5;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/*--------- end - TOP MENU ----------*/
|
||||||
|
.webbpm .user-department,
|
||||||
|
.webbpm .user-info {
|
||||||
|
color: #5a6473;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .user-info > * {
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm [log-out] {
|
||||||
|
max-width: 40%;
|
||||||
|
margin-right: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .content {
|
||||||
|
padding: 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .inner {
|
||||||
|
min-height: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow-y : scroll;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*--------------task-list------------------*/
|
||||||
|
|
||||||
|
.task-list {
|
||||||
|
font-size: 0;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-list > div {
|
||||||
|
display: block;
|
||||||
|
float: left;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 100%;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-list-tree-panel {
|
||||||
|
width: 20%;
|
||||||
|
background: #e9edf2;
|
||||||
|
border-right: 1px solid #b9c1ca;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-list-tree-panel .task-list-filter {
|
||||||
|
position: relative;
|
||||||
|
margin-top: 15px;
|
||||||
|
z-index: 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-list-tree-panel .task-list-filter li {
|
||||||
|
position: relative;
|
||||||
|
padding: 8px 10px;
|
||||||
|
margin: 2px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-list-tree-panel .task-list-filter li::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
left: 0px;
|
||||||
|
bottom: 0px;
|
||||||
|
right: 0;
|
||||||
|
top: 0;
|
||||||
|
border: 1px solid rgb(206, 212, 219);
|
||||||
|
border-radius: 4px;
|
||||||
|
background-color: #fff;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-list-tree-panel .task-list-filter li label:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-list-tree-panel .task-list-filter li label {
|
||||||
|
position: relative;
|
||||||
|
width: 100%;
|
||||||
|
margin: 0;
|
||||||
|
z-index: 11;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-list-tree-panel .task-list-filter li label input[type="radio"] {
|
||||||
|
float: left;
|
||||||
|
margin-top: 2px;
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-list-tree-panel .task-list-filter li label span {
|
||||||
|
float: right;
|
||||||
|
background-color: #bbb;
|
||||||
|
padding: 0px 4px;
|
||||||
|
border-radius: 3px;
|
||||||
|
min-width: 25px;
|
||||||
|
text-align: center;
|
||||||
|
color: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-list-tree-panel .task-list-filter li.ontime label span {
|
||||||
|
background-color: #a0c367;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-list-tree-panel .task-list-filter li.overdue label span {
|
||||||
|
background-color: #fc2d2d;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-list-header {
|
||||||
|
border-bottom: 1px solid #b9c1ca;
|
||||||
|
background-color: #ccd6e0;
|
||||||
|
height: 30px;
|
||||||
|
line-height: 30px;
|
||||||
|
color: #565968;
|
||||||
|
font-size: 14px;
|
||||||
|
padding: 0 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.structure-box {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-list-workplace {
|
||||||
|
width: 80%;
|
||||||
|
padding: 15px 15px 0;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
/*--------------task-list end--------------*/
|
||||||
|
|
||||||
|
/*---------------table-list----------------*/
|
||||||
|
.task-tbl {
|
||||||
|
background: #fff;
|
||||||
|
width: 100%;
|
||||||
|
border-collapse: collapse;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-tbl .td, .task-tbl .th {
|
||||||
|
border: 1px solid #b9c1ca;
|
||||||
|
padding: 10px 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-tbl .th {
|
||||||
|
color: #565968;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-tbl .td {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-tbl .thead {
|
||||||
|
background: #ccd6e0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-tbl > .tr:hover {
|
||||||
|
cursor: pointer;
|
||||||
|
background: #e9edf2;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-tbl .tr.task-ontime > .td.task,
|
||||||
|
.task-tbl .tr.task-overdue > .td.task {
|
||||||
|
position: relative;
|
||||||
|
padding-left: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-tbl .tr.task-ontime > .td.task::before,
|
||||||
|
.task-tbl .tr.task-overdue > .td.task::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 12px;
|
||||||
|
left: 5px;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
background-color: #a0c367;
|
||||||
|
border-radius: 10px;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-tbl .tr.task-overdue > .td.task::before {
|
||||||
|
background-color: #ff0000;
|
||||||
|
}
|
||||||
|
/*----------------table-list end----------------*/
|
||||||
|
|
||||||
|
/*--------------Окно сообщения об ошибке--------------*/
|
||||||
|
.webbpm #toast-container {
|
||||||
|
font-size: 12px;
|
||||||
|
bottom: auto;
|
||||||
|
overflow-y: auto;
|
||||||
|
overflow-x: hidden;
|
||||||
|
max-height: 95%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm #toast-container .toast:hover {
|
||||||
|
box-shadow: 0 0 12px #999999;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm #toast-container .ngx-toastr {
|
||||||
|
min-width: 540px;
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm #toast-container .toast-error {
|
||||||
|
background-color: #d9534f;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm #toast-container .toast-close-button {
|
||||||
|
text-shadow: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .toast-message > div {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .toast-message > .active {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .toast-message > .active::after {
|
||||||
|
display: block;
|
||||||
|
content: "";
|
||||||
|
float: none;
|
||||||
|
clear: both;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .toast-message > .active a {
|
||||||
|
float: right;
|
||||||
|
margin: 5px 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .toast-message > .toast-msg-close.active ~ .toast-msg-text {
|
||||||
|
display: block;
|
||||||
|
font-size: 0.8em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .toast-message a:not([href]):not([tabindex]) {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
/*------------Окно сообщения об ошибке end------------*/
|
||||||
|
|
||||||
|
/*----------------- Ошибка 404 -------------------*/
|
||||||
|
.webbpm .container .task-not-found-page {
|
||||||
|
position: relative;
|
||||||
|
display: table;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .container .task-not-found-container {
|
||||||
|
display: table-cell;
|
||||||
|
color: #000;
|
||||||
|
font-size: 1.8em;
|
||||||
|
background-color: #c9d4e0;
|
||||||
|
text-align: center;
|
||||||
|
vertical-align: middle;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .container .task-not-found-container > div {
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .container .task-not-found-container > div:first-child {
|
||||||
|
color: #fff;
|
||||||
|
font-size: 7.8em;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .container .task-not-found-container > div:last-child {
|
||||||
|
text-align: left;
|
||||||
|
margin-left: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .container .task-not-found-container h2 {
|
||||||
|
font-size: 2em;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
.webbpm .container .task-not-found-container a {
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
/*--------------- end Ошибка 404 -----------------*/
|
||||||
|
|
||||||
|
/*-------------- MOBILE --------------*/
|
||||||
|
.webbpm.mobile .task-list-tree-panel,
|
||||||
|
.webbpm.mobile footer {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm.mobile .task-list-workplace {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm.mobile .container {
|
||||||
|
bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm.mobile form {
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm.mobile .form-signin {
|
||||||
|
width: auto;
|
||||||
|
padding: 20px;
|
||||||
|
margin: 40px 20px;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm.mobile .form-signin h1,
|
||||||
|
.webbpm.mobile .form-signin h2 {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm.mobile .form-signin label {
|
||||||
|
text-align: left;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm.mobile .form-signin input[type="text"],
|
||||||
|
.webbpm.mobile .form-signin input[type="password"] {
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm.mobile .form-signin .login-btn-box {
|
||||||
|
width: auto;
|
||||||
|
margin-right: 0;
|
||||||
|
}
|
||||||
|
/*-------------- end MOBILE --------------*/
|
||||||
|
|
||||||
|
|
||||||
|
/*-------------- НОВЫЙ ДИЗАЙН --------------*/
|
||||||
|
/*------------------ Фильтры ------------------*/
|
||||||
|
.webbpm .task-list {
|
||||||
|
height: auto;
|
||||||
|
min-height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .task-list > .task-list-tree-panel {
|
||||||
|
background: #fff;
|
||||||
|
border-right: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .task-list > .task-list-tree-panel,
|
||||||
|
.webbpm .task-list > .task-list-workplace {
|
||||||
|
font-size: 14px;
|
||||||
|
float: none;
|
||||||
|
width: 100%;
|
||||||
|
height: auto;
|
||||||
|
min-height: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .task-list-tree-panel .task-list-filter {
|
||||||
|
font-family: Corbel;
|
||||||
|
margin: 0;
|
||||||
|
box-shadow: 0px 4px 10px -5px rgba(40, 40, 40, 0.3);
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .task-list-tree-panel .task-list-filter ul {
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .task-list-tree-panel .task-list-filter li {
|
||||||
|
display: inline-block;
|
||||||
|
padding: 12px 0px;
|
||||||
|
margin: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .task-list-tree-panel .task-list-filter li:first-of-type {
|
||||||
|
font-size: 1.4em;
|
||||||
|
font-weight: bold;
|
||||||
|
width: 197px;
|
||||||
|
padding: 0 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .task-list-tree-panel .task-list-filter li::before {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .task-list-tree-panel .task-list-filter li label {
|
||||||
|
font-size: 1.3em;
|
||||||
|
font-weight: normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .task-list-tree-panel .task-list-filter li label div {
|
||||||
|
display: inline-block;
|
||||||
|
width: 10px;
|
||||||
|
height: 10px;
|
||||||
|
border-radius: 10px;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .task-list-tree-panel .task-list-filter li.ontime label div {
|
||||||
|
background-color: #2da6a1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .task-list-tree-panel .task-list-filter li.overdue label div {
|
||||||
|
background-color: #9c5d7a;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .task-list-tree-panel .task-list-filter li label input[type="radio"] {
|
||||||
|
float: none;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .task-list-tree-panel .task-list-filter li label span {
|
||||||
|
float: none;
|
||||||
|
color: #000;
|
||||||
|
font-weight: normal;
|
||||||
|
text-align: left;
|
||||||
|
min-width: auto;
|
||||||
|
padding: 5px 15px;
|
||||||
|
border-radius: 0;
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
.task-list-tree-panel .task-list-filter li label input[type="radio"].ng-valid-parse ~ span {
|
||||||
|
background-color: #eaedf2;
|
||||||
|
|
||||||
|
}
|
||||||
|
/*-------------- end НОВЫЙ ДИЗАЙН --------------*/
|
||||||
|
|
||||||
|
.webbpm .dialog-stack-trace {
|
||||||
|
overflow-x: auto;
|
||||||
|
overflow-y: auto;
|
||||||
|
max-height: 300px;
|
||||||
|
}
|
||||||
|
.webbpm .dialog-show-button {
|
||||||
|
text-decoration: underline;
|
||||||
|
cursor: pointer;
|
||||||
|
}
|
||||||
|
.webbpm .dialog-error-number {
|
||||||
|
font-size: 1.25rem;
|
||||||
|
}
|
||||||
|
.webbpm .dialog-error-title {
|
||||||
|
font-size: 1rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*-- login --*/
|
||||||
|
.webbpm .form-signin,
|
||||||
|
.webbpm .form-signup,
|
||||||
|
.webbpm .confirm {
|
||||||
|
color: #333;
|
||||||
|
width: 580px;
|
||||||
|
padding: 80px 100px;
|
||||||
|
margin: 20px auto;
|
||||||
|
border: 1px solid #b2b6b8;
|
||||||
|
background: #fff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .form-signin.esia {
|
||||||
|
width: 450px;
|
||||||
|
text-align: center;
|
||||||
|
padding: 45px 55px 35px 55px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .form-signin h1,
|
||||||
|
.webbpm .form-signin h2,
|
||||||
|
.webbpm .form-signup h2,
|
||||||
|
.webbpm .confirm h2 {
|
||||||
|
text-align: center;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .form-signin.esia h1,
|
||||||
|
.webbpm .form-signin.esia h2 {
|
||||||
|
margin-left: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .form-signin label,
|
||||||
|
.webbpm .form-signup label {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .form-signin input {
|
||||||
|
width: 240px;
|
||||||
|
font-size: 14px;
|
||||||
|
display: inline-block;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .form-signin.esia input {
|
||||||
|
width: 160px;
|
||||||
|
margin-top: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .form-signin .row,
|
||||||
|
.webbpm .form-signup .row {
|
||||||
|
display: flex;
|
||||||
|
margin: 0 0 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .registration-link,
|
||||||
|
.webbpm .login-link {
|
||||||
|
margin-right: 20px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .form-signin .row.registration {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .form-signin .login-btn-box {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .form-signin .row.registration > * + *,
|
||||||
|
.webbpm .form-signin .login-btn-box .password,
|
||||||
|
.webbpm .form-signin .login-btn-box .btn + .btn {
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .input-group > .input-group-append > .input-group-text {
|
||||||
|
border-radius: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .form-signin .register-btn-box,
|
||||||
|
.webbpm .form-signup .register-btn-box,
|
||||||
|
.webbpm .form-signup .reset-password-btn-box {
|
||||||
|
width: 220px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .form-signup .row international-phone-number .flagInput .btn {
|
||||||
|
border-left: 1px solid #c6cdd3;
|
||||||
|
}
|
||||||
|
.webbpm .form-signup .row international-phone-number .flagInput ~ input {
|
||||||
|
border-left: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .form-signin .has-error .help-block {
|
||||||
|
padding-left: 125px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .form-signup .has-account a {
|
||||||
|
|
||||||
|
margin-left: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/*------------------ Формы регистрации и подтверждения ------------------*/
|
||||||
|
.form-signup .has-account {
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-signup .has-account a span,
|
||||||
|
.confirm a span {
|
||||||
|
margin-right: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-signup .dropbtn.btn {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-signup .input-group-text {
|
||||||
|
border-left-width: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-signup input.ng-invalid.ng-touched,
|
||||||
|
.form-signup input.ng-invalid.ng-dirty {
|
||||||
|
border-color: red !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-signup .msg-alert {
|
||||||
|
color: red;
|
||||||
|
font-size: 11px;
|
||||||
|
padding: 3px 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-signup .consent {
|
||||||
|
color: #929292;
|
||||||
|
font-size: 13px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
/*------------------ End - Формы регистрации и подтверждения ------------------*/
|
||||||
|
|
||||||
|
/*------------------ Сообщения об ошибке ------------------*/
|
||||||
|
.webbpm .error_message {
|
||||||
|
width: 650px;
|
||||||
|
margin: 0 auto;
|
||||||
|
margin-top: 10%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .error_title {
|
||||||
|
position: relative;
|
||||||
|
color: #9c5d7a;
|
||||||
|
font-size: 5.5em;
|
||||||
|
font-weight: bold;
|
||||||
|
margin-left: 100px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
.webbpm .error_title::before {
|
||||||
|
position: absolute;
|
||||||
|
content: "";
|
||||||
|
left: -100px;
|
||||||
|
width: 75px;
|
||||||
|
height: 75px;
|
||||||
|
border-radius: 40px;
|
||||||
|
background-color: #9c5d7a;
|
||||||
|
background-image: url("../img/access_denied.png");
|
||||||
|
background-repeat: no-repeat;
|
||||||
|
background-position: 50% 50%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .error_title_long {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
font-size: 2.5em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.webbpm .error_body {
|
||||||
|
font-size: 2em;
|
||||||
|
line-height: 1.2;
|
||||||
|
margin: 5px 0 0 100px;
|
||||||
|
}
|
||||||
|
/*---------------- end Сообщения об ошибке ---------------*/
|
||||||
|
/*-------------- Поле телефона ------------ */
|
||||||
|
.flag {
|
||||||
|
background-image: url('./../img/country-flags.jpg') !important;
|
||||||
|
}
|
||||||
|
/*-------------- end Поле телефона ------------ */
|
||||||
|
|
||||||
10
frontend/src/resources/css/style.css
Normal file
|
|
@ -0,0 +1,10 @@
|
||||||
|
@import "../../../node_modules/angular-calendar/css/angular-calendar.css";
|
||||||
|
@import "../../../node_modules/bootstrap/dist/css/bootstrap-grid.css";
|
||||||
|
@import "../../../node_modules/bootstrap/dist/css/bootstrap-reboot.css";
|
||||||
|
@import "../../../node_modules/bootstrap/dist/css/bootstrap.css";
|
||||||
|
@import "../../../node_modules/bootstrap-icons/font/bootstrap-icons.css";
|
||||||
|
@import "../../../node_modules/font-awesome/css/font-awesome.css";
|
||||||
|
@import "../../../node_modules/@webbpm/base-package/css/style.css";
|
||||||
|
@import "structure.css";
|
||||||
|
@import "inbox-app.css";
|
||||||
|
@import "components-app.css";
|
||||||
BIN
frontend/src/resources/fonts/Segoe.ttf
Normal file
BIN
frontend/src/resources/fonts/SegoeB.ttf
Normal file
BIN
frontend/src/resources/fonts/SegoeBL.ttf
Normal file
BIN
frontend/src/resources/fonts/SegoeSB.ttf
Normal file
BIN
frontend/src/resources/fonts/SegoeSL.ttf
Normal file
BIN
frontend/src/resources/img/access_denied.png
Normal file
|
After Width: | Height: | Size: 855 B |
BIN
frontend/src/resources/img/admin.png
Normal file
|
After Width: | Height: | Size: 811 B |
BIN
frontend/src/resources/img/country-flags.jpg
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
frontend/src/resources/img/create.png
Normal file
|
After Width: | Height: | Size: 673 B |
BIN
frontend/src/resources/img/logo-full.png
Normal file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
frontend/src/resources/img/logo.png
Normal file
|
After Width: | Height: | Size: 2.3 KiB |
BIN
frontend/src/resources/img/progress.gif
Normal file
|
After Width: | Height: | Size: 6.6 KiB |
BIN
frontend/src/resources/img/project.png
Normal file
|
After Width: | Height: | Size: 712 B |
BIN
frontend/src/resources/img/tasks.png
Normal file
|
After Width: | Height: | Size: 351 B |
|
|
@ -0,0 +1,4 @@
|
||||||
|
<div class="error_message">
|
||||||
|
<div class="error_title">403</div>
|
||||||
|
<div class="error_body">Доступ запрещен</div>
|
||||||
|
</div>
|
||||||
|
|
@ -0,0 +1,34 @@
|
||||||
|
<div *ngIf="(currentSession | async)?.authorities.includes('BPMN.ADMIN.PROCESS_INSTANCE.LIST') ||
|
||||||
|
(currentSession | async)?.authorities.includes('USER_MANAGEMENT.USER.LIST') ||
|
||||||
|
(currentSession | async)?.authorities.includes('USER_MANAGEMENT.GROUP.LIST') ||
|
||||||
|
(currentSession | async)?.authorities.includes('USER_MANAGEMENT.ROLE.LIST') ||
|
||||||
|
(currentSession | async)?.authorities.includes('USER_MANAGEMENT.ORG_UNIT.LIST') ||
|
||||||
|
(currentSession | async)?.authorities.includes('USER_MANAGEMENT.AUTHORITY.LIST')" ngbDropdown class="nav-item" [placement]="placement">
|
||||||
|
<button class="nav-link bi bi-gear-fill" id="adminDropdownMenu" ngbDropdownToggle title="Администрирование"></button>
|
||||||
|
<div ngbDropdownMenu aria-labelledby="adminDropdownMenu">
|
||||||
|
<button *ngIf="(currentSession | async)?.authorities.includes('BPMN.ADMIN.PROCESS_INSTANCE.LIST')"
|
||||||
|
routerLink="/process/instance" ngbDropdownItem>
|
||||||
|
Экземпляры процессов
|
||||||
|
</button>
|
||||||
|
<button *ngIf="(currentSession | async)?.authorities.includes('USER_MANAGEMENT.USER.LIST')"
|
||||||
|
routerLink="/user-management/users" ngbDropdownItem>
|
||||||
|
Пользователи
|
||||||
|
</button>
|
||||||
|
<button *ngIf="(currentSession | async)?.authorities.includes('USER_MANAGEMENT.GROUP.LIST')"
|
||||||
|
routerLink="/user-management/groups" ngbDropdownItem>
|
||||||
|
Группы
|
||||||
|
</button>
|
||||||
|
<button *ngIf="(currentSession | async)?.authorities.includes('USER_MANAGEMENT.ROLE.LIST')"
|
||||||
|
routerLink="/user-management/roles" ngbDropdownItem>
|
||||||
|
Роли
|
||||||
|
</button>
|
||||||
|
<button *ngIf="(currentSession | async)?.authorities.includes('USER_MANAGEMENT.ORG_UNIT.LIST')"
|
||||||
|
routerLink="/user-management/org-units" ngbDropdownItem>
|
||||||
|
Организации
|
||||||
|
</button>
|
||||||
|
<button *ngIf="(currentSession | async)?.authorities.includes('USER_MANAGEMENT.AUTHORITY.LIST')"
|
||||||
|
routerLink="/user-management/authorities" ngbDropdownItem>
|
||||||
|
Безопасность действий
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
@ -0,0 +1,10 @@
|
||||||
|
<footer>
|
||||||
|
<div id="webbpm-footer">
|
||||||
|
<div class="fl-left"><span>Web-BPM</span></div>
|
||||||
|
<div class="fl-right">
|
||||||
|
<span><a href="mailto:info@micord.ru">Поддержка</a></span>
|
||||||
|
<span><a href="http://www.micord.ru" target="_blank">Компания "Микорд"</a></span>
|
||||||
|
<span><application-version></application-version></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</footer>
|
||||||
|
|
@ -0,0 +1,13 @@
|
||||||
|
<nav class="header" *ngIf="currentSession | async as session" id="webbpm-header">
|
||||||
|
<div class="header-logo">
|
||||||
|
<div class="logo"><a routerLink="/"></a></div>
|
||||||
|
</div>
|
||||||
|
<div class="header-menu">
|
||||||
|
<process *ngIf="session.authorities.includes('BPMN.USER.START_PROCESS')"></process>
|
||||||
|
<div *ngIf="session.authorities.includes('BPMN.USER.TASK_LIST')">
|
||||||
|
<button class="nav-link bi bi-file-text-fill" (click)="openTaskList()" title="Задачи"></button>
|
||||||
|
</div>
|
||||||
|
<admin-menu [placement]="'bottom'"></admin-menu>
|
||||||
|
<div ngbDropdown class="logout" log-out></div>
|
||||||
|
</div>
|
||||||
|
</nav>
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
<span id="version-footer">Версия: {{applicationVersion}}</span>
|
||||||
|
|
@ -0,0 +1,27 @@
|
||||||
|
<div class="confirm">
|
||||||
|
<div class="form-logo">
|
||||||
|
<div></div>
|
||||||
|
</div>
|
||||||
|
<div class="info">
|
||||||
|
<div>
|
||||||
|
<h2>Подтверждение почты</h2>
|
||||||
|
|
||||||
|
<div *ngIf="verificationStatus.toString() === 'VERIFYING'">
|
||||||
|
Подтверждение...
|
||||||
|
</div>
|
||||||
|
<div *ngIf="verificationStatus.toString() === 'VERIFIED'">
|
||||||
|
<div class="alert alert-success">
|
||||||
|
Адрес электронной почты успешно подтвержден
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="verificationStatus.toString() === 'FAILED'">
|
||||||
|
<div class="alert alert-danger">{{ errorMessage }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div *ngIf="(currentSession | async) == null">
|
||||||
|
<a href="#/login"><span class="fa fa-lock"></span>Войти</a><br/>
|
||||||
|
<a href="#/registration">Зарегистрироваться</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
<button class="nav-link bi bi-person-fill" ngbDropdownToggle title="Пользователь"></button>
|
||||||
|
<div ngbDropdownMenu *ngIf="currentSession | async as session">
|
||||||
|
<div class="user-info">
|
||||||
|
<div class="user-fio">{{session.fullUserName}}</div>
|
||||||
|
<div class="user-login">{{session.username}}</div>
|
||||||
|
<div class="user-department">{{getOrgUnitName()}}</div>
|
||||||
|
</div>
|
||||||
|
<button ngbDropdownItem *ngIf="isLogoutButtonVisible()" (click)="logout()">Выход</button>
|
||||||
|
</div>
|
||||||
51
frontend/src/resources/template/app/component/login.html
Normal file
|
|
@ -0,0 +1,51 @@
|
||||||
|
<div class="form-signin">
|
||||||
|
<form #formComponent="ngForm">
|
||||||
|
<div class="alert alert-success" [hidden]="!confirmationSent">На ваш почтовый адрес было
|
||||||
|
отправлено письмо. Подтвердите почту, чтобы войти в систему
|
||||||
|
</div>
|
||||||
|
<div class="alert alert-danger" [hidden]="!errorMessage">{{errorMessage}}</div>
|
||||||
|
|
||||||
|
<h2>Вход</h2>
|
||||||
|
<div class="row registration">
|
||||||
|
<span>Еще нет аккаунта?</span><a href="#/registration">Зарегистрироваться</a>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<label>Пользователь</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input type="text" name="username" class="form-control" placeholder="Пользователь" required autofocus
|
||||||
|
[(ngModel)]="username" maxlength="100">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<label>Пароль</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input
|
||||||
|
[type]="passwordType ? 'text' : 'password'"
|
||||||
|
name="password"
|
||||||
|
class="form-control field-password-view"
|
||||||
|
placeholder="" required
|
||||||
|
[(ngModel)]="password"
|
||||||
|
maxlength="100"
|
||||||
|
>
|
||||||
|
<div class="input-group-append">
|
||||||
|
<span class="input-group-text">
|
||||||
|
<i
|
||||||
|
(click)="togglePasswordType()"
|
||||||
|
class="fa"
|
||||||
|
[ngClass]="{
|
||||||
|
'fa-eye': passwordType,
|
||||||
|
'fa-eye-slash': !passwordType
|
||||||
|
}"
|
||||||
|
></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="login-btn-box">
|
||||||
|
<!--<esia-login-button></esia-login-button>-->
|
||||||
|
<button type="submit" class="btn btn-primary" (click)="formComponent.form.valid && login()">Войти</button>
|
||||||
|
<div class="password"><a href="#/reset-password">Забыли пароль?</a></div>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
103
frontend/src/resources/template/app/component/new_password.html
Normal file
|
|
@ -0,0 +1,103 @@
|
||||||
|
<div class="form-signup">
|
||||||
|
<div class="form-logo">
|
||||||
|
<div></div>
|
||||||
|
</div>
|
||||||
|
<div class="form-new-password">
|
||||||
|
<form #formComponent="ngForm">
|
||||||
|
<div [hidden]="!errorMessage" class="alert alert-danger">{{ errorMessage }}</div>
|
||||||
|
<p class="has-account">Вспомнили пароль?
|
||||||
|
<a href="#/login"><span class="fa fa-lock"></span>Войти</a></p>
|
||||||
|
|
||||||
|
<p class="has-account">Задайте новый пароль</p>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<label>Пароль</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input
|
||||||
|
#passwordInput="ngModel"
|
||||||
|
[(ngModel)]="password"
|
||||||
|
[type]="passwordType ? 'text' : 'password'"
|
||||||
|
class="form-control"
|
||||||
|
maxlength="32"
|
||||||
|
minlength="6"
|
||||||
|
name="password"
|
||||||
|
pattern="^(?=.*[a-zA-Z])(?=.*[0-9])[a-zA-Z0-9]+$"
|
||||||
|
required
|
||||||
|
(change)="validPasswords()"
|
||||||
|
>
|
||||||
|
<div class="input-group-append">
|
||||||
|
<span class="input-group-text">
|
||||||
|
<i
|
||||||
|
(click)="togglePasswordType()"
|
||||||
|
class="fa"
|
||||||
|
[ngClass]="{
|
||||||
|
'fa-eye': passwordType,
|
||||||
|
'fa-eye-slash': !passwordType
|
||||||
|
}"
|
||||||
|
></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="passwordInput.invalid && (passwordInput.dirty || passwordInput.touched)">
|
||||||
|
<div *ngIf="passwordInput.errors.required" class="msg-alert">Поле обязательно
|
||||||
|
</div>
|
||||||
|
<div *ngIf="passwordInput.errors.minlength" class="msg-alert">Пароль должен
|
||||||
|
содержать как минимум 6 символов
|
||||||
|
</div>
|
||||||
|
<div *ngIf="passwordInput.errors.pattern" class="msg-alert">Пароль должен
|
||||||
|
содержать заглавные и прописные буквы и как минимум 1 цифру
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<label>Подтверждение пароля</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input
|
||||||
|
#confirmPasswordInput="ngModel"
|
||||||
|
[(ngModel)]="confirmPassword"
|
||||||
|
[type]="confirmPasswordType ? 'text' : 'password'"
|
||||||
|
class="form-control"
|
||||||
|
maxlength="32"
|
||||||
|
minlength="6"
|
||||||
|
name="confirmPassword"
|
||||||
|
pattern="^(?=.*[a-zA-Z])(?=.*[0-9])[a-zA-Z0-9]+$"
|
||||||
|
required
|
||||||
|
(change)="validPasswords()"
|
||||||
|
>
|
||||||
|
<div class="input-group-append">
|
||||||
|
<span class="input-group-text">
|
||||||
|
<i
|
||||||
|
(click)="toggleConfirmPasswordType()"
|
||||||
|
class="fa"
|
||||||
|
[ngClass]="{
|
||||||
|
'fa-eye': confirmPasswordType,
|
||||||
|
'fa-eye-slash': !confirmPasswordType
|
||||||
|
}"
|
||||||
|
></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="confirmPasswordInput.invalid && (confirmPasswordInput.dirty || confirmPasswordInput.touched)">
|
||||||
|
<div *ngIf="confirmPasswordInput.errors.required" class="msg-alert">Поле обязательно
|
||||||
|
</div>
|
||||||
|
<div *ngIf="confirmPasswordInput.errors.minlength" class="msg-alert">Пароль должен
|
||||||
|
содержать как минимум 6 символов
|
||||||
|
</div>
|
||||||
|
<div *ngIf="confirmPasswordInput.errors.pattern" class="msg-alert">Пароль должен
|
||||||
|
содержать заглавные и прописные буквы и как минимум 1 цифру
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="reset-password-btn-box">
|
||||||
|
<button
|
||||||
|
(click)="formComponent.form.valid && validPasswords() && changePassword()"
|
||||||
|
[disabled]="!formComponent.form.valid && !validPasswords()"
|
||||||
|
class="btn btn-primary"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
Изменить пароль
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
<div ngbDropdown class="nav-item">
|
||||||
|
<button class="nav-link bi bi-clipboard-plus-fill" id="startProcessDropdownMenu" ngbDropdownToggle title="Создать"></button>
|
||||||
|
<div ngbDropdownMenu aria-labelledby="startProcessDropdownMenu">
|
||||||
|
<div class="dropdown-menu-inner">
|
||||||
|
<div *ngFor="let process of processList">
|
||||||
|
<button (click)="startProcess(process.processDefId)" ngbDropdownItem>
|
||||||
|
{{ process.name }}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
</div><!-- TODO: move to directive or something else -->
|
||||||
|
|
@ -0,0 +1,3 @@
|
||||||
|
<div class="modal-body">
|
||||||
|
<div class="progress"></div>
|
||||||
|
</div>
|
||||||
121
frontend/src/resources/template/app/component/register.html
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
<div class="form-signup">
|
||||||
|
<div class="form-logo">
|
||||||
|
<div></div>
|
||||||
|
</div>
|
||||||
|
<div class="form-register">
|
||||||
|
<form #formComponent="ngForm">
|
||||||
|
<div [hidden]="!errorMessage" class="alert alert-danger">{{ errorMessage }}</div>
|
||||||
|
<h2>Регистрация</h2>
|
||||||
|
<p class="has-account">Уже зарегистрированы?
|
||||||
|
<a href="#/login"><span class="fa fa-lock"></span>Войти</a></p>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<label>Имя</label>
|
||||||
|
<input
|
||||||
|
#name="ngModel"
|
||||||
|
[(ngModel)]="username"
|
||||||
|
class="form-control"
|
||||||
|
maxlength="100"
|
||||||
|
name="username"
|
||||||
|
required
|
||||||
|
type="text"
|
||||||
|
>
|
||||||
|
<div *ngIf="name.invalid && (name.dirty || name.touched)">
|
||||||
|
<div *ngIf="name.errors.required" class="msg-alert">Поле обязательно</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<label>Адрес эл. почты</label>
|
||||||
|
<input
|
||||||
|
#emailInput="ngModel"
|
||||||
|
[(ngModel)]="email"
|
||||||
|
class="form-control"
|
||||||
|
email
|
||||||
|
maxlength="100"
|
||||||
|
name="email"
|
||||||
|
required
|
||||||
|
type="email"
|
||||||
|
>
|
||||||
|
<div *ngIf="emailInput.invalid && (emailInput.dirty || emailInput.touched)">
|
||||||
|
<div *ngIf="emailInput.errors.required" class="msg-alert">Поле обязательно</div>
|
||||||
|
<div *ngIf="emailInput.errors.email" class="msg-alert">Неверный формат адреса
|
||||||
|
эл. почты
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<label>Номер телефона</label>
|
||||||
|
<international-phone-number
|
||||||
|
#phoneInput="ngModel"
|
||||||
|
[(ngModel)]="phoneNumber"
|
||||||
|
[defaultCountry]="'ru'"
|
||||||
|
[pattern]="'^\\+(?!7 ?\\d{11})[0-9 ]+$'"
|
||||||
|
maxlength="20"
|
||||||
|
minlength="8"
|
||||||
|
name="phoneNumber"
|
||||||
|
placeholder="+79991112233"
|
||||||
|
(focusout)="phoneInputFocusOut()"
|
||||||
|
required
|
||||||
|
></international-phone-number>
|
||||||
|
<div *ngIf="phoneInput.invalid && (phoneInput.dirty || phoneIsTouched)">
|
||||||
|
<div *ngIf="phone.selectedCountry">
|
||||||
|
<div *ngIf="phoneHasOnlyDialCode()" class="msg-alert">Поле обязательно</div>
|
||||||
|
<div *ngIf="!phoneHasOnlyDialCode()" class="msg-alert">Введите корректный номер</div>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="!phone.selectedCountry">
|
||||||
|
<div *ngIf="phoneInput.errors.required" class="msg-alert">Поле обязательно</div>
|
||||||
|
<div *ngIf="!phoneInput.errors.required" class="msg-alert">Введите код страны</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="row">
|
||||||
|
<label>Пароль</label>
|
||||||
|
<div class="input-group">
|
||||||
|
<input
|
||||||
|
#passwordInput="ngModel"
|
||||||
|
[(ngModel)]="password"
|
||||||
|
[type]="fieldType ? 'text' : 'password'"
|
||||||
|
class="form-control"
|
||||||
|
maxlength="32"
|
||||||
|
minlength="8"
|
||||||
|
name="password"
|
||||||
|
[pattern]="passwordPattern"
|
||||||
|
required
|
||||||
|
>
|
||||||
|
<div class="input-group-append">
|
||||||
|
<span class="input-group-text">
|
||||||
|
<i
|
||||||
|
(click)="toggleFieldType()"
|
||||||
|
class="fa"
|
||||||
|
[ngClass]="{
|
||||||
|
'fa-eye': fieldType,
|
||||||
|
'fa-eye-slash': !fieldType
|
||||||
|
}"
|
||||||
|
></i>
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="passwordInput.invalid && (passwordInput.dirty || passwordInput.touched)">
|
||||||
|
<div *ngIf="passwordInput.errors.required" class="msg-alert">Поле обязательно
|
||||||
|
</div>
|
||||||
|
<div *ngIf="passwordInput.errors.minlength" class="msg-alert">Пароль должен
|
||||||
|
содержать как минимум 8 символов
|
||||||
|
</div>
|
||||||
|
<div *ngIf="passwordInput.errors.pattern" class="msg-alert" [innerText]="passwordPatternErrorMessage">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="register-btn-box">
|
||||||
|
<button
|
||||||
|
(click)="formComponent.form.valid && register()"
|
||||||
|
[disabled]="!formComponent.form.valid"
|
||||||
|
class="btn btn-primary"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
Зарегистрироваться
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<div *ngIf="consent" [innerHTML]="consent" class="consent"></div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
@ -0,0 +1,45 @@
|
||||||
|
<div class="form-signup">
|
||||||
|
<div class="form-logo">
|
||||||
|
<div></div>
|
||||||
|
</div>
|
||||||
|
<div class="form-reset-password">
|
||||||
|
<form #formComponent="ngForm">
|
||||||
|
<div [hidden]="!errorMessage" class="alert alert-danger">{{ errorMessage }}</div>
|
||||||
|
<p class="has-account">Вспомнили пароль?
|
||||||
|
<a href="#/login"><span class="fa fa-lock"></span>Войти</a></p>
|
||||||
|
|
||||||
|
<p class="has-account">Укажите адрес эл. почты, который был указан при регистрации,
|
||||||
|
на него пришлем временный пароль. Пароль сможете поменять в личном кабинете.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<div class="row">
|
||||||
|
<label>Адрес эл. почты</label>
|
||||||
|
<input
|
||||||
|
#emailInput="ngModel"
|
||||||
|
[(ngModel)]="email"
|
||||||
|
class="form-control"
|
||||||
|
email
|
||||||
|
maxlength="100"
|
||||||
|
name="email"
|
||||||
|
required
|
||||||
|
type="email"
|
||||||
|
>
|
||||||
|
<div *ngIf="emailInput.invalid && (emailInput.dirty || emailInput.touched)">
|
||||||
|
<div *ngIf="emailInput.errors.required" class="msg-alert">Поле обязательно</div>
|
||||||
|
<div *ngIf="emailInput.errors.email" class="msg-alert">Неверный формат адреса эл. почты
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="reset-password-btn-box">
|
||||||
|
<button
|
||||||
|
(click)="formComponent.form.valid && resetPassword()"
|
||||||
|
[disabled]="!formComponent.form.valid"
|
||||||
|
class="btn btn-primary"
|
||||||
|
type="submit"
|
||||||
|
>
|
||||||
|
Восстановить
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
3
frontend/src/resources/template/app/component/task.html
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
<div id="page">
|
||||||
|
<router-outlet></router-outlet>
|
||||||
|
</div>
|
||||||
49
frontend/src/resources/template/app/component/task_list.html
Normal file
|
|
@ -0,0 +1,49 @@
|
||||||
|
<div class="inner">
|
||||||
|
<div class="task-list">
|
||||||
|
<div class="task-list-tree-panel">
|
||||||
|
<div class="task-list-filter">
|
||||||
|
<ul>
|
||||||
|
<li>Фильтры</li>
|
||||||
|
<li>
|
||||||
|
<label>
|
||||||
|
<input type="radio" name="task-filter" value="All" [(ngModel)]="showMode" (ngModelChange)="filterVisibleTasks()">
|
||||||
|
<span>Все [{{tasks.length}}]</span>
|
||||||
|
</label></li>
|
||||||
|
<li class="ontime">
|
||||||
|
<label>
|
||||||
|
<input type="radio" name="task-filter" value="OnTime" [(ngModel)]="showMode" (ngModelChange)="filterVisibleTasks()">
|
||||||
|
<span><div></div>Без превышения срока [{{onTimeTasks.length}}]</span>
|
||||||
|
</label></li>
|
||||||
|
<li class="overdue">
|
||||||
|
<label>
|
||||||
|
<input type="radio" name="task-filter" value="Overdue" [(ngModel)]="showMode" (ngModelChange)="filterVisibleTasks()">
|
||||||
|
<span><div></div>С превышением срока [{{overdueTasks.length}}]</span>
|
||||||
|
</label></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="task-list-workplace">
|
||||||
|
<div class="alert alert-danger" [hidden]="!errorMessage">{{errorMessage}}</div>
|
||||||
|
<div class="table task-tbl">
|
||||||
|
<div class="thead">
|
||||||
|
<div class="tr">
|
||||||
|
<div class="th">Процесс</div>
|
||||||
|
<div class="th">Версия</div>
|
||||||
|
<div class="th">Задача</div>
|
||||||
|
<div class="th">Дата создания</div>
|
||||||
|
<div class="th">Срок</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div *ngFor="let task of visibleTasks"
|
||||||
|
class="tr" [ngClass]="{'task-overdue': isOverdue(task), 'task-ontime': isOnTime(task)}"
|
||||||
|
(click)="startTask(task)">
|
||||||
|
<div class="td">{{ task.processName }}</div>
|
||||||
|
<div class="td">{{ task.processVersion }}</div>
|
||||||
|
<div class="td task">{{ task.name }}</div>
|
||||||
|
<div class="td">{{ task.createdOn | date:'dd.MM.yyyy HH:mm:ss' }}</div>
|
||||||
|
<div class="td">{{ task.expirationTime | date:'dd.MM.yyyy HH:mm:ss' }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
@ -0,0 +1,8 @@
|
||||||
|
<div class="task-not-found-page">
|
||||||
|
<div class="task-not-found-container">
|
||||||
|
<div>:(</div>
|
||||||
|
<div><h2>Ошибка</h2>
|
||||||
|
Данная задача не существует.<br/>
|
||||||
|
Перейти к <a (click)="goToTaskList()" tabindex>списку задач</a></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
3
frontend/src/resources/template/preview/preview.html
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
<div id="page">
|
||||||
|
<router-outlet></router-outlet>
|
||||||
|
</div>
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
<router-outlet></router-outlet>
|
||||||
6
frontend/src/resources/template/webbpm/home.html
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
<div id="home">
|
||||||
|
<div class="inner">
|
||||||
|
<div class="task-list">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
1
frontend/src/resources/template/webbpm/page.html
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
<div #pageContent></div>
|
||||||
12
frontend/src/resources/template/webbpm/webbpm.html
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
<div class="wrapper">
|
||||||
|
<app-header *ngIf="headerVisible">
|
||||||
|
</app-header>
|
||||||
|
|
||||||
|
<div class="container">
|
||||||
|
<div class="container-inside" id="webbpm-angular-application-container">
|
||||||
|
<router-outlet></router-outlet>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<app-footer *ngIf="footerVisible">
|
||||||
|
</app-footer>
|
||||||
|
</div>
|
||||||
8
frontend/src/ts/main.aot.ts
Normal file
|
|
@ -0,0 +1,8 @@
|
||||||
|
import "../../src/resources/css/style.css";
|
||||||
|
import {platformBrowser} from '@angular/platform-browser';
|
||||||
|
import {enableProdMode} from "@angular/core";
|
||||||
|
import {WebbpmModuleNgFactory} from "./modules/webbpm/webbpm.module.ngfactory";
|
||||||
|
|
||||||
|
window['dev_mode'] = false;
|
||||||
|
enableProdMode();
|
||||||
|
platformBrowser().bootstrapModuleFactory(WebbpmModuleNgFactory);
|
||||||
7
frontend/src/ts/main.ts
Normal file
|
|
@ -0,0 +1,7 @@
|
||||||
|
import {platformBrowserDynamic} from "@angular/platform-browser-dynamic";
|
||||||
|
import {enableProdMode} from "@angular/core";
|
||||||
|
import {WebbpmModule} from "./modules/webbpm/webbpm.module";
|
||||||
|
|
||||||
|
window['dev_mode'] = true;
|
||||||
|
enableProdMode();
|
||||||
|
platformBrowserDynamic().bootstrapModule(WebbpmModule);
|
||||||
72
frontend/src/ts/modules/app/app-routing.module.ts
Normal file
|
|
@ -0,0 +1,72 @@
|
||||||
|
import {NgModule} from "@angular/core";
|
||||||
|
import {RouterModule, Routes} from "@angular/router";
|
||||||
|
import {AccessDeniedComponent} from "./component/access-denied.component";
|
||||||
|
import {LoginComponent} from "./component/login.component";
|
||||||
|
import {AuthenticationGuard, ConfirmExitGuard, SignedInGuard, ProcessInstanceRouteResolver} from "@webbpm/base-package";
|
||||||
|
import {RegisterComponent} from "./component/register.component";
|
||||||
|
import {ConfirmUserEmailComponent} from "./component/confirm-user-email.component";
|
||||||
|
import {ResetPasswordComponent} from "./component/reset-password.component";
|
||||||
|
import {NewPasswordComponent} from "./component/new-password.component";
|
||||||
|
import {TaskListComponent} from "./component/task-list.component";
|
||||||
|
|
||||||
|
const appRoutes: Routes = [
|
||||||
|
{
|
||||||
|
path: 'login',
|
||||||
|
component: LoginComponent,
|
||||||
|
canActivate: [SignedInGuard]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'access-denied',
|
||||||
|
component: AccessDeniedComponent,
|
||||||
|
canActivate: [AuthenticationGuard, ConfirmExitGuard]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'registration',
|
||||||
|
component: RegisterComponent,
|
||||||
|
canActivate: [SignedInGuard]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'confirm',
|
||||||
|
component: ConfirmUserEmailComponent
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'reset-password',
|
||||||
|
component: ResetPasswordComponent
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'new-password',
|
||||||
|
component: NewPasswordComponent
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'process',
|
||||||
|
canActivate: [AuthenticationGuard, ConfirmExitGuard],
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: 'instance',
|
||||||
|
loadChildren: 'generated-sources/page-process-instance-list.module#PageprocessinstancelistModule',
|
||||||
|
resolve: {
|
||||||
|
processInstanceId: ProcessInstanceRouteResolver
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'instance/:processInstanceId',
|
||||||
|
loadChildren: 'generated-sources/page-process-instance.module#PageprocessinstanceModule',
|
||||||
|
resolve: {
|
||||||
|
processInstanceId: ProcessInstanceRouteResolver
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: 'tasks',
|
||||||
|
component: TaskListComponent,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forChild(appRoutes)],
|
||||||
|
exports: [RouterModule]
|
||||||
|
})
|
||||||
|
export class AppRoutingModule {
|
||||||
|
|
||||||
|
}
|
||||||
80
frontend/src/ts/modules/app/app.module.ts
Normal file
|
|
@ -0,0 +1,80 @@
|
||||||
|
import {forwardRef, NgModule} from "@angular/core";
|
||||||
|
import {NgbModule} from "@ng-bootstrap/ng-bootstrap";
|
||||||
|
import {CommonModule, registerLocaleData} from "@angular/common";
|
||||||
|
import localeRu from '@angular/common/locales/ru';
|
||||||
|
import {FormsModule} from "@angular/forms";
|
||||||
|
import {AgGridModule} from "ag-grid-angular";
|
||||||
|
import {
|
||||||
|
BpmnModule,
|
||||||
|
ComponentsModule,
|
||||||
|
CoreModule,
|
||||||
|
ProgressIndicationService,
|
||||||
|
SecurityModule
|
||||||
|
} from "@webbpm/base-package";
|
||||||
|
import {AdminMenuComponent} from "./component/admin-menu.component";
|
||||||
|
import {AppHeaderComponent} from "./component/app-header.component";
|
||||||
|
import {AppFooterComponent} from "./component/app-footer.component";
|
||||||
|
import {LogOutComponent} from "./component/logout.component";
|
||||||
|
import {LoginComponent} from "./component/login.component";
|
||||||
|
import {AccessDeniedComponent} from "./component/access-denied.component";
|
||||||
|
import {ApplicationVersionComponent} from "./component/application-version.component";
|
||||||
|
import {RouterModule} from "@angular/router";
|
||||||
|
import {RegisterComponent} from "./component/register.component";
|
||||||
|
import {ConfirmUserEmailComponent} from "./component/confirm-user-email.component";
|
||||||
|
import {InternationalPhoneNumberModule} from "ngx-international-phone-number";
|
||||||
|
import {ResetPasswordComponent} from "./component/reset-password.component";
|
||||||
|
import {NewPasswordComponent} from "./component/new-password.component";
|
||||||
|
import {AppProgressIndicationComponent} from "./component/app-progress-indication.component";
|
||||||
|
import {AppProgressIndicationService} from "./service/app-progress-indication.service";
|
||||||
|
import {TaskListComponent} from "./component/task-list.component";
|
||||||
|
import {ProcessListComponent} from "./component/process-list.component";
|
||||||
|
import {TaskComponent} from "./component/task.component";
|
||||||
|
import {TaskNotFoundComponent} from "./component/task-not-found.component";
|
||||||
|
|
||||||
|
registerLocaleData(localeRu);
|
||||||
|
export const DIRECTIVES = [
|
||||||
|
forwardRef(() => AppHeaderComponent),
|
||||||
|
forwardRef(() => AppFooterComponent),
|
||||||
|
forwardRef(() => AdminMenuComponent),
|
||||||
|
forwardRef(() => ApplicationVersionComponent),
|
||||||
|
forwardRef(() => LogOutComponent),
|
||||||
|
forwardRef(() => LoginComponent),
|
||||||
|
forwardRef(() => AccessDeniedComponent),
|
||||||
|
forwardRef(() => RegisterComponent),
|
||||||
|
forwardRef(() => ConfirmUserEmailComponent),
|
||||||
|
forwardRef(() => ResetPasswordComponent),
|
||||||
|
forwardRef(() => NewPasswordComponent),
|
||||||
|
forwardRef(() => AppProgressIndicationComponent),
|
||||||
|
forwardRef(() => TaskListComponent),
|
||||||
|
forwardRef(() => ProcessListComponent),
|
||||||
|
forwardRef(() => TaskComponent),
|
||||||
|
forwardRef(() => TaskNotFoundComponent)
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
FormsModule,
|
||||||
|
CoreModule,
|
||||||
|
NgbModule,
|
||||||
|
BpmnModule,
|
||||||
|
SecurityModule,
|
||||||
|
ComponentsModule,
|
||||||
|
AgGridModule,
|
||||||
|
RouterModule,
|
||||||
|
InternationalPhoneNumberModule
|
||||||
|
],
|
||||||
|
declarations: [
|
||||||
|
DIRECTIVES
|
||||||
|
],
|
||||||
|
exports: [
|
||||||
|
DIRECTIVES
|
||||||
|
],
|
||||||
|
providers: [
|
||||||
|
{ provide: ProgressIndicationService, useClass: AppProgressIndicationService }
|
||||||
|
],
|
||||||
|
bootstrap: [],
|
||||||
|
entryComponents: [AppProgressIndicationComponent, TaskNotFoundComponent]
|
||||||
|
})
|
||||||
|
export class AppModule {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
import {ChangeDetectionStrategy, Component} from "@angular/core";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
moduleId: module.id,
|
||||||
|
selector: "access-denied",
|
||||||
|
templateUrl: "../../../../../src/resources/template/app/component/access_denied.html",
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
})
|
||||||
|
export class AccessDeniedComponent {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
import {ChangeDetectionStrategy, Component, Input} from "@angular/core";
|
||||||
|
import {UserService, Session} from "@webbpm/base-package";
|
||||||
|
import {NgbDropdownConfig, Placement} from "@ng-bootstrap/ng-bootstrap";
|
||||||
|
import {Observable} from "rxjs";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
moduleId: module.id,
|
||||||
|
selector: 'admin-menu',
|
||||||
|
templateUrl: '../../../../../src/resources/template/app/component/admin_menu.html',
|
||||||
|
providers: [NgbDropdownConfig],
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
})
|
||||||
|
export class AdminMenuComponent {
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
public placement: Placement = 'bottom';
|
||||||
|
public currentSession: Observable<Session>;
|
||||||
|
|
||||||
|
constructor(protected userService: UserService, public config: NgbDropdownConfig) {
|
||||||
|
this.config.placement = this.placement;
|
||||||
|
this.currentSession = this.userService.getCurrentSession();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
import {Component} from "@angular/core";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
moduleId: module.id,
|
||||||
|
selector: "app-footer",
|
||||||
|
templateUrl: "../../../../../src/resources/template/app/component/app_footer.html"
|
||||||
|
})
|
||||||
|
export class AppFooterComponent {
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
import {ChangeDetectionStrategy, Component} from "@angular/core";
|
||||||
|
import {Router} from "@angular/router";
|
||||||
|
import {UserService, Session} from "@webbpm/base-package";
|
||||||
|
import {Observable} from "rxjs";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
moduleId: module.id,
|
||||||
|
selector: "app-header",
|
||||||
|
templateUrl: "../../../../../src/resources/template/app/component/app_header.html",
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
})
|
||||||
|
export class AppHeaderComponent {
|
||||||
|
|
||||||
|
public currentSession: Observable<Session>;
|
||||||
|
|
||||||
|
constructor(protected userService: UserService,
|
||||||
|
protected router: Router) {
|
||||||
|
this.currentSession = this.userService.getCurrentSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
public openTaskList(): void {
|
||||||
|
this.router.navigateByUrl("/process/tasks");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
import {ChangeDetectionStrategy, Component} from "@angular/core";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
moduleId: module.id,
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
selector: 'progress-indication-dialog-content',
|
||||||
|
templateUrl: '../../../../../src/resources/template/app/component/progress-indication.html'
|
||||||
|
})
|
||||||
|
export class AppProgressIndicationComponent {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input} from "@angular/core";
|
||||||
|
import {HttpClient} from "@angular/common/http";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
moduleId: module.id,
|
||||||
|
selector: "application-version",
|
||||||
|
templateUrl: "../../../../../src/resources/template/app/component/application_version.html",
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
})
|
||||||
|
export class ApplicationVersionComponent {
|
||||||
|
@Input()
|
||||||
|
public applicationVersion: string;
|
||||||
|
|
||||||
|
constructor(private httpClient: HttpClient, private cd: ChangeDetectorRef) {
|
||||||
|
this.loadAppVersion(); //TODO: check version url
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadAppVersion() {
|
||||||
|
this.httpClient.get("version").toPromise().then((version: any) => {
|
||||||
|
this.applicationVersion = version.number;
|
||||||
|
this.cd.markForCheck();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,51 @@
|
||||||
|
import {ActivatedRoute, Router} from "@angular/router";
|
||||||
|
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input} from "@angular/core";
|
||||||
|
import {Session, UserService} from "@webbpm/base-package";
|
||||||
|
import {Observable} from "rxjs";
|
||||||
|
|
||||||
|
enum VerificationStatus {
|
||||||
|
VERIFYING = "VERIFYING",
|
||||||
|
VERIFIED = "VERIFIED",
|
||||||
|
FAILED = "FAILED"
|
||||||
|
}
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
moduleId: module.id,
|
||||||
|
selector: "confirm",
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush,
|
||||||
|
templateUrl: "../../../../../src/resources/template/app/component/confirm-user-email.html"
|
||||||
|
})
|
||||||
|
export class ConfirmUserEmailComponent {
|
||||||
|
public verificationStatus = VerificationStatus.VERIFYING;
|
||||||
|
public currentSession: Observable<Session>;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
public errorMessage: string;
|
||||||
|
|
||||||
|
constructor(private router: Router, private userService: UserService,
|
||||||
|
private route: ActivatedRoute, private cd: ChangeDetectorRef) {
|
||||||
|
this.currentSession = this.userService.getCurrentSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
const link: string = this.route.snapshot.queryParamMap.get("link");
|
||||||
|
// remove link from url to prevent http referer leakage
|
||||||
|
this.router.navigate([], { relativeTo: this.route, replaceUrl: true });
|
||||||
|
this.userService.confirm(
|
||||||
|
link,
|
||||||
|
(reason) => {
|
||||||
|
this.verificationStatus = VerificationStatus.FAILED;
|
||||||
|
if (reason.status === 404) {
|
||||||
|
this.errorMessage = 'Ссылка недействительна. Требуется повторная регистрация.';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.errorMessage = 'Произошла ошибка, обратитесь в службу технической поддержки!';
|
||||||
|
}
|
||||||
|
this.cd.markForCheck();
|
||||||
|
})
|
||||||
|
.then(() => {
|
||||||
|
this.verificationStatus = VerificationStatus.VERIFIED;
|
||||||
|
this.cd.markForCheck();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
68
frontend/src/ts/modules/app/component/login.component.ts
Normal file
|
|
@ -0,0 +1,68 @@
|
||||||
|
import {Component, Input} from "@angular/core";
|
||||||
|
import {ActivatedRoute, Router} from "@angular/router";
|
||||||
|
import {UserService, Credentials} from "@webbpm/base-package";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
moduleId: module.id,
|
||||||
|
selector: "login",
|
||||||
|
templateUrl: "../../../../../src/resources/template/app/component/login.html"
|
||||||
|
})
|
||||||
|
export class LoginComponent {
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
public username: string;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
public password: string;
|
||||||
|
public passwordType: boolean;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
public errorMessage: string;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
public confirmationSent: boolean;
|
||||||
|
|
||||||
|
constructor(private router: Router, private userService: UserService,
|
||||||
|
private route: ActivatedRoute) {
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.confirmationSent = this.route.snapshot.queryParamMap.get('confirmationSent') === 'true';
|
||||||
|
this.router.navigate([], { relativeTo: this.route, replaceUrl: true });
|
||||||
|
}
|
||||||
|
|
||||||
|
public login(): void {
|
||||||
|
let credentials: Credentials = new Credentials();
|
||||||
|
credentials.username = this.username;
|
||||||
|
credentials.password = this.password;
|
||||||
|
|
||||||
|
this.userService.login(credentials, "Password")
|
||||||
|
.then(() => this.router.navigateByUrl("/"),
|
||||||
|
(reason: any) => {
|
||||||
|
switch (reason.status) {
|
||||||
|
case 401: {
|
||||||
|
this.errorMessage = "Неправильный логин или пароль";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 404: {
|
||||||
|
this.errorMessage = "Приложение стартует. Пожалуйста, подождите...";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default: {
|
||||||
|
this.errorMessage =
|
||||||
|
"Произошла неизвестная ошибка, обратитесь в службу технической поддержки!";
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public goToRegister(): void {
|
||||||
|
this.router.navigateByUrl("/register");
|
||||||
|
}
|
||||||
|
|
||||||
|
togglePasswordType(): void {
|
||||||
|
this.passwordType = !this.passwordType;
|
||||||
|
}
|
||||||
|
}
|
||||||
37
frontend/src/ts/modules/app/component/logout.component.ts
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
import {Component} from "@angular/core";
|
||||||
|
import {UserService, Session, AuthenticationMethodService} from "@webbpm/base-package";
|
||||||
|
import {Observable} from "rxjs";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
moduleId: module.id,
|
||||||
|
selector: "[log-out]",
|
||||||
|
templateUrl: "../../../../../src/resources/template/app/component/log_out.html"
|
||||||
|
})
|
||||||
|
export class LogOutComponent {
|
||||||
|
|
||||||
|
public currentSession: Observable<Session>;
|
||||||
|
|
||||||
|
constructor(private userService: UserService, private authenticationMethodService: AuthenticationMethodService) {
|
||||||
|
this.currentSession = userService.getCurrentSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
public logout(): void {
|
||||||
|
this.userService.logout();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getCurrentUserName(): string {
|
||||||
|
return this.userService.getCurrentUserName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getFullUserName(): string {
|
||||||
|
return this.userService.getFullUserName();
|
||||||
|
}
|
||||||
|
|
||||||
|
public isLogoutButtonVisible(): boolean {
|
||||||
|
return this.authenticationMethodService.isFormAuth();
|
||||||
|
}
|
||||||
|
|
||||||
|
public getOrgUnitName(): string {
|
||||||
|
return this.userService.getOrgUnitName();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
import {ActivatedRoute, Router} from "@angular/router";
|
||||||
|
import {Component, Input} from "@angular/core";
|
||||||
|
import {Session, UserPasswordResetRequestDto, UserService} from "@webbpm/base-package";
|
||||||
|
import {Observable} from "rxjs";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
moduleId: module.id,
|
||||||
|
selector: "newPassword",
|
||||||
|
templateUrl: "../../../../../src/resources/template/app/component/new_password.html"
|
||||||
|
})
|
||||||
|
export class NewPasswordComponent {
|
||||||
|
public currentSession: Observable<Session>;
|
||||||
|
|
||||||
|
private token: string;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
public password: string;
|
||||||
|
public passwordType: boolean;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
public confirmPassword: string;
|
||||||
|
public confirmPasswordType: boolean;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
public errorMessage: string;
|
||||||
|
|
||||||
|
constructor(private router: Router, private userService: UserService,
|
||||||
|
private route: ActivatedRoute) {
|
||||||
|
this.currentSession = this.userService.getCurrentSession();
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.token = this.route.snapshot.queryParamMap.get("token");
|
||||||
|
|
||||||
|
this.router.navigate([], {relativeTo: this.route, replaceUrl: true});
|
||||||
|
|
||||||
|
if (this.token == undefined || this.token === '') {
|
||||||
|
this.errorMessage = 'Ссылка недействительна. Требуется повторить восстановление пароля.';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public changePassword(): void {
|
||||||
|
let dto: UserPasswordResetRequestDto = new UserPasswordResetRequestDto();
|
||||||
|
dto.password = this.password;
|
||||||
|
dto.passwordConfirm = this.confirmPassword;
|
||||||
|
this.userService.changePassword(dto, this.token)
|
||||||
|
.then(() => this.router.navigateByUrl("/"),
|
||||||
|
() => {
|
||||||
|
this.errorMessage =
|
||||||
|
'Произошла неизвестная ошибка, обратитесь в службу технической поддержки!';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
togglePasswordType(): void {
|
||||||
|
this.passwordType = !this.passwordType;
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleConfirmPasswordType(): void {
|
||||||
|
this.confirmPasswordType = !this.confirmPasswordType;
|
||||||
|
}
|
||||||
|
|
||||||
|
validPasswords(): boolean {
|
||||||
|
if (this.password === undefined || this.confirmPassword === undefined) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
let eq = this.password === this.confirmPassword;
|
||||||
|
if (!eq) {
|
||||||
|
this.errorMessage = 'Введенные пароли не совпадают. Убедитесь, что данные, ' +
|
||||||
|
'введенные в поле "Подтверждение пароля", совпадают с теми, ' +
|
||||||
|
'которые указаны в поле "Пароль".';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.errorMessage = '';
|
||||||
|
}
|
||||||
|
return eq;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,44 @@
|
||||||
|
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input} from "@angular/core";
|
||||||
|
import {Process, TaskService, ProcessDefinitionResource, ProcessService} from "@webbpm/base-package";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
moduleId: module.id,
|
||||||
|
selector: 'process',
|
||||||
|
templateUrl: '../../../../../src/resources/template/app/component/process_list.html',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
})
|
||||||
|
export class ProcessListComponent {
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
public processList: Process[];
|
||||||
|
@Input()
|
||||||
|
public errorMessage: string;
|
||||||
|
|
||||||
|
constructor(private processDefinitionResource: ProcessDefinitionResource,
|
||||||
|
private taskService: TaskService,
|
||||||
|
private cd: ChangeDetectorRef,
|
||||||
|
private processService: ProcessService) {
|
||||||
|
this.processList = [];
|
||||||
|
this.loadProcessList();
|
||||||
|
}
|
||||||
|
|
||||||
|
loadProcessList() {
|
||||||
|
this.processDefinitionResource.list().then(
|
||||||
|
(processList) => {
|
||||||
|
this.processList = processList;
|
||||||
|
this.cd.markForCheck();
|
||||||
|
},
|
||||||
|
() => {
|
||||||
|
this.errorMessage = "Error load process list";
|
||||||
|
this.cd.markForCheck();
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
startProcess(processDefinitionId: string) {
|
||||||
|
this.processService.start(processDefinitionId, null).then(
|
||||||
|
(processInstanceId: number) => {
|
||||||
|
this.taskService.startAndOpenNextTask(processInstanceId);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
71
frontend/src/ts/modules/app/component/register.component.ts
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
import {Component, Input, ViewChild} from "@angular/core";
|
||||||
|
import {AppConfigService, UserDto, UserService} from "@webbpm/base-package";
|
||||||
|
import {Router} from "@angular/router";
|
||||||
|
import {PhoneNumberComponent} from "ngx-international-phone-number";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
moduleId: module.id,
|
||||||
|
selector: "register",
|
||||||
|
templateUrl: "../../../../../src/resources/template/app/component/register.html"
|
||||||
|
})
|
||||||
|
export class RegisterComponent {
|
||||||
|
|
||||||
|
public passwordPattern: string;
|
||||||
|
public passwordPatternErrorMessage: string;
|
||||||
|
public errorMessage: string;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
public username: string;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
public email: string;
|
||||||
|
@ViewChild(PhoneNumberComponent)
|
||||||
|
public phone: PhoneNumberComponent;
|
||||||
|
public phoneNumber: string;
|
||||||
|
|
||||||
|
public phoneIsTouched: boolean = false;
|
||||||
|
@Input()
|
||||||
|
public password: string;
|
||||||
|
|
||||||
|
public fieldType: boolean;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
public consent: string;
|
||||||
|
|
||||||
|
constructor(private router: Router, private userService: UserService,private appConfigService: AppConfigService) {
|
||||||
|
this.passwordPattern = appConfigService.getParamValue("password_pattern");
|
||||||
|
this.passwordPatternErrorMessage = appConfigService.getParamValue("password_pattern_error");
|
||||||
|
}
|
||||||
|
|
||||||
|
public register(): void {
|
||||||
|
let user: UserDto = new UserDto();
|
||||||
|
user.username = this.username;
|
||||||
|
user.email = this.email;
|
||||||
|
user.name = this.username;
|
||||||
|
user.phone = this.phone.value;
|
||||||
|
user.password = this.password;
|
||||||
|
|
||||||
|
this.userService.register(user)
|
||||||
|
.then(() => this.router.navigateByUrl("/login?confirmationSent=true"),
|
||||||
|
(reason: any) => {
|
||||||
|
if (reason.status === 409) {
|
||||||
|
this.errorMessage = 'Пользователь с данным почтовым адресом уже существует';
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
this.errorMessage = 'Произошла неизвестная ошибка, обратитесь в службу технической поддержки!';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
toggleFieldType(): void {
|
||||||
|
this.fieldType = !this.fieldType;
|
||||||
|
}
|
||||||
|
|
||||||
|
phoneHasOnlyDialCode(): boolean {
|
||||||
|
return this.phone.phoneNumber.trim() === this.phone.getSelectedCountryDialCode().trim()
|
||||||
|
}
|
||||||
|
|
||||||
|
phoneInputFocusOut(): void {
|
||||||
|
this.phoneIsTouched = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,30 @@
|
||||||
|
import {Component, Input} from "@angular/core";
|
||||||
|
import {UserService} from "@webbpm/base-package";
|
||||||
|
import {Router} from "@angular/router";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
moduleId: module.id,
|
||||||
|
selector: "resetPassword",
|
||||||
|
templateUrl: "../../../../../src/resources/template/app/component/reset_password.html"
|
||||||
|
})
|
||||||
|
export class ResetPasswordComponent {
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
public email: string;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
public errorMessage: string;
|
||||||
|
|
||||||
|
constructor(private router: Router, private userService: UserService) {
|
||||||
|
}
|
||||||
|
|
||||||
|
resetPassword(): void {
|
||||||
|
|
||||||
|
this.userService.resetPassword(this.email)
|
||||||
|
.then(() => this.router.navigateByUrl("/"),
|
||||||
|
(reason: any) => {
|
||||||
|
this.errorMessage =
|
||||||
|
'Произошла неизвестная ошибка, обратитесь в службу технической поддержки!';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
121
frontend/src/ts/modules/app/component/task-list.component.ts
Normal file
|
|
@ -0,0 +1,121 @@
|
||||||
|
import {Component, Input} from "@angular/core";
|
||||||
|
import {Task, TaskService, TaskStatus, TaskReference, TaskResource, ProcessVariable, UserService, RolesService, ProcessService} from "@webbpm/base-package";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
moduleId: module.id,
|
||||||
|
selector: "task-list",
|
||||||
|
templateUrl: "../../../../../src/resources/template/app/component/task_list.html"
|
||||||
|
})
|
||||||
|
export class TaskListComponent {
|
||||||
|
@Input()
|
||||||
|
public tasks: Task[];
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
public onTimeTasks: Task[];
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
public overdueTasks: Task[];
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
public visibleTasks: Task[];
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
public showMode: string;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
public errorMessage: string;
|
||||||
|
|
||||||
|
@Input()
|
||||||
|
public roles: string[];
|
||||||
|
|
||||||
|
constructor(private taskService: TaskService,
|
||||||
|
private $taskResource: TaskResource,
|
||||||
|
private userService: UserService,
|
||||||
|
private rolesService: RolesService,
|
||||||
|
private processService: ProcessService) {
|
||||||
|
this.tasks = [];
|
||||||
|
this.onTimeTasks = [];
|
||||||
|
this.overdueTasks = [];
|
||||||
|
this.visibleTasks = [];
|
||||||
|
this.roles = [];
|
||||||
|
this.showMode = 'All';
|
||||||
|
|
||||||
|
this.rolesService.getRoles().then((roles: string[]) => {
|
||||||
|
this.roles = roles;
|
||||||
|
});
|
||||||
|
|
||||||
|
this.loadTasks();
|
||||||
|
}
|
||||||
|
|
||||||
|
private loadTasks(): void {
|
||||||
|
this.$taskResource.list()
|
||||||
|
.then(
|
||||||
|
(tasks: Task[]) => this.initTasks(tasks),
|
||||||
|
() => this.errorMessage = "Error load tasks"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public startTask(task: Task): Promise<any> {
|
||||||
|
let taskRef: TaskReference = new TaskReference();
|
||||||
|
taskRef.processInstanceId = task.processInstanceId;
|
||||||
|
taskRef.taskId = task.id;
|
||||||
|
|
||||||
|
if (task.status == TaskStatus.InProgress) {
|
||||||
|
return this.taskService.openTask(taskRef);
|
||||||
|
}
|
||||||
|
else if (task.status == TaskStatus.Reserved || task.status == TaskStatus.Ready) {
|
||||||
|
return this.taskService.startAndOpenTask(taskRef);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private initTasks(tasks: Task[]): void {
|
||||||
|
this.tasks = tasks;
|
||||||
|
this.filterOnTimeTasks();
|
||||||
|
this.filterOverdueTasks();
|
||||||
|
this.filterVisibleTasks();
|
||||||
|
}
|
||||||
|
|
||||||
|
public isVisible(task: Task): boolean {
|
||||||
|
return this.showMode == 'All' ||
|
||||||
|
this.showMode == 'OnTime' && !this.isOverdue(task) ||
|
||||||
|
this.showMode == 'Overdue' && this.isOverdue(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
public isOverdue(task: Task): boolean {
|
||||||
|
if (!task.expirationTime) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return new Date(task.expirationTime).getTime() <= new Date().getTime();
|
||||||
|
}
|
||||||
|
|
||||||
|
public isOnTime(task: Task): boolean {
|
||||||
|
return !this.isOverdue(task);
|
||||||
|
}
|
||||||
|
|
||||||
|
public startProcess(processDefinitionId: string, processVars?: Array<ProcessVariable | any>) {
|
||||||
|
this.processService.start(processDefinitionId, processVars).then(
|
||||||
|
(processInstanceId: number) => {
|
||||||
|
this.taskService.startAndOpenNextTask(processInstanceId);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public filterOnTimeTasks() {
|
||||||
|
this.onTimeTasks = this.tasks
|
||||||
|
.filter((task: Task) => this.isOnTime(task));
|
||||||
|
}
|
||||||
|
|
||||||
|
public filterOverdueTasks() {
|
||||||
|
this.overdueTasks = this.tasks
|
||||||
|
.filter((task: Task) => this.isOverdue(task));
|
||||||
|
}
|
||||||
|
|
||||||
|
public filterVisibleTasks() {
|
||||||
|
this.visibleTasks = this.tasks
|
||||||
|
.filter((task: Task) => this.isVisible(task));
|
||||||
|
}
|
||||||
|
|
||||||
|
public hasRole(role: string) {
|
||||||
|
return this.roles.includes(role);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,18 @@
|
||||||
|
import {ChangeDetectionStrategy, Component} from "@angular/core";
|
||||||
|
import {Router} from "@angular/router";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
moduleId: module.id,
|
||||||
|
selector: 'task-not-found',
|
||||||
|
templateUrl: '../../../../../src/resources/template/app/component/task_not_found.html',
|
||||||
|
changeDetection: ChangeDetectionStrategy.OnPush
|
||||||
|
})
|
||||||
|
export class TaskNotFoundComponent {
|
||||||
|
|
||||||
|
constructor(private router: Router) {
|
||||||
|
}
|
||||||
|
|
||||||
|
goToTaskList() {
|
||||||
|
return this.router.navigateByUrl("/process/tasks");
|
||||||
|
}
|
||||||
|
}
|
||||||
9
frontend/src/ts/modules/app/component/task.component.ts
Normal file
|
|
@ -0,0 +1,9 @@
|
||||||
|
import {Component} from "@angular/core";
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
moduleId: module.id,
|
||||||
|
selector: "[task]",
|
||||||
|
templateUrl: "../../../../../src/resources/template/app/component/task.html"
|
||||||
|
})
|
||||||
|
export class TaskComponent {
|
||||||
|
}
|
||||||