initial commit

This commit is contained in:
kochetkov 2024-11-21 10:29:18 +03:00
commit 725a26478c
901 changed files with 131887 additions and 0 deletions

67
.gitignore vendored Normal file
View 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
View 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
View 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 в качестве имени базы данных используйте базу данных приложения.
Предоставление необходимых прав для роли &lt;your-project-security-role&gt;
```
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";
```
## Дополнительные ограничения базы секьюрити
Логин пользователя &lt;user_account.username&gt; и имена ролей &lt;user_role.name&gt; не должны совпадать, так как в ходе работы 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
```
## Создание нового администратора
Создайте группу &lt;your-admin-group&gt; и предоставьте ей права в модуль администрирования. Для этого выполните в БД проекта
```
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 студии

309
backend/pom.xml Normal file
View file

@ -0,0 +1,309 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd" xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>ru.micord.ervu</groupId>
<artifactId>eks</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<groupId>ru.micord.ervu.eks</groupId>
<artifactId>backend</artifactId>
<packaging>war</packaging>
<dependencies>
<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>ru.micord.ervu.eks</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>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>
<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>
<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>
<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>
<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.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>
<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>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-spring</artifactId>
</dependency>
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-provider-jdbc-template</artifactId>
</dependency>
<dependency>
<groupId>ru.cg.webbpm.packages.base</groupId>
<artifactId>backend</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<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>
<build>
<plugins>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<configuration>
<warName>${project.artifactId}</warName>
</configuration>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>ru.cg.webbpm.modules.resources</groupId>
<artifactId>resources-impl-development</artifactId>
</dependency>
</dependencies>
</profile>
<profile>
<id>dev</id>
<dependencies>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
</dependency>
</dependencies>
</profile>
</profiles>
</project>

View file

@ -0,0 +1,92 @@
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.apache.http.client.config.RequestConfig;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
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.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler;
import org.springframework.web.client.RestTemplate;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import javax.sql.DataSource;
import java.time.Duration;
/**
* 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 {
@Value("${config.data.executor.socket.timeout:10}")
private int socketTimeout;
@Value("${config.data.executor.connection.timeout:10}")
private int connectionTimeout;
@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);
}
@Bean
public RestTemplate restTemplate() {
RequestConfig requestConfig = RequestConfig.custom()
.setSocketTimeout(socketTimeout * 1000)
.setConnectionRequestTimeout(connectionTimeout * 1000)
.setConnectTimeout(connectionTimeout * 1000)
.build();
CloseableHttpClient httpClient = HttpClients.custom()
.setDefaultRequestConfig(requestConfig)
.build();
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
return new RestTemplate(factory);
}
}

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

View file

@ -0,0 +1,24 @@
package dto;
import java.util.List;
public class ExportDataRequest {
public String type;
public List<String> ids;
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public List<String> getIds() {
return ids;
}
public void setIds(List<String> ids) {
this.ids = ids;
}
}

View file

@ -0,0 +1,27 @@
package rpc;
import org.springframework.beans.factory.annotation.Autowired;
import ru.cg.webbpm.modules.webkit.annotations.RpcCall;
import ru.cg.webbpm.modules.webkit.annotations.RpcService;
import ru.cg.webbpm.modules.webkit.beans.Behavior;
import service.ConfigExecutorService;
import java.util.List;
/**
* @author Evgenii Malkov
*/
@RpcService
public class ConfigExecutorRpcService extends Behavior {
private final ConfigExecutorService configExecutorService;
public ConfigExecutorRpcService(@Autowired ConfigExecutorService configExecutorService) {
this.configExecutorService = configExecutorService;
}
@RpcCall
public void callConfigExecutor(String methodPath, List<String> ids) {
configExecutorService.call(methodPath, ids);
}
}

View file

@ -0,0 +1,30 @@
package rpc;
import java.util.List;
import dto.ExportDataRequest;
import model.FileModel;
import org.springframework.beans.factory.annotation.Autowired;
import service.ConfigExecutorService;
import ru.cg.webbpm.modules.webkit.annotations.RpcCall;
import ru.cg.webbpm.modules.webkit.annotations.RpcService;
import ru.cg.webbpm.modules.webkit.beans.Behavior;
/**
* @author kochetkov
*/
@RpcService
public class ExportDataRpcService extends Behavior {
private final ConfigExecutorService configExecutorService;
public ExportDataRpcService(@Autowired ConfigExecutorService configExecutorService) {
this.configExecutorService = configExecutorService;
}
@RpcCall
public FileModel exportData(ExportDataRequest request) {
return configExecutorService.exportData(request);
}
}

View file

@ -0,0 +1,169 @@
package ru.micord.ervu_eks.component.service;
import java.sql.Array;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import bpmn.handler.sql_handler.SQLHandlerUtils;
import bpmn.handler.sql_handler.SQLParameters;
import org.jooq.DSLContext;
import org.jooq.Record;
import org.jooq.Result;
import org.jooq.exception.DataAccessException;
import org.jooq.impl.DefaultDataType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import service.button.sql.ExecuteSqlButtonService;
import utils.SqlQueryUtils;
import ru.cg.webbpm.modules.core.runtime.api.context.ExecutionContextHelper;
import ru.cg.webbpm.modules.database.api.provider.DslProvider;
import ru.cg.webbpm.modules.standard_annotations.editor.TextAreaEditor;
import ru.cg.webbpm.modules.standard_annotations.validation.NotNull;
public class ReadOnlySqlButtonServiceImpl implements ExecuteSqlButtonService {
private static final Logger logger = LoggerFactory.getLogger(ReadOnlySqlButtonServiceImpl.class);
@Autowired
private DslProvider dslProvider;
@Autowired
protected ExecutionContextHelper executionContextHelper;
private DSLContext dsl;
@TextAreaEditor
@NotNull
public String sql;
public String jndiName;
public void initDsl() {
if (dsl != null) {
return;
}
dsl = this.dslProvider.getDslContext(jndiName);
if (dsl == null) {
throw new RuntimeException("Couldn't get dslContext with datasourceJndiName = " + jndiName);
}
}
public List<?> executeSql(Object[] params) {
initDsl();
SQLParameters parameters = new SQLParameters();
parameters.setSql(sql);
parameters.setSqlParameters(params);
return switch (SqlQueryUtils.queryType(sql)) {
case SELECT -> executeSelect(parameters);
case UNKNOWN -> executeUnknownQuery(parameters);
default -> Collections.EMPTY_LIST;
};
}
private List<?> executeSelect(SQLParameters parameters) {
Result<Record> result = executeSelectRaw(parameters);
if (result.size() > 1) {
String message = String.format("Query \"%s\" with arguments %s returned more than one row.",
parameters.getSql(), Arrays.toString(parameters.getSqlParameters())
);
throw new IllegalStateException(message);
}
Record row = result.get(0);
List<Object> results = new ArrayList<>();
for (int i = 0;
i < row.size();
i++) {
results.add(row.getValue(i));
}
return results;
}
private List<?> executeUnknownQuery(SQLParameters parameters) {
// jooq does not provide any way to get generated keys for sql queries
Connection connection = dsl.configuration().connectionProvider().acquire();
try (PreparedStatement statement = connection.prepareStatement(parameters.getSql())) {
setParameters(statement, parameters, connection);
String query = dsl.renderInlined(
dsl.query(parameters.getSql(), parameters.getSqlParameters()));
String executionContext =
"\n with Execution Context: \n" + executionContextHelper.getExecutionContext();
logger.debug("Executing query " + query + executionContext);
statement.execute();
ResultSet resultSet = statement.getResultSet();
if (resultSet != null) {
List<Object> results = new ArrayList<>();
ResultSetMetaData metaData = resultSet.getMetaData();
while (resultSet.next()) {
Object[] row = new Object[metaData.getColumnCount()];
for (int i = 0;
i < metaData.getColumnCount();
i++) {
row[i] = resultSet.getObject(i + 1);
}
results.add(row);
}
return results;
}
else {
return Collections.emptyList();
}
}
catch (SQLException e) {
StringBuilder sb = new StringBuilder("Failed to execute SQL query. SQL: \"").append(
parameters.getSql()).append("\"");
if (parameters.getSqlParameters() != null && parameters.getSqlParameters().length > 0) {
String parametersStr = Arrays.toString(parameters.getSqlParameters());
sb.append(", with parameters: ").append(parametersStr);
}
throw new DataAccessException(sb.toString(), e);
}
finally {
dsl.configuration().connectionProvider().release(connection);
}
}
private void setParameters(PreparedStatement statement, SQLParameters parameters,
Connection connection) throws SQLException {
for (int i = 0;
i < parameters.getSqlParameters().length;
i++) {
Object parameter = parameters.getSqlParameters()[i];
if (parameter == null) {
// Also documentation states that it's better to use setNull method
// because not all databases support setObject with null value but
// in practice we don't know parameter type and the only sql type left
// for setNull method argument is java.sql.Types.NULL, which is not
// supported by all databases as well.
statement.setObject(i + 1, null);
}
else {
if (SQLHandlerUtils.isArrayExceptBytesArray(parameter)) {
Object[] castParameter = (Object[]) parameter;
org.jooq.SQLDialect dialect = dsl.configuration().dialect();
String typeName = DefaultDataType.getDataType(dialect,
castParameter.getClass().getComponentType()
).getTypeName();
Array arrayOf = connection.createArrayOf(typeName, castParameter);
statement.setArray(i + 1, arrayOf);
}
else {
statement.setObject(i + 1, parameter);
}
}
}
}
private Result<Record> executeSelectRaw(SQLParameters parameters) {
return dsl.resultQuery(parameters.getSql(), parameters.getSqlParameters()).fetch();
}
}

View file

@ -0,0 +1,88 @@
package service;
import java.lang.invoke.MethodHandles;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Base64;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import dto.ExportDataRequest;
import model.FileModel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
/**
* @author Evgenii Malkov
*/
@Service
public class ConfigExecutorService {
private static final Logger LOGGER = LoggerFactory.getLogger(
MethodHandles.lookup().lookupClass());
private final RestTemplate restTemplate;
private final String url;
public ConfigExecutorService(@Autowired RestTemplate restTemplate,
@Value("${config.data.executor.url}") String url) {
this.restTemplate = restTemplate;
this.url = url;
}
public FileModel exportData(ExportDataRequest request) {
HttpHeaders headers = new HttpHeaders();
HttpEntity<ExportDataRequest> entity = new HttpEntity<>(request, headers);
ResponseEntity<byte[]> response = restTemplate.exchange(
url.concat("/").concat("downloadCSV"),
HttpMethod.POST, entity, byte[].class
);
String content = Base64.getEncoder().encodeToString(response.getBody());
FileModel fileModel = new FileModel();
fileModel.setFileContent(content);
fileModel.setFileExtension(".csv");
fileModel.setFileName(
request.getType() + "_" + new SimpleDateFormat("dd.MM.yyyy").format(new Date()) + ".csv");
return fileModel;
}
public void call(String methodPath, List<String> ids) {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
HttpEntity<List<String>> entity = new HttpEntity<>(ids, headers);
LOGGER.info("Starts call config executor service with method: {}, for ids: {}", methodPath,
ids
);
try {
ResponseEntity<Object> response = restTemplate.exchange(url.concat(methodPath),
HttpMethod.POST, entity, Object.class
);
LOGGER.info("Method: {}, executed with status: {}, for ids:{}", methodPath,
response.getStatusCode().value(), ids
);
}
catch (Exception e) {
throw new RuntimeException(
String.format("Failed call config executor service method: %s for ids: %s with error",
methodPath, ids
), e);
}
}
public List<String> getExportDataIds() {
ResponseEntity<String[]> listDownloadTypes = restTemplate.getForEntity(
url.concat("/").concat("listDownloadTypes"), String[].class);
return Arrays.stream(Objects.requireNonNull(listDownloadTypes.getBody())).toList();
}
}

View file

@ -0,0 +1,35 @@
package service;
import java.util.List;
import model.ComboBoxModel;
import org.springframework.beans.factory.annotation.Autowired;
import service.field.ComboBoxService;
public class DataTypeComboBoxService implements ComboBoxService {
@Autowired
private ConfigExecutorService configExecutorService;
@Override
public List<ComboBoxModel> loadData() {
return configExecutorService.getExportDataIds().stream().sorted().map(s -> new ComboBoxModel(s, s, s, true)).toList();
}
@Override
public ComboBoxModel loadModelWithoutGraphConditions(Object id, Object parentValue) {
throw new UnsupportedOperationException();
}
@Override
public List<ComboBoxModel> loadDataWithFilter(Object filterValue) {
throw new UnsupportedOperationException();
}
@Override
public List<ComboBoxModel> loadDataByParentValueWithFilter(Object parentValue,
Object filterValue) {
throw new UnsupportedOperationException();
}
}

View file

38
config-data-executor/.gitignore vendored Normal file
View file

@ -0,0 +1,38 @@
target/
!.mvn/wrapper/maven-wrapper.jar
!**/src/main/**/target/
!**/src/test/**/target/
### IntelliJ IDEA ###
.idea/modules.xml
.idea/jarRepositories.xml
.idea/compiler.xml
.idea/libraries/
*.iws
*.iml
*.ipr
### Eclipse ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache
### NetBeans ###
/nbproject/private/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/
build/
!**/src/main/**/build/
!**/src/test/**/build/
### VS Code ###
.vscode/
### Mac OS ###
.DS_Store

View file

@ -0,0 +1,4 @@
FROM bellsoft/liberica-openjdk-alpine:17-cds
COPY target/*.jar app.jar
CMD ["java", "-jar", "app.jar"]

View file

@ -0,0 +1,114 @@
<?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>ru.micord.ervu</groupId>
<artifactId>eks</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<groupId>ru.micord.ervu.eks</groupId>
<artifactId>config-data-executor</artifactId>
<packaging>war</packaging>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencyManagement>
<dependencies>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
</dependency>
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>transactions-jta</artifactId>
</dependency>
<dependency>
<groupId>com.atomikos</groupId>
<artifactId>transactions-jdbc</artifactId>
</dependency>
<dependency>
<groupId>javax.transaction</groupId>
<artifactId>javax.transaction-api</artifactId>
</dependency>
<dependency>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>jakarta.xml.bind</groupId>
<artifactId>jakarta.xml.bind-api</artifactId>
</dependency>
<dependency>
<groupId>com.sun.xml.bind</groupId>
<artifactId>jaxb-impl</artifactId>
</dependency>
<dependency>
<groupId>com.arangodb</groupId>
<artifactId>arangodb-java-driver</artifactId>
</dependency>
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-s3</artifactId>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
<repositories>
<repository>
<id>maven_central</id>
<name>Maven Central</name>
<url>https://repo.maven.apache.org/maven2/</url>
</repository>
</repositories>
<build>
<finalName>${artifactId}</finalName>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-war-plugin</artifactId>
<version>3.1.0</version>
<configuration>
<failOnMissingWebXml>false</failOnMissingWebXml>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<version>2.7.18</version>
<configuration>
<mainClass>org.micord.Main</mainClass>
</configuration>
<executions>
<execution>
<goals>
<goal>repackage</goal>
</goals>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>

View file

@ -0,0 +1,15 @@
package org.micord;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
/**
* @author Maksim Tereshin
*/
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class Main {
public static void main(String[] args) {
SpringApplication.run(Main.class, args);
}
}

View file

@ -0,0 +1,8 @@
package org.micord;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.web.servlet.support.SpringBootServletInitializer;
@SpringBootApplication
public class SpringBootTomcatApplication extends SpringBootServletInitializer {
}

View file

@ -0,0 +1,34 @@
package org.micord.config;
import com.arangodb.ArangoDB;
import com.arangodb.ArangoDBException;
import com.arangodb.ArangoDatabase;
import org.micord.models.AqlConnectionParams;
/**
* @author Maksim Tereshin
*/
public class ArangoDBConnection {
public static ArangoDatabase getConnection(AqlConnectionParams params) {
try {
ArangoDB arangoDB = new ArangoDB.Builder()
.host(params.getHost(), params.getPort())
.user(params.getUsername())
.password(params.getPassword())
.build();
ArangoDatabase db = arangoDB.db(params.getDatabase());
if (!db.exists()) {
throw new ArangoDBException("Database does not exist: " + params.getDatabase());
}
return db;
} catch (ArangoDBException e) {
throw new RuntimeException("Failed to connect to ArangoDB", e);
}
}
}

View file

@ -0,0 +1,39 @@
package org.micord.config;
import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.icatch.jta.UserTransactionManager;
import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.transaction.jta.JtaTransactionManager;
/**
* @author Maksim Tereshin
*/
@Configuration
@EnableTransactionManagement
public class AtomikosConfig {
@Bean
public UserTransaction userTransaction() throws Throwable {
UserTransactionImp userTransactionImp = new UserTransactionImp();
userTransactionImp.setTransactionTimeout(300);
return userTransactionImp;
}
@Bean
public TransactionManager atomikosTransactionManager() {
UserTransactionManager userTransactionManager = new UserTransactionManager();
userTransactionManager.setForceShutdown(true);
return userTransactionManager;
}
@Bean
public JtaTransactionManager transactionManager() throws Throwable {
return new JtaTransactionManager(userTransaction(), atomikosTransactionManager());
}
}

View file

@ -0,0 +1,67 @@
package org.micord.config;
import com.atomikos.jdbc.AtomikosDataSourceBean;
import org.micord.models.SqlConnectionParams;
import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
* @author Maksim Tereshin
*/
public class DatabaseConnection {
private static final Map<String, DataSource> dataSources = new HashMap<>();
public static Connection getConnection(SqlConnectionParams params) throws SQLException {
try {
Class.forName(params.getJdbcDriverClassName());
} catch (ClassNotFoundException e) {
throw new SQLException("Unable to load the JDBC driver class", e);
}
return getXaDataSource(params).getConnection();
}
public static DataSource getXaDataSource(SqlConnectionParams params) {
String database = params.getJdbcDatabase();
if (!dataSources.containsKey(database)) {
AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
xaDataSource.setUniqueResourceName("jdbcDatasource_" + database);
xaDataSource.setXaDataSourceClassName(params.getJdbcXaDataSourceClassName());
xaDataSource.setPoolSize(Integer.parseInt(params.getJdbcXaDataSourcePoolSize()));
if (params.getJdbcXaDataSourceBorrowConnectionTimeout() != null) {
xaDataSource.setBorrowConnectionTimeout(Integer.parseInt(params.getJdbcXaDataSourceBorrowConnectionTimeout()));
}
Properties xaProperties = loadDatabaseProperties(params);
xaDataSource.setXaProperties(xaProperties);
dataSources.put(database, xaDataSource);
}
return dataSources.get(database);
}
private static Properties loadDatabaseProperties(SqlConnectionParams params) {
Properties xaProperties = new Properties();
try {
xaProperties.setProperty("user", params.getJdbcUsername());
xaProperties.setProperty("password", params.getJdbcPassword());
xaProperties.setProperty("serverName", params.getJdbcHost());
xaProperties.setProperty("portNumber", String.valueOf(params.getJdbcPort()));
xaProperties.setProperty("databaseName", params.getJdbcDatabase());
} catch (Exception e) {
throw new RuntimeException("Failed to load database properties", e);
}
return xaProperties;
}
}

View file

@ -0,0 +1,19 @@
package org.micord.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.net.http.HttpClient;
/**
* @author Maksim Tereshin
*/
@Configuration
public class HttpClientConfig {
@Bean
public HttpClient httpClient() {
return HttpClient.newHttpClient();
}
}

View file

@ -0,0 +1,76 @@
package org.micord.config;
import org.micord.models.S3ConnectionParams;
import org.micord.models.S3Request;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.URI;
import java.net.http.HttpRequest;
import java.nio.charset.StandardCharsets;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.Base64;
/**
* @author Maksim Tereshin
*/
public class S3HttpConnection {
public static HttpRequest buildHttpRequest(S3Request request, String file) throws Exception {
S3ConnectionParams connectionParams = request.getS3ConnectionParams();
String host = connectionParams.getHost() + ":" + connectionParams.getPort();
String s3Key = connectionParams.getS3Key();
String s3Secret = connectionParams.getS3Secret();
String method = connectionParams.getMethod().toUpperCase();
String body = connectionParams.getBody();
String resource = "/" + file;
String contentType = connectionParams.getContentType();
String date = ZonedDateTime.now().format(DateTimeFormatter.RFC_1123_DATE_TIME);
String signature = generateSignature(method, contentType, date, resource, s3Secret);
HttpRequest.Builder requestBuilder = HttpRequest.newBuilder()
.uri(URI.create("http://" + host + resource))
.header("Date", date)
.header("Content-Type", contentType)
.header("Authorization", "AWS " + s3Key + ":" + signature);
switch (method) {
case "DELETE":
requestBuilder.DELETE();
break;
case "GET":
requestBuilder.GET();
break;
case "PUT":
requestBuilder.PUT(HttpRequest.BodyPublishers.ofString(body != null ? body : ""));
break;
case "POST":
requestBuilder.POST(HttpRequest.BodyPublishers.ofString(body != null ? body : ""));
break;
default:
throw new IllegalArgumentException("Unsupported HTTP method: " + method);
}
return requestBuilder.build();
}
private static String generateSignature(String method, String contentType, String date, String resource, String s3Secret) throws Exception {
String stringToSign = method + "\n" +
"\n" + // MD5 - not used for DELETE requests
contentType + "\n" +
date + "\n" +
resource;
Mac mac = Mac.getInstance("HmacSHA1");
SecretKeySpec secretKey = new SecretKeySpec(s3Secret.getBytes(StandardCharsets.UTF_8), "HmacSHA1");
mac.init(secretKey);
byte[] hash = mac.doFinal(stringToSign.getBytes(StandardCharsets.UTF_8));
return Base64.getEncoder().encodeToString(hash);
}
}

View file

@ -0,0 +1,78 @@
package org.micord.controller;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.List;
import org.micord.models.DownloadCSVRequest;
import org.micord.service.ApiService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.InputStreamResource;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
/**
* REST Controller for API operations.
*/
@RestController
@RequestMapping("/api")
public class ApiController {
@Autowired
private ApiService apiService;
@PostMapping("/block")
public ResponseEntity<?> block(@RequestBody List<String> ids) throws FileNotFoundException {
apiService.process("block", ids);
return ResponseEntity.ok("");
}
@PostMapping("/unblock")
public ResponseEntity<?> unblock(@RequestBody List<String> ids) throws FileNotFoundException {
apiService.process("unblock", ids);
return ResponseEntity.ok("");
}
@PostMapping("/removeFromSystem")
public ResponseEntity<?> removeFromSystem(@RequestBody List<String> ids)
throws FileNotFoundException {
apiService.process("removeFromSystem", ids);
return ResponseEntity.ok("");
}
@PostMapping("/removeFromCallList")
public ResponseEntity<?> removeFromCallList(@RequestBody List<String> ids)
throws FileNotFoundException {
apiService.process("removeFromCallList", ids);
return ResponseEntity.ok("");
}
@PostMapping("/downloadCSV")
public ResponseEntity<Resource> downloadCSV(@RequestBody DownloadCSVRequest request)
throws IOException {
File csvFile = apiService.download("downloadCSV", request);
InputStreamResource resource = new InputStreamResource(new FileInputStream(csvFile));
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + csvFile.getName())
.contentType(MediaType.parseMediaType("text/csv"))
.contentLength(csvFile.length())
.body(resource);
}
@GetMapping("/listDownloadTypes")
public ResponseEntity<?> listDownloadTypes()
throws FileNotFoundException {
List<String> downloadCSVTypes = apiService.getDownloadTypes("downloadCSV");
return ResponseEntity.ok(downloadCSVTypes);
}
}

View file

@ -0,0 +1,19 @@
package org.micord.enums;
import lombok.Getter;
/**
* @author Maksim Tereshin
*/
@Getter
public enum RequestArgumentType {
SQL("SQL"),
AQL("AQL"),
S3("S3");
private final String type;
RequestArgumentType(String type) {
this.type = type;
}
}

View file

@ -0,0 +1,50 @@
package org.micord.models;
import lombok.Setter;
import jakarta.xml.bind.annotation.XmlElement;
/**
* @author Maksim Tereshin
*/
@Setter
public class AqlConnectionParams {
private String host;
private int port;
private String username;
private String password;
private String database;
private String collection;
@XmlElement(name = "Host")
public String getHost() {
return host;
}
@XmlElement(name = "Port")
public int getPort() {
return port;
}
@XmlElement(name = "Username")
public String getUsername() {
return username;
}
@XmlElement(name = "Password")
public String getPassword() {
return password;
}
@XmlElement(name = "Database")
public String getDatabase() {
return database;
}
@XmlElement(name = "Collection")
public String getCollection() {
return collection;
}
}

View file

@ -0,0 +1,42 @@
package org.micord.models;
import lombok.Setter;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlElementWrapper;
import java.util.List;
import java.util.stream.Collectors;
/**
* @author Maksim Tereshin
*/
@Setter
public class AqlRequest extends BaseRequest {
private AqlConnectionParams aqlConnectionParams;
private List<AqlRequestCollection> aqlRequestCollections;
@XmlElement(name = "AqlConnectionParams")
public AqlConnectionParams getAqlConnectionParams() {
return aqlConnectionParams;
}
@XmlElementWrapper(name = "AqlRequestCollections")
@XmlElement(name = "AqlRequestCollection")
public List<AqlRequestCollection> getAqlRequestCollections() {
return aqlRequestCollections;
}
public List<AqlRequestCollection> getReadCollections() {
return aqlRequestCollections.stream()
.filter(collection -> collection.getType() != null && collection.getType().contains("read"))
.collect(Collectors.toList());
}
public List<AqlRequestCollection> getWriteCollections() {
return aqlRequestCollections.stream()
.filter(collection -> collection.getType() != null && collection.getType().contains("write"))
.collect(Collectors.toList());
}
}

View file

@ -0,0 +1,27 @@
package org.micord.models;
import lombok.Setter;
import jakarta.xml.bind.annotation.XmlAttribute;
import jakarta.xml.bind.annotation.XmlValue;
/**
* @author Maksim Tereshin
*/
@Setter
public class AqlRequestCollection {
private String type;
private String collectionName;
@XmlAttribute(name = "type")
public String getType() {
return type;
}
@XmlValue
public String getCollectionName() {
return collectionName;
}
}

View file

@ -0,0 +1,29 @@
package org.micord.models;
import lombok.Setter;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlSeeAlso;
import java.util.List;
/**
* @author Maksim Tereshin
*/
@Setter
@XmlSeeAlso({SqlRequest.class, S3Request.class, AqlRequest.class})
public abstract class BaseRequest {
private List<RequestArgument> requestArguments;
private String requestURL;
@XmlElement(name = "RequestArgument")
public List<RequestArgument> getRequestArguments() {
return requestArguments;
}
@XmlElement(name = "RequestURL")
public String getRequestURL() {
return requestURL;
}
}

View file

@ -0,0 +1,21 @@
package org.micord.models;
import java.nio.file.attribute.FileTime;
public class CachedConfig {
private final Requests config;
private final FileTime modifiedTime;
public CachedConfig(Requests config, FileTime modifiedTime) {
this.config = config;
this.modifiedTime = modifiedTime;
}
public Requests getConfig() {
return config;
}
public FileTime getModifiedTime() {
return modifiedTime;
}
}

View file

@ -0,0 +1,13 @@
package org.micord.models;
import lombok.Data;
import java.util.List;
@Data
public class DownloadCSVRequest {
private String type;
private List<String> ids;
}

View file

@ -0,0 +1,23 @@
package org.micord.models;
import jakarta.xml.bind.annotation.XmlElement;
import lombok.Setter;
@Setter
public class DownloadRequest extends BaseRequest {
private SqlConnectionParams sqlConnectionParams;
private String downloadRequestType;
@XmlElement(name = "SqlConnectionParams")
public SqlConnectionParams getSqlConnectionParams() {
return sqlConnectionParams;
}
@XmlElement(name = "DownloadRequestType")
public String getDownloadRequestType() {
return downloadRequestType;
}
}

View file

@ -0,0 +1,29 @@
package org.micord.models;
import lombok.Setter;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlSeeAlso;
import java.util.List;
/**
* @author Maksim Tereshin
*/
@Setter
@XmlSeeAlso({SqlRequest.class, S3Request.class})
public abstract class Request {
private List<RequestArgument> requestArguments;
private String requestURL;
@XmlElement(name = "RequestArgument")
public List<RequestArgument> getRequestArguments() {
return requestArguments;
}
@XmlElement(name = "RequestURL")
public String getRequestURL() {
return requestURL;
}
}

View file

@ -0,0 +1,44 @@
package org.micord.models;
import lombok.Setter;
import org.micord.enums.RequestArgumentType;
import jakarta.xml.bind.annotation.XmlAttribute;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlElementWrapper;
import jakarta.xml.bind.annotation.XmlRootElement;
import java.util.List;
/**
* @author Maksim Tereshin
*/
@Setter
@XmlRootElement(name = "RequestArgument")
public class RequestArgument {
private RequestArgumentType type;
private String requestArgumentName;;
private String requestArgumentURL;
private SqlConnectionParams requestArgumentConnectionParams;
@XmlAttribute(name = "type")
public RequestArgumentType getType() {
return type;
}
@XmlElement(name = "RequestArgumentName")
public String getRequestArgumentName() {
return requestArgumentName;
}
@XmlElement(name = "RequestArgumentURL")
public String getRequestArgumentURL() {
return requestArgumentURL;
}
@XmlElement(name = "RequestArgumentConnectionParams")
public SqlConnectionParams getRequestArgumentConnectionParams() {
return requestArgumentConnectionParams;
}
}

View file

@ -0,0 +1,41 @@
package org.micord.models;
import lombok.Setter;
import jakarta.xml.bind.annotation.XmlElement;
import jakarta.xml.bind.annotation.XmlRootElement;
import java.util.List;
/**
* @author Maksim Tereshin
*/
@Setter
@XmlRootElement(name = "Requests")
public class Requests {
private List<SqlRequest> sqlRequests;
private List<AqlRequest> aqlRequests;
private List<S3Request> s3Requests;
private List<DownloadRequest> downloadRequests;
@XmlElement(name = "DownloadRequest")
public List<DownloadRequest> getDownloadRequests() {
return downloadRequests;
}
@XmlElement(name = "SqlRequest")
public List<SqlRequest> getSqlRequests() {
return sqlRequests;
}
@XmlElement(name = "AqlRequest")
public List<AqlRequest> getAqlRequests() {
return aqlRequests;
}
@XmlElement(name = "S3Request")
public List<S3Request> getS3Requests() {
return s3Requests;
}
}

View file

@ -0,0 +1,56 @@
package org.micord.models;
import lombok.Setter;
import jakarta.xml.bind.annotation.XmlElement;
/**
* @author Maksim Tereshin
*/
@Setter
public class S3ConnectionParams {
private String s3Key;
private String s3Secret;
private String host;
private String port;
private String contentType;
private String method;
private String body;
@XmlElement(name = "S3Key")
public String getS3Key() {
return s3Key;
}
@XmlElement(name = "S3Secret")
public String getS3Secret() {
return s3Secret;
}
@XmlElement(name = "Host")
public String getHost() {
return host;
}
@XmlElement(name = "Port")
public String getPort() {
return port;
}
@XmlElement(name = "ContentType")
public String getContentType() {
return contentType;
}
@XmlElement(name = "Method")
public String getMethod() {
return method;
}
@XmlElement(name = "Body")
public String getBody() {
return body;
}
}

View file

@ -0,0 +1,20 @@
package org.micord.models;
import lombok.Setter;
import jakarta.xml.bind.annotation.XmlElement;
/**
* @author Maksim Tereshin
*/
@Setter
public class S3Request extends BaseRequest {
private S3ConnectionParams s3ConnectionParams;
@XmlElement(name = "S3ConnectionParams")
public S3ConnectionParams getS3ConnectionParams() {
return s3ConnectionParams;
}
}

View file

@ -0,0 +1,66 @@
package org.micord.models;
import lombok.Setter;
import jakarta.xml.bind.annotation.XmlElement;
@Setter
public class SqlConnectionParams {
private String jdbcHost;
private String jdbcPort;
private String jdbcUsername;
private String jdbcPassword;
private String jdbcDriverClassName;
private String jdbcXaDataSourceClassName;
private String jdbcXaDataSourcePoolSize;
private String jdbcDatabase;
private String jdbcXaDataSourceBorrowConnectionTimeout;
@XmlElement(name = "JdbcXaDataSourceBorrowConnectionTimeout")
public String getJdbcXaDataSourceBorrowConnectionTimeout() {
return jdbcXaDataSourceBorrowConnectionTimeout;
}
@XmlElement(name = "JdbcXaDataSourcePoolSize")
public String getJdbcXaDataSourcePoolSize() {
return jdbcXaDataSourcePoolSize;
}
@XmlElement(name = "JdbcHost")
public String getJdbcHost() {
return jdbcHost;
}
@XmlElement(name = "JdbcPort")
public String getJdbcPort() {
return jdbcPort;
}
@XmlElement(name = "JdbcUsername")
public String getJdbcUsername() {
return jdbcUsername;
}
@XmlElement(name = "JdbcPassword")
public String getJdbcPassword() {
return jdbcPassword;
}
@XmlElement(name = "JdbcDriverClassName")
public String getJdbcDriverClassName() {
return jdbcDriverClassName;
}
@XmlElement(name = "JdbcXaDataSourceClassName")
public String getJdbcXaDataSourceClassName() {
return jdbcXaDataSourceClassName;
}
@XmlElement(name = "JdbcDatabase")
public String getJdbcDatabase() {
return jdbcDatabase;
}
}

View file

@ -0,0 +1,20 @@
package org.micord.models;
import lombok.Setter;
import jakarta.xml.bind.annotation.XmlElement;
/**
* @author Maksim Tereshin
*/
@Setter
public class SqlRequest extends BaseRequest {
private SqlConnectionParams sqlConnectionParams;
@XmlElement(name = "SqlConnectionParams")
public SqlConnectionParams getSqlConnectionParams() {
return sqlConnectionParams;
}
}

View file

@ -0,0 +1,64 @@
package org.micord.service;
import org.micord.models.*;
import org.micord.utils.ConfigLoader;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.*;
import java.util.stream.Collectors;
@Service
public class ApiService {
@Autowired
private ConfigLoader configLoader;
@Autowired
private RequestService sqlAndAqlService;
@Autowired
private DownloadService downloadService;
public void process(String methodName, List<String> ids) throws FileNotFoundException {
Requests config = getConfig(methodName);
sqlAndAqlService.processSqlAndAqlRequests(config, ids);
}
public File download(String methodName, DownloadCSVRequest request) throws IOException {
Requests config = getConfig(methodName);
String type = request.getType();
List<String> ids = Optional.ofNullable(request.getIds())
.filter(list -> !list.isEmpty())
.orElse(null);
DownloadRequest selectedRequest = config.getDownloadRequests().stream()
.filter(r -> r.getDownloadRequestType().equals(type))
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("Invalid download type: " + type));
return downloadService.download(selectedRequest, ids);
}
public List<String> getDownloadTypes(String methodName) throws FileNotFoundException {
Requests config = getConfig(methodName);
return config.getDownloadRequests().stream()
.map(DownloadRequest::getDownloadRequestType)
.collect(Collectors.toList());
}
private Requests getConfig(String methodName) throws FileNotFoundException {
Optional<Requests> optionalConfig = configLoader.loadConfigIfModified(methodName);
if (optionalConfig.isEmpty()) {
throw new FileNotFoundException("Configuration for method " + methodName + " could not be loaded.");
}
return optionalConfig.get();
}
}

View file

@ -0,0 +1,157 @@
package org.micord.service;
import org.micord.config.DatabaseConnection;
import org.micord.models.DownloadRequest;
import org.micord.models.RequestArgument;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Service
public class DownloadService {
private static final Logger logger = LoggerFactory.getLogger(DownloadService.class);
public File download(DownloadRequest selectedRequest, List<String> ids) {
return processDownloadRequest(selectedRequest, ids);
}
private File processDownloadRequest(DownloadRequest request, List<String> ids) {
Map<String, Object> query = buildSqlQuery(request, ids);
try (Connection connection = DatabaseConnection.getConnection(
request.getSqlConnectionParams())) {
String requestURL = (String) query.get("requestURL");
List<String[]> results = executeSqlQuery(connection, requestURL);
File csvFile = File.createTempFile("download-", ".csv");
try (PrintWriter writer = new PrintWriter(csvFile)) {
for (String[] row : results) {
writer.println(String.join(",", row));
}
} catch (IOException e) {
logger.error("Failed to write to CSV file", e);
}
return csvFile;
}
catch (SQLException | IOException e) {
logger.error("SQL execution failed for query: {}", query, e);
}
return null;
}
private Map<String, Object> buildSqlQuery(DownloadRequest request, List<String> ids) {
Map<String, Object> resultMap = new HashMap<>();
String endpointArguments;
String requestURL = request.getRequestURL();
if (ids == null || ids.isEmpty()) {
resultMap.put("requestURL", requestURL
.replace("${DB}", request.getSqlConnectionParams().getJdbcDatabase())
.replace("where id in ${endpointArguments}", ""));
return resultMap;
}
if (requestURL.contains(":=")) {
endpointArguments = "'{" + ids.stream()
.map(String::trim)
.collect(Collectors.joining(", ")) + "}'";
} else {
endpointArguments = "(" + ids.stream()
.map(s -> "'" + s.trim() + "'")
.collect(Collectors.joining(", ")) + ")";
}
if (request.getRequestArguments() != null && !request.getRequestArguments().isEmpty()) {
for (RequestArgument argument : request.getRequestArguments()) {
if (argument.getRequestArgumentConnectionParams() != null) {
try (Connection connection = DatabaseConnection.getConnection(
argument.getRequestArgumentConnectionParams())) {
String query = argument.getRequestArgumentURL();
List<String> result = fetchFileListFromDatabaseSQL(connection, query);
resultMap.put("ids", result);
if (result != null && !result.isEmpty()) {
String resultSet = "(" + result.stream()
.map(s -> "'" + s.trim() + "'")
.collect(Collectors.joining(", ")) + ")";
requestURL = requestURL.replace("${" + argument.getRequestArgumentName() + "}", resultSet);
}
}
catch (SQLException e) {
logger.error("Failed to execute query for RequestArgument", e);
}
}
}
}
resultMap.put("requestURL", requestURL
.replace("${DB}", request.getSqlConnectionParams().getJdbcDatabase())
.replace("${endpointArguments}", endpointArguments));
return resultMap;
}
private List<String[]> executeSqlQuery(Connection connection, String query) throws SQLException {
List<String[]> results = new ArrayList<>();
try (PreparedStatement stmt = connection.prepareStatement(query);
ResultSet resultSet = stmt.executeQuery()) {
int columnCount = resultSet.getMetaData().getColumnCount();
// Add headers
String[] headers = new String[columnCount];
for (int i = 1; i <= columnCount; i++) {
headers[i - 1] = resultSet.getMetaData().getColumnName(i);
}
results.add(headers);
// Add rows
while (resultSet.next()) {
String[] row = new String[columnCount];
for (int i = 1; i <= columnCount; i++) {
row[i - 1] = resultSet.getString(i);
}
results.add(row);
}
}
return results;
}
public List<String> fetchFileListFromDatabaseSQL(Connection connection, String query)
throws SQLException {
List<String> results = new ArrayList<>();
try (PreparedStatement stmt = connection.prepareStatement(query);
ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
results.add(rs.getString(1)); // Fetch the first column
}
}
return results;
}
}

View file

@ -0,0 +1,329 @@
package org.micord.service;
import java.net.HttpURLConnection;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.stream.Collectors;
import com.arangodb.ArangoCursor;
import com.arangodb.ArangoDBException;
import com.arangodb.ArangoDatabase;
import com.arangodb.entity.StreamTransactionEntity;
import com.arangodb.model.AqlQueryOptions;
import com.arangodb.model.StreamTransactionOptions;
import org.micord.config.ArangoDBConnection;
import org.micord.config.DatabaseConnection;
import org.micord.config.S3HttpConnection;
import org.micord.enums.RequestArgumentType;
import org.micord.models.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* @author Maksim Tereshin
*/
@Service
public class RequestService {
private static final Logger logger = LoggerFactory.getLogger(RequestService.class);
@Autowired
private HttpClient httpClient;
public void processS3Requests(List<S3Request> s3Requests, List<String> ids) {
if (s3Requests != null) {
s3Requests.forEach(request -> {
List<CompletableFuture<Void>> futures = ids.stream()
.map(id -> CompletableFuture.runAsync(() -> processS3Request(request, id)))
.toList();
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]))
.thenRun(() -> logger.info("Successfully processed all S3 requests."))
.exceptionally(ex -> {
logger.error("Failed to process S3 requests", ex);
return null;
});
});
}
}
private void processS3Request(S3Request request, String id) {
try {
List<String> files = new ArrayList<>();
if (request.getRequestArguments() != null && !request.getRequestArguments().isEmpty()) {
for (RequestArgument argument : request.getRequestArguments()) {
try (Connection connection = DatabaseConnection.getConnection(
argument.getRequestArgumentConnectionParams())) {
String query = argument.getRequestArgumentURL();
List<String> result = fetchFileListFromDatabaseSQL(connection, query);
if (result != null && !result.isEmpty()) {
files.addAll(result);
}
}
catch (SQLException e) {
logger.error("Failed to execute query for RequestArgument", e);
}
}
}
files.forEach(file -> {
HttpRequest httpRequest;
try {
httpRequest = S3HttpConnection.buildHttpRequest(request, file);
}
catch (Exception e) {
throw new RuntimeException(e);
}
httpClient.sendAsync(httpRequest, HttpResponse.BodyHandlers.ofString())
.thenAccept(response -> {
if (response.statusCode() == HttpURLConnection.HTTP_NO_CONTENT
|| response.statusCode() == HttpURLConnection.HTTP_OK) {
logger.info("Successfully deleted object for ID {}", id);
}
else {
logger.error("Failed to delete object for ID {}. Response code: {}", id,
response.statusCode()
);
}
})
.exceptionally(ex -> {
logger.error("Failed to delete object for ID {}", id, ex);
return null;
});
});
}
catch (Exception e) {
logger.error("Failed to process S3 request for id: {}", id, e);
}
}
@Transactional
public void processSqlAndAqlRequests(Requests config, List<String> ids) {
if (config.getSqlRequests() != null) {
for (SqlRequest request : config.getSqlRequests()) {
processSqlRequests(request, ids);
}
}
if (config.getAqlRequests() != null) {
for (AqlRequest request : config.getAqlRequests()) {
processAqlRequests(request, ids);
}
}
processS3Requests(config.getS3Requests(), ids);
}
private void processSqlRequests(SqlRequest request, List<String> ids) {
Map<String, Object> query = buildSqlQuery(request, ids);
try (Connection connection = DatabaseConnection.getConnection(
request.getSqlConnectionParams())) {
String requestURL = (String) query.get("requestURL");
executeSqlQuery(connection, requestURL);
List<String> queryIds = (List<String>) query.get("ids");
if (queryIds != null && !queryIds.isEmpty()) {
ids.addAll(queryIds);
} else {
logger.warn("No IDs found for the query");
}
logger.info("Successfully executed query {} for IDs: ({})", requestURL, String.join(", ", ids));
}
catch (SQLException e) {
logger.error("SQL execution failed for query: {}", query, e);
}
}
private Map<String, Object> buildSqlQuery(SqlRequest request, List<String> ids) {
Map<String, Object> resultMap = new HashMap<>();
String endpointArguments;
String requestURL = request.getRequestURL();
if (requestURL.contains(":=")) {
endpointArguments = "'{" + ids.stream()
.map(String::trim)
.collect(Collectors.joining(", ")) + "}'";
} else {
endpointArguments = "(" + ids.stream()
.map(s -> "'" + s.trim() + "'")
.collect(Collectors.joining(", ")) + ")";
}
if (request.getRequestArguments() != null && !request.getRequestArguments().isEmpty()) {
for (RequestArgument argument : request.getRequestArguments()) {
if (argument.getRequestArgumentConnectionParams() != null) {
try (Connection connection = DatabaseConnection.getConnection(
argument.getRequestArgumentConnectionParams())) {
String query = argument.getRequestArgumentURL();
List<String> result = fetchFileListFromDatabaseSQL(connection, query);
resultMap.put("ids", result);
if (result != null && !result.isEmpty()) {
String resultSet = "(" + result.stream()
.map(s -> "'" + s.trim() + "'")
.collect(Collectors.joining(", ")) + ")";
requestURL = requestURL.replace("${" + argument.getRequestArgumentName() + "}", resultSet);
}
}
catch (SQLException e) {
logger.error("Failed to execute query for RequestArgument", e);
}
}
}
}
resultMap.put("requestURL", requestURL
.replace("${DB}", request.getSqlConnectionParams().getJdbcDatabase())
.replace("${endpointArguments}", endpointArguments));
return resultMap;
}
private boolean executeSqlQuery(Connection connection, String query) throws SQLException {
try (PreparedStatement stmt = connection.prepareStatement(query)) {
return stmt.execute();
}
}
public List<String> fetchFileListFromDatabaseSQL(Connection connection, String query)
throws SQLException {
List<String> results = new ArrayList<>();
try (PreparedStatement stmt = connection.prepareStatement(query);
ResultSet rs = stmt.executeQuery()) {
while (rs.next()) {
results.add(rs.getString(1)); // Fetch the first column
}
}
return results;
}
private void processAqlRequests(AqlRequest request, List<String> ids) {
ArangoDatabase arangoDb = ArangoDBConnection.getConnection(request.getAqlConnectionParams());
// TODO: implement for multiple request arguments
RequestArgument requestArgument = request.getRequestArguments().get(0);
List<String> aqlCollectionRead = request.getReadCollections().stream()
.map(AqlRequestCollection::getCollectionName)
.toList();
String aqlCollectionWrite = request.getWriteCollections().stream()
.map(AqlRequestCollection::getCollectionName)
.findFirst().orElseGet(null);
StreamTransactionEntity tx = null;
try {
StreamTransactionOptions options = new StreamTransactionOptions()
.writeCollections(aqlCollectionWrite)
.readCollections(aqlCollectionRead.toArray(new String[0]));
tx = arangoDb.beginStreamTransaction(options);
String transactionId = tx.getId();
logger.info("Stream transaction started with ID: {}", transactionId);
Map<String, Object> entities = executeSelectAqlRequest(arangoDb, aqlCollectionWrite, requestArgument, ids, transactionId);
executeMainAqlRequest(arangoDb, aqlCollectionWrite, request.getRequestURL(), entities, transactionId);
arangoDb.commitStreamTransaction(transactionId);
logger.info("Stream transaction with ID {} committed successfully", transactionId);
}
catch (ArangoDBException e) {
if (tx != null) {
arangoDb.abortStreamTransaction(tx.getId());
logger.error("Stream transaction with ID {} aborted due to an error", tx.getId(), e);
}
throw new RuntimeException("Failed to execute AQL request within a stream transaction", e);
}
logger.info("Successfully executed AQL request");
}
private Map<String, Object> executeSelectAqlRequest(ArangoDatabase arangoDb,
String aqlCollectionWrite,
RequestArgument requestArgument,
List<String> ids, String transactionId) {
Map<String, Object> entities = new HashMap<>();
String url = requestArgument.getRequestArgumentURL();
RequestArgumentType type = requestArgument.getType();
if (type == RequestArgumentType.AQL) {
Map<String, Object> bindVars = new HashMap<>();
bindVars.put("ids", ids);
AqlQueryOptions aqlQueryOptions = new AqlQueryOptions().streamTransactionId(transactionId);
try (ArangoCursor<Map> cursor = arangoDb.query(url, Map.class, bindVars, aqlQueryOptions)) {
while (cursor.hasNext()) {
Map<String, Object> result = cursor.next();
for (Map.Entry<String, Object> entry : result.entrySet()) {
String key = entry.getKey();
Object entityValue = entry.getValue();
entities.put(key, entityValue);
}
}
}
catch (Exception e) {
logger.error("Failed to execute AQL url", e);
}
} else if (type == RequestArgumentType.SQL) {
if (requestArgument.getRequestArgumentConnectionParams() != null) {
try (Connection connection = DatabaseConnection.getConnection(
requestArgument.getRequestArgumentConnectionParams())) {
String query = requestArgument.getRequestArgumentURL();
List<String> result = fetchFileListFromDatabaseSQL(connection, query);
entities.put(aqlCollectionWrite, result);
}
catch (SQLException e) {
logger.error("Failed to execute query for RequestArgument", e);
}
}
}
return entities;
}
private void executeMainAqlRequest(ArangoDatabase arangoDb, String aqlCollectionWrite, String requestURL,
Map<String, Object> entities, String transactionId) {
if (entities == null || entities.isEmpty()) {
logger.warn("No entities found for main AQL request.");
return;
}
Map<String, Object> bindVars = new HashMap<>();
// TODO: verify correctness of received entities and compare keys
Object writeEntity = entities.get(aqlCollectionWrite);
bindVars.put("ids", entities);
AqlQueryOptions aqlQueryOptions = new AqlQueryOptions().streamTransactionId(transactionId);
arangoDb.query(requestURL, null, bindVars, aqlQueryOptions);
logger.info("Successfully removed {}: {}", aqlCollectionWrite, writeEntity);
}
}

View file

@ -0,0 +1,70 @@
package org.micord.utils;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.attribute.FileTime;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import jakarta.xml.bind.JAXBContext;
import jakarta.xml.bind.JAXBException;
import jakarta.xml.bind.Unmarshaller;
import org.micord.models.CachedConfig;
import org.micord.models.Requests;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
/**
* @author Maksim Tereshin
*/
@Component
public class ConfigLoader {
private static final Logger LOGGER = Logger.getLogger(ConfigLoader.class.getName());
private static final Map<String, CachedConfig> cachedConfigs = new ConcurrentHashMap<>();
@Value("${configDirectory}")
private String configDirectory;
public Optional<Requests> loadConfigIfModified(String methodName) {
String fileName = methodName + ".xml";
if (configDirectory == null) {
LOGGER.log(Level.SEVERE, "No configuration directory found for method: " + methodName);
return Optional.empty();
}
try {
File configFile = new File(configDirectory + File.separator + fileName);
Path configFilePath = configFile.toPath();
FileTime currentModifiedTime = Files.getLastModifiedTime(configFilePath);
CachedConfig cachedConfig = cachedConfigs.getOrDefault(methodName, null);
if (cachedConfig == null || !currentModifiedTime.equals(cachedConfig.getModifiedTime())) {
// Load the updated configuration
JAXBContext jaxbContext = JAXBContext.newInstance(Requests.class);
Unmarshaller unmarshaller = jaxbContext.createUnmarshaller();
Requests loadedConfig = (Requests) unmarshaller.unmarshal(configFile);
cachedConfigs.put(methodName, new CachedConfig(loadedConfig, currentModifiedTime));
return Optional.of(loadedConfig);
}
else {
return Optional.of(cachedConfigs.get(methodName).getConfig());
}
}
catch (IOException e) {
LOGGER.log(Level.SEVERE, "Failed to load configuration file: " + fileName, e);
return Optional.empty(); // Return empty if there is an IO error
}
catch (JAXBException e) {
LOGGER.log(Level.SEVERE, "Failed to unmarshal configuration file: " + fileName, e);
return Optional.empty(); // Return empty if unmarshalling fails
}
}
}

View file

@ -0,0 +1 @@
configDirectory: C:\work\ervu-secret\config\cde-xml

749
config.md Normal file
View 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
View file

@ -0,0 +1,2 @@
/*.ear
/*.jar

43
config/Dockerfile Normal file
View file

@ -0,0 +1,43 @@
FROM quay.io/wildfly/wildfly:26.1.3.Final-jdk17
USER root
ARG POSTGRES_DRIVER_VERSION=42.7.3
RUN yum-config-manager --disable base --disable extras --disable updates \
&& yum-config-manager --enable C7.8.2003-base --enable C7.8.2003-extras --enable C7.8.2003-updates \
&& yum -y --disableplugin=fastestmirror 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/
COPY --chown=jboss *.war $JBOSS_HOME/standalone/deployments/

58
config/Dockerfile.build Normal file
View file

@ -0,0 +1,58 @@
FROM maven:3-openjdk-17-slim AS builder
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 \
&& mvn package -T4C \
&& cd config-data-executor \
&& mvn clean \
&& mvn package -T4C
FROM quay.io/wildfly/wildfly:26.1.3.Final-jdk17
USER root
ARG POSTGRES_DRIVER_VERSION=42.7.3
RUN yum-config-manager --disable base --disable extras --disable updates \
&& yum-config-manager --enable C7.8.2003-base --enable C7.8.2003-extras --enable C7.8.2003-updates \
&& yum -y --disableplugin=fastestmirror 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 config/entrypoint.sh .
COPY --chown=jboss config/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 --from=builder --chown=jboss /app/distribution/target/*.ear $JBOSS_HOME/standalone/deployments/
COPY --from=builder --chown=jboss /app/config-data-executor/target/*.war $JBOSS_HOME/standalone/deployments/

1
config/JDK_version Normal file
View file

@ -0,0 +1 @@
17

View file

@ -0,0 +1,50 @@
<Requests>
<AqlRequest>
<RequestURL>
<![CDATA[
with edges
for edg IN edges
filter edg._key in ${argument}
remove edg IN edges
]]>
</RequestURL>
<RequestArgument type="AQL">
<RequestArgumentURL>
<![CDATA[
WITH applications, subject, history, edges
FOR app IN applications
FILTER app.statement.recruitsData.mainInfo[0].id IN @ids
LET parentEdges = (
FOR vertex, edge, path
IN 1..1
OUTBOUND app._id edges
OPTIONS { uniqueVertices: "path" }
FILTER edge.field IN ["applicant", "history", "interdepreq"]
RETURN { edgesId: edge._key, parent: DOCUMENT(vertex._id) }
)
RETURN {
applicationId: app._key,
edgesId: (FOR e IN parentEdges RETURN e.edgesId),
subjectId: (FOR e IN parentEdges FILTER e.parent.schema == "Subject" RETURN e.parent._key),
historyId: (FOR e IN parentEdges FILTER e.parent.schema == "History" RETURN e.parent._key),
interdepreqId: (FOR e IN parentEdges FILTER e.parent.schema == "Interdepreq" RETURN e.parent._key)
}
]]>
</RequestArgumentURL>
</RequestArgument>
<AqlRequestCollections>
<AqlRequestCollection type="read">applications</AqlRequestCollection>
<AqlRequestCollection type="read">subject</AqlRequestCollection>
<AqlRequestCollection type="read">history</AqlRequestCollection>
<AqlRequestCollection type="read, write">edges</AqlRequestCollection>
</AqlRequestCollections>
<AqlConnectionParams>
<Host>localhost</Host>
<Port>8529</Port>
<Username>root</Username>
<Password>test</Password>
<Database>_system</Database>
</AqlConnectionParams>
</AqlRequest>
</Requests>

167
config/cde-xml/block.xml Normal file
View file

@ -0,0 +1,167 @@
<Requests>
<SqlRequest>
<RequestURL>
<![CDATA[
DO
$$
begin
update recruits
set current_recruitment_id = '04047533-ee2d-4266-b656-e509a547f0e9'
,target_recruitment_id = '04047533-ee2d-4266-b656-e509a547f0e9'
,department_id_old = '04047533-ee2d-4266-b656-e509a547f0e9'
,system_pgs_status = '13'
,extra_info = jsonb_set(coalesce(extra_info,'{}'::jsonb), '{blocked}',
('{"cur":'|| coalesce('"'||current_recruitment_id::text||'"','null')||
',"trg":'|| coalesce('"'||target_recruitment_id::text||'"','null')||
',"dio":'|| coalesce('"'||department_id_old::text||'"','null')||
',"st":'|| coalesce('"'||system_pgs_status::text||'"','null')||'}')::jsonb
)
where id in ${endpointArguments};
end
$$;
]]>
</RequestURL>
<SqlConnectionParams>
<JdbcHost>10.10.31.118</JdbcHost>
<JdbcPort>5432</JdbcPort>
<JdbcUsername>ervu</JdbcUsername>
<JdbcPassword>ervu</JdbcPassword>
<JdbcDriverClassName>org.postgresql.Driver</JdbcDriverClassName>
<JdbcXaDataSourceClassName>org.postgresql.xa.PGXADataSource</JdbcXaDataSourceClassName>
<JdbcXaDataSourcePoolSize>50</JdbcXaDataSourcePoolSize>
<JdbcDatabase>person_registry</JdbcDatabase>
</SqlConnectionParams>
</SqlRequest>
<SqlRequest>
<RequestURL>
<![CDATA[
DO
$$
BEGIN
DELETE FROM subpoena_history where subpoena_id in (select id FROM subpoena where recruit_id in ${endpointArguments});
DELETE FROM subpoena_appearance where subpoena_id in (select id FROM subpoena where recruit_id in ${endpointArguments});
DELETE FROM subpoena_send_info where subpoena_id in (select id FROM subpoena where recruit_id in ${endpointArguments});
DELETE FROM notification_item where restriction_document_item_id in (
select id from restriction_document_item where restriction_document_create_id in (
select id FROM restriction_document where subpoena_id in (select id FROM subpoena where recruit_id in ${endpointArguments})));
DELETE FROM notification_item where restriction_document_item_id in (
select id from restriction_document_item where restriction_document_cancel_id in (
select id FROM restriction_document where subpoena_id in (select id FROM subpoena where recruit_id in ${endpointArguments})));
DELETE FROM restriction_document_item where restriction_document_create_id in (
select id FROM restriction_document where subpoena_id in (select id FROM subpoena where recruit_id in ${endpointArguments}));
DELETE FROM restriction_document_item where restriction_document_cancel_id in (
select id FROM restriction_document where subpoena_id in (select id FROM subpoena where recruit_id in ${endpointArguments}));
DELETE FROM restriction_document where subpoena_id in (select id FROM subpoena where recruit_id in ${endpointArguments});
DELETE FROM subpoena where recruit_id in ${endpointArguments};
END
$$;
]]>
</RequestURL>
<SqlConnectionParams>
<JdbcHost>10.10.31.118</JdbcHost>
<JdbcPort>5432</JdbcPort>
<JdbcUsername>ervu</JdbcUsername>
<JdbcPassword>ervu</JdbcPassword>
<JdbcDriverClassName>org.postgresql.Driver</JdbcDriverClassName>
<JdbcXaDataSourceClassName>org.postgresql.xa.PGXADataSource</JdbcXaDataSourceClassName>
<JdbcXaDataSourcePoolSize>50</JdbcXaDataSourcePoolSize>
<JdbcDatabase>subpoena</JdbcDatabase>
</SqlConnectionParams>
</SqlRequest>
<SqlRequest>
<RequestURL>
<![CDATA[
DO
$$
BEGIN
DELETE
FROM incident_history
where incident_id in (select id from incident where recruit_id in ${endpointArguments});
DELETE FROM incident where recruit_id in ${endpointArguments};
END
$$;
]]>
</RequestURL>
<SqlConnectionParams>
<JdbcHost>10.10.31.118</JdbcHost>
<JdbcPort>5432</JdbcPort>
<JdbcUsername>ervu</JdbcUsername>
<JdbcPassword>ervu</JdbcPassword>
<JdbcDriverClassName>org.postgresql.Driver</JdbcDriverClassName>
<JdbcXaDataSourceClassName>org.postgresql.xa.PGXADataSource</JdbcXaDataSourceClassName>
<JdbcXaDataSourcePoolSize>50</JdbcXaDataSourcePoolSize>
<JdbcDatabase>incidents</JdbcDatabase>
</SqlConnectionParams>
</SqlRequest>
<SqlRequest>
<RequestURL>
<![CDATA[
DO
$$
DECLARE
var1 uuid[] := '{
${endpointArguments}
}';
BEGIN
DELETE FROM notifications where recruit_id = any (var1);
END
$$;
]]>
</RequestURL>
<SqlConnectionParams>
<JdbcHost>10.10.31.118</JdbcHost>
<JdbcPort>5432</JdbcPort>
<JdbcUsername>ervu</JdbcUsername>
<JdbcPassword>ervu</JdbcPassword>
<JdbcDriverClassName>org.postgresql.Driver</JdbcDriverClassName>
<JdbcXaDataSourceClassName>org.postgresql.xa.PGXADataSource</JdbcXaDataSourceClassName>
<JdbcXaDataSourcePoolSize>50</JdbcXaDataSourcePoolSize>
<JdbcDatabase>geps_notifications</JdbcDatabase>
</SqlConnectionParams>
</SqlRequest>
<SqlRequest>
<RequestURL>
<![CDATA[
DO
$$
DECLARE
recruitIds uuid[] := ${endpointArguments};
importIds uuid[] := ARRAY(SELECT import_id
FROM import_results
WHERE recruit_id = ANY (recruitIds));
BEGIN
WITH ImportDeletes AS (DELETE FROM import_journal_start WHERE import_id = ANY (importIds) RETURNING journal_id)
DELETE
FROM journal
WHERE id IN (SELECT journal_id FROM ImportDeletes);
DELETE FROM import_journal_finish WHERE import_id = ANY (importIds);
DELETE FROM import_events_journal WHERE import_id = ANY (importIds);
DELETE FROM import_validation_errors WHERE import_id = ANY (importIds);
DELETE FROM import_results WHERE import_id = ANY (importIds);
DELETE FROM uploaded_files WHERE extra_info ->> 'importId' = ANY (importIds::text[]);
DELETE FROM object_history WHERE object_id = ANY (recruitIds);
DELETE FROM object_versions WHERE object_id = ANY (recruitIds);
END
$$;
]]>
</RequestURL>
<SqlConnectionParams>
<JdbcHost>10.10.31.118</JdbcHost>
<JdbcPort>5432</JdbcPort>
<JdbcUsername>ervu</JdbcUsername>
<JdbcPassword>ervu</JdbcPassword>
<JdbcDriverClassName>org.postgresql.Driver</JdbcDriverClassName>
<JdbcXaDataSourceClassName>org.postgresql.xa.PGXADataSource</JdbcXaDataSourceClassName>
<JdbcXaDataSourcePoolSize>50</JdbcXaDataSourcePoolSize>
<JdbcDatabase>journal_dev</JdbcDatabase>
</SqlConnectionParams>
</SqlRequest>
</Requests>

View file

@ -0,0 +1,54 @@
<Requests>
<DownloadRequest>
<DownloadRequestType>Type_A</DownloadRequestType>
<RequestURL>
Select system_id_ern from public.recruits where id in ${endpointArguments};
</RequestURL>
<SqlConnectionParams>
<JdbcHost>10.10.31.118</JdbcHost>
<JdbcPort>5432</JdbcPort>
<JdbcUsername>ervu</JdbcUsername>
<JdbcPassword>ervu</JdbcPassword>
<JdbcDriverClassName>org.postgresql.Driver</JdbcDriverClassName>
<JdbcXaDataSourceClassName>org.postgresql.xa.PGXADataSource</JdbcXaDataSourceClassName>
<JdbcXaDataSourcePoolSize>50</JdbcXaDataSourcePoolSize>
<JdbcDatabase>person_registry</JdbcDatabase>
<JdbcXaDataSourceBorrowConnectionTimeout>4000</JdbcXaDataSourceBorrowConnectionTimeout>
</SqlConnectionParams>
</DownloadRequest>
<DownloadRequest>
<DownloadRequestType>Type_B</DownloadRequestType>
<RequestURL>
Select system_id_ern from public.recruits where id in ${endpointArguments};
</RequestURL>
<SqlConnectionParams>
<JdbcHost>10.10.31.118</JdbcHost>
<JdbcPort>5432</JdbcPort>
<JdbcUsername>ervu</JdbcUsername>
<JdbcPassword>ervu</JdbcPassword>
<JdbcDriverClassName>org.postgresql.Driver</JdbcDriverClassName>
<JdbcXaDataSourceClassName>org.postgresql.xa.PGXADataSource</JdbcXaDataSourceClassName>
<JdbcXaDataSourcePoolSize>50</JdbcXaDataSourcePoolSize>
<JdbcDatabase>person_registry</JdbcDatabase>
<JdbcXaDataSourceBorrowConnectionTimeout>4000</JdbcXaDataSourceBorrowConnectionTimeout>
</SqlConnectionParams>
</DownloadRequest>
<DownloadRequest>
<DownloadRequestType>Type_C</DownloadRequestType>
<RequestURL>
Select system_id_ern from public.recruits where id in ${endpointArguments};
</RequestURL>
<SqlConnectionParams>
<JdbcHost>10.10.31.118</JdbcHost>
<JdbcPort>5432</JdbcPort>
<JdbcUsername>ervu</JdbcUsername>
<JdbcPassword>ervu</JdbcPassword>
<JdbcDriverClassName>org.postgresql.Driver</JdbcDriverClassName>
<JdbcXaDataSourceClassName>org.postgresql.xa.PGXADataSource</JdbcXaDataSourceClassName>
<JdbcXaDataSourcePoolSize>50</JdbcXaDataSourcePoolSize>
<JdbcDatabase>person_registry</JdbcDatabase>
<JdbcXaDataSourceBorrowConnectionTimeout>4000</JdbcXaDataSourceBorrowConnectionTimeout>
</SqlConnectionParams>
</DownloadRequest>
</Requests>

View file

@ -0,0 +1,52 @@
<Requests>
<SqlRequest>
<RequestURL>
<![CDATA[
DO
$$
BEGIN
DELETE FROM summoned_list where recruit_id in ${endpointArguments};
DELETE FROM recruit_active_list where recruit_id in ${endpointArguments};
DELETE FROM documents where recruit_id in ${endpointArguments};
DELETE FROM subpoena where recruit_id in ${endpointArguments};
END
$$;
]]>
</RequestURL>
<SqlConnectionParams>
<JdbcHost>10.10.31.118</JdbcHost>
<JdbcPort>5432</JdbcPort>
<JdbcUsername>ervu</JdbcUsername>
<JdbcPassword>ervu</JdbcPassword>
<JdbcDriverClassName>org.postgresql.Driver</JdbcDriverClassName>
<JdbcXaDataSourceClassName>org.postgresql.xa.PGXADataSource</JdbcXaDataSourceClassName>
<JdbcXaDataSourcePoolSize>50</JdbcXaDataSourcePoolSize>
<JdbcDatabase>summon-list-registry</JdbcDatabase>
</SqlConnectionParams>
</SqlRequest>
<SqlRequest>
<RequestURL>
<![CDATA[
update recruits
set current_recruitment_id = jsonb_extract_path_text(extra_info,'blocked','cur')::uuid
,target_recruitment_id = jsonb_extract_path_text(extra_info,'blocked','trg')::uuid
,department_id_old = jsonb_extract_path_text(extra_info,'blocked','dio')::uuid
,system_pgs_status = '1.2'
,conscription = null
,extra_info = extra_info - ('blocked')
where id in ${endpointArguments};
]]>
</RequestURL>
<SqlConnectionParams>
<JdbcHost>10.10.31.118</JdbcHost>
<JdbcPort>5432</JdbcPort>
<JdbcUsername>ervu</JdbcUsername>
<JdbcPassword>ervu</JdbcPassword>
<JdbcDriverClassName>org.postgresql.Driver</JdbcDriverClassName>
<JdbcXaDataSourceClassName>org.postgresql.xa.PGXADataSource</JdbcXaDataSourceClassName>
<JdbcXaDataSourcePoolSize>50</JdbcXaDataSourcePoolSize>
<JdbcDatabase>person_registry</JdbcDatabase>
</SqlConnectionParams>
</SqlRequest>
</Requests>

View file

@ -0,0 +1,244 @@
<Requests>
<!-- 002_2-ervu_subpoena_registry_delete_all_with_recruit -->
<SqlRequest>
<RequestURL>
<![CDATA[
DO
$$
BEGIN
DELETE FROM subpoena_history where subpoena_id in (select id FROM subpoena where recruit_id in ${endpointArguments});
DELETE FROM subpoena_appearance where subpoena_id in (select id FROM subpoena where recruit_id in ${endpointArguments});
DELETE FROM subpoena_send_info where subpoena_id in (select id FROM subpoena where recruit_id in ${endpointArguments});
DELETE FROM notification_item where restriction_document_item_id in (
select id from restriction_document_item where restriction_document_create_id in (
select id FROM restriction_document where subpoena_id in (select id FROM subpoena where recruit_id in ${endpointArguments})));
DELETE FROM notification_item where restriction_document_item_id in (
select id from restriction_document_item where restriction_document_cancel_id in (
select id FROM restriction_document where subpoena_id in (select id FROM subpoena where recruit_id in ${endpointArguments})));
DELETE FROM restriction_document_item_history WHERE recruit_id in ${endpointArguments};
DELETE FROM restriction_document_item where restriction_document_create_id in (
select id FROM restriction_document where subpoena_id in (select id FROM subpoena where recruit_id in ${endpointArguments}));
DELETE FROM restriction_document_item where restriction_document_cancel_id in (
select id FROM restriction_document where subpoena_id in (select id FROM subpoena where recruit_id in ${endpointArguments}));
DELETE FROM restriction_document where subpoena_id in (select id FROM subpoena where recruit_id in ${endpointArguments});
DELETE FROM subpoena where recruit_id in ${endpointArguments};
DELETE FROM recruits WHERE id in ${endpointArguments};
END
$$;
]]>
</RequestURL>
<SqlConnectionParams>
<JdbcHost>10.10.31.118</JdbcHost>
<JdbcPort>5432</JdbcPort>
<JdbcUsername>ervu</JdbcUsername>
<JdbcPassword>ervu</JdbcPassword>
<JdbcDriverClassName>org.postgresql.Driver</JdbcDriverClassName>
<JdbcXaDataSourceClassName>org.postgresql.xa.PGXADataSource</JdbcXaDataSourceClassName>
<JdbcXaDataSourcePoolSize>50</JdbcXaDataSourcePoolSize>
<JdbcDatabase>subpoena</JdbcDatabase>
</SqlConnectionParams>
</SqlRequest>
<!-- 005-ervu_decision_document-delete-recruit -->
<SqlRequest>
<RequestURL>
<![CDATA[
DO
$$
BEGIN
DELETE FROM decision where recruit_id in ${endpointArguments};
DELETE FROM foreign_decision where recruit_id in ${endpointArguments};
DELETE FROM infringement where recruit_id in ${endpointArguments};
DELETE FROM system_document where attachment_id in (SELECT id FROM attachment where recruit_id in ${endpointArguments}) ;
DELETE FROM attachment where recruit_id in ${endpointArguments};
DELETE FROM recruit where id in ${endpointArguments};
END
$$;
]]>
</RequestURL>
<SqlConnectionParams>
<JdbcHost>10.10.31.118</JdbcHost>
<JdbcPort>5432</JdbcPort>
<JdbcUsername>ervu</JdbcUsername>
<JdbcPassword>ervu</JdbcPassword>
<JdbcDriverClassName>org.postgresql.Driver</JdbcDriverClassName>
<JdbcXaDataSourceClassName>org.postgresql.xa.PGXADataSource</JdbcXaDataSourceClassName>
<JdbcXaDataSourcePoolSize>50</JdbcXaDataSourcePoolSize>
<JdbcDatabase>decision-document-service</JdbcDatabase>
</SqlConnectionParams>
</SqlRequest>
<!-- 004-ervu_journal-delete-recruit -->
<SqlRequest>
<RequestURL>
<![CDATA[
DO
$$
DECLARE
recruitIds uuid[] := ${endpointArguments};
importIds uuid[] := ARRAY(SELECT import_id
FROM import_results
WHERE recruit_id = ANY (recruitIds));
BEGIN
WITH ImportDeletes AS (DELETE FROM import_journal_start WHERE import_id = ANY (importIds) RETURNING journal_id)
DELETE
FROM journal
WHERE id IN (SELECT journal_id FROM ImportDeletes);
DELETE FROM import_journal_finish WHERE import_id = ANY (importIds);
DELETE FROM import_events_journal WHERE import_id = ANY (importIds);
DELETE FROM import_validation_errors WHERE import_id = ANY (importIds);
DELETE FROM import_results WHERE import_id = ANY (importIds);
DELETE FROM uploaded_files WHERE extra_info ->> 'importId' = ANY (importIds::text[]);
DELETE FROM object_history WHERE object_id = ANY (recruitIds);
DELETE FROM object_versions WHERE object_id = ANY (recruitIds);
END
$$;
]]>
</RequestURL>
<SqlConnectionParams>
<JdbcHost>10.10.31.118</JdbcHost>
<JdbcPort>5432</JdbcPort>
<JdbcUsername>ervu</JdbcUsername>
<JdbcPassword>ervu</JdbcPassword>
<JdbcDriverClassName>org.postgresql.Driver</JdbcDriverClassName>
<JdbcXaDataSourceClassName>org.postgresql.xa.PGXADataSource</JdbcXaDataSourceClassName>
<JdbcXaDataSourcePoolSize>50</JdbcXaDataSourcePoolSize>
<JdbcDatabase>journal_dev</JdbcDatabase>
</SqlConnectionParams>
</SqlRequest>
<!-- 008-ervu_geps_notifications-delete-recruit -->
<SqlRequest>
<RequestURL>
<![CDATA[
DO
$$
DECLARE
var1 uuid[] := '{
${endpointArguments}
}';
BEGIN
DELETE FROM notifications where recruit_id = any (var1);
END
$$;
]]>
</RequestURL>
<SqlConnectionParams>
<JdbcHost>10.10.31.118</JdbcHost>
<JdbcPort>5432</JdbcPort>
<JdbcUsername>ervu</JdbcUsername>
<JdbcPassword>ervu</JdbcPassword>
<JdbcDriverClassName>org.postgresql.Driver</JdbcDriverClassName>
<JdbcXaDataSourceClassName>org.postgresql.xa.PGXADataSource</JdbcXaDataSourceClassName>
<JdbcXaDataSourcePoolSize>50</JdbcXaDataSourcePoolSize>
<JdbcDatabase>geps_notifications</JdbcDatabase>
</SqlConnectionParams>
</SqlRequest>
<!-- 007_2-ervu_incidents-delete-all_of_recruit -->
<SqlRequest>
<RequestURL>
<![CDATA[
DO
$$
DECLARE
var1 uuid[] := '{
${endpointArguments}
}';
BEGIN
DELETE
FROM incident_history
where incident_id in (select id from incident where recruit_id = any (var1));
DELETE FROM incident where recruit_id = any (var1);
DELETE FROM recruits where id = any (var1);
END
$$;
]]>
</RequestURL>
<SqlConnectionParams>
<JdbcHost>10.10.31.118</JdbcHost>
<JdbcPort>5432</JdbcPort>
<JdbcUsername>ervu</JdbcUsername>
<JdbcPassword>ervu</JdbcPassword>
<JdbcDriverClassName>org.postgresql.Driver</JdbcDriverClassName>
<JdbcXaDataSourceClassName>org.postgresql.xa.PGXADataSource</JdbcXaDataSourceClassName>
<JdbcXaDataSourcePoolSize>50</JdbcXaDataSourcePoolSize>
<JdbcDatabase>incidents</JdbcDatabase>
</SqlConnectionParams>
</SqlRequest>
<!-- 001-ervu_person_registry-delete-recruit -->
<SqlRequest>
<RequestURL>
<![CDATA[
DO
$$
DECLARE
var1 uuid[] := '{
${endpointArguments}
}';
BEGIN
DELETE FROM recruits_info where recruit_id = any (var1);
-- DELETE FROM recruit_archive where recruit_id = any (var1);
DELETE FROM recruit_xml_data where recruit_id = any (var1);
DELETE FROM recruits_history where recruit_id = any (var1);
DELETE FROM application where recruit_id = any (var1);
DELETE FROM department_history where recruit_id = any (var1);
DELETE FROM documents where recruit_id = any (var1);
DELETE FROM decision where recruit_id = any (var1);
DELETE FROM personal_documents where recruit_id = any (var1);
DELETE FROM recruit_private_file where recruit_id = any (var1);
DELETE FROM system_documents where recruit_id = any (var1);
DELETE FROM system_document_dto where recruit_id = any (var1);
DELETE FROM subpoena_dto where recruit_id = any (var1);
DELETE FROM attachments where recruit_id = any (var1);
DELETE FROM summoned_list where recruit_id = any (var1);
DELETE FROM recruits where id = any (var1);
END
$$;
]]>
</RequestURL>
<SqlConnectionParams>
<JdbcHost>10.10.31.118</JdbcHost>
<JdbcPort>5432</JdbcPort>
<JdbcUsername>ervu</JdbcUsername>
<JdbcPassword>ervu</JdbcPassword>
<JdbcDriverClassName>org.postgresql.Driver</JdbcDriverClassName>
<JdbcXaDataSourceClassName>org.postgresql.xa.PGXADataSource</JdbcXaDataSourceClassName>
<JdbcXaDataSourcePoolSize>50</JdbcXaDataSourcePoolSize>
<JdbcDatabase>person_registry</JdbcDatabase>
</SqlConnectionParams>
</SqlRequest>
<!-- 009_1-ervu_appeal_document-delete-appeal-with-recruit -->
<SqlRequest>
<RequestURL>
<![CDATA[
DO
$$
DECLARE
var1 uuid[] := '{
${endpointArguments}
}';
BEGIN
DELETE FROM appeal_document where recruit_id = any (var1);
DELETE FROM recruit where id = any (var1);
END
$$;
]]>
</RequestURL>
<SqlConnectionParams>
<JdbcHost>10.10.31.118</JdbcHost>
<JdbcPort>5432</JdbcPort>
<JdbcUsername>ervu</JdbcUsername>
<JdbcPassword>ervu</JdbcPassword>
<JdbcDriverClassName>org.postgresql.Driver</JdbcDriverClassName>
<JdbcXaDataSourceClassName>org.postgresql.xa.PGXADataSource</JdbcXaDataSourceClassName>
<JdbcXaDataSourcePoolSize>50</JdbcXaDataSourcePoolSize>
<JdbcDatabase>appeal-document-service</JdbcDatabase>
</SqlConnectionParams>
</SqlRequest>
</Requests>

View file

@ -0,0 +1,28 @@
<Requests>
<S3Request>
<RequestArgument>
<RequestArgumentURL>
select path from paths;
</RequestArgumentURL>
<RequestArgumentConnectionParams>
<JdbcHost>localhost</JdbcHost>
<JdbcPort>5432</JdbcPort>
<JdbcUsername>postgres</JdbcUsername>
<JdbcPassword>password</JdbcPassword>
<JdbcDriverClassName>org.postgresql.Driver</JdbcDriverClassName>
<JdbcXaDataSourceClassName>org.postgresql.xa.PGXADataSource</JdbcXaDataSourceClassName>
<JdbcXaDataSourcePoolSize>50</JdbcXaDataSourcePoolSize>
<JdbcDatabase>micord_test</JdbcDatabase>
</RequestArgumentConnectionParams>
</RequestArgument>
<S3ConnectionParams>
<S3Key>minioadmin</S3Key>
<S3Secret>minioadmin</S3Secret>
<Host>127.0.0.1</Host>
<Port>9000</Port>
<ContentType>application/octet-stream</ContentType>
<Method>DELETE</Method>
<Body></Body>
</S3ConnectionParams>
</S3Request>
</Requests>

View file

@ -0,0 +1,32 @@
<Requests>
<SqlRequest>
<RequestURL>
<![CDATA[
DO
$$
begin
update recruits
set current_recruitment_id = jsonb_extract_path_text(extra_info,'blocked','cur')::uuid
,target_recruitment_id = jsonb_extract_path_text(extra_info,'blocked','trg')::uuid
,department_id_old = jsonb_extract_path_text(extra_info,'blocked','dio')::uuid
,system_pgs_status = '1.2'
,conscription = null
,extra_info = extra_info - ('blocked')
where id in ${endpointArguments};
end
$$;
]]>
</RequestURL>
<SqlConnectionParams>
<JdbcHost>10.10.31.118</JdbcHost>
<JdbcPort>5432</JdbcPort>
<JdbcUsername>ervu</JdbcUsername>
<JdbcPassword>ervu</JdbcPassword>
<JdbcDriverClassName>org.postgresql.Driver</JdbcDriverClassName>
<JdbcXaDataSourceClassName>org.postgresql.xa.PGXADataSource</JdbcXaDataSourceClassName>
<JdbcXaDataSourcePoolSize>50</JdbcXaDataSourcePoolSize>
<JdbcDatabase>person_registry</JdbcDatabase>
</SqlConnectionParams>
</SqlRequest>
</Requests>

20
config/db.env Normal file
View file

@ -0,0 +1,20 @@
# App datasource
DB_APP_NAME=ervu-eks
DB_APP_HOST=db
DB_APP_PORT=5432
DB_APP_USERNAME=ervu-eks
DB_APP_PASSWORD=ervu-eks
# Security datasource
DB_SEC_NAME=ervu-eks
DB_SEC_HOST=db
DB_SEC_PORT=5432
DB_SEC_USERNAME=ervu-eks-sec
DB_SEC_PASSWORD=ervu-eks-sec
# Person datasource
DB_PERSON_NAME=person_registry
DB_PERSON_HOST=10.10.31.118
DB_PERSON_PORT=5432
DB_PERSON_USERNAME=ervu
DB_PERSON_PASSWORD=ervu

View file

@ -0,0 +1,31 @@
version: "3"
services:
db:
image: postgres:15-bullseye
volumes:
- ./initdb.d:/docker-entrypoint-initdb.d
- db-data:/var/lib/postgresql/data
command:
- "--max_prepared_transactions=100"
ports:
- "127.0.0.1:5432:5432"
environment:
- POSTGRES_PASSWORD=supersecretpassword
- TZ="Europe/Moscow"
webbpm-app:
image: ervu-eks:latest
depends_on:
- db
ports:
- "127.0.0.1:9990:9990"
- "127.0.0.1:8080:8080"
volumes:
- ./cde-xml:/cde-xml
environment:
- TZ="Europe/Moscow"
env_file:
- db.env
volumes:
db-data:

View file

@ -0,0 +1,14 @@
version: "3"
services:
webbpm-app:
build:
context: .
dockerfile: Dockerfile
volumes:
- ./cde-xml:/cde-xml
ports:
- 8080
- 8787
- 9990
env_file:
- micord.env

48
config/entrypoint.sh Normal file
View 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

View file

@ -0,0 +1,23 @@
CREATE ROLE "ervu-eks" WITH
LOGIN
NOSUPERUSER
INHERIT
NOCREATEDB
NOCREATEROLE
NOREPLICATION
PASSWORD 'ervu-eks';
CREATE ROLE "ervu-eks-sec" WITH
LOGIN
NOSUPERUSER
INHERIT
NOCREATEDB
NOCREATEROLE
NOREPLICATION
PASSWORD 'ervu-eks-sec';
CREATE DATABASE "ervu-eks"
WITH
OWNER = "ervu-eks";
GRANT CREATE ON DATABASE "ervu-eks" TO "ervu-eks-sec";

22
config/micord.env Normal file
View file

@ -0,0 +1,22 @@
TZ=Europe/Moscow
# App datasource
DB_APP_USERNAME=ervu-eks
DB_APP_PASSWORD=ervu-eks
DB_APP_HOST=10.10.31.118
DB_APP_PORT=5432
DB_APP_NAME=ervu-eks
# Security datasource
DB_SEC_USERNAME=ervu-eks-sec
DB_SEC_PASSWORD=ervu-eks-sec
DB_SEC_HOST=10.10.31.118
DB_SEC_PORT=5432
DB_SEC_NAME=ervu-eks
# Person datasource
DB_PERSON_USERNAME=ervu
DB_PERSON_PASSWORD=ervu
DB_PERSON_HOST=10.10.31.118
DB_PERSON_PORT=5432
DB_PERSON_NAME=person_registry

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

View file

@ -0,0 +1,68 @@
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=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}
data-source add \
--name=PERSONDS \
--enabled=true \
--driver-name=postgresql \
--connection-url=jdbc:postgresql://${env.DB_PERSON_HOST:db}:${env.DB_PERSON_PORT:5432}/${env.DB_PERSON_NAME:person_registry} \
--jndi-name=java:/webbpm/personRegistryDS \
--user-name=${env.DB_PERSON_USERNAME:ervu} \
--password=${env.DB_PERSON_PASSWORD:ervu} \
--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 \
--max-pool-size=10
/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=security.password.regex:add(value="^((?=(.*\\d){1,})(?=.*[a-zа-яё])(?=.*[A-ZА-ЯЁ]).{8,})$")
/system-property=gar.enable:add(value=false)
/system-property=fias.enable:add(value=false)
/system-property=bpmn.enable:add(value=false)
/system-property=config.data.executor.url:add(value="http://localhost:8080/config-data-executor/api")
/system-property=config.data.executor.socket.timeout:add(value="10")
/system-property=config.data.executor.connection.timeout:add(value="10")
/system-property=configDirectory:add(value="/cde-xml")

View file

@ -0,0 +1 @@
$JBOSS_HOME/bin/add-user.sh demo@example.com demo

View 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" \
)

View 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

590
config/standalone.xml Normal file
View file

@ -0,0 +1,590 @@
<?xml version="1.0" ?>
<server xmlns="urn:jboss:domain:19.0">
<extensions>
<extension module="org.jboss.as.clustering.infinispan"/>
<extension module="org.jboss.as.connector"/>
<extension module="org.jboss.as.deployment-scanner"/>
<extension module="org.jboss.as.ee"/>
<extension module="org.jboss.as.ejb3"/>
<extension module="org.jboss.as.jaxrs"/>
<extension module="org.jboss.as.jdr"/>
<extension module="org.jboss.as.jmx"/>
<extension module="org.jboss.as.jpa"/>
<extension module="org.jboss.as.jsf"/>
<extension module="org.jboss.as.logging"/>
<extension module="org.jboss.as.mail"/>
<extension module="org.jboss.as.naming"/>
<extension module="org.jboss.as.pojo"/>
<extension module="org.jboss.as.remoting"/>
<extension module="org.jboss.as.sar"/>
<extension module="org.jboss.as.transactions"/>
<extension module="org.jboss.as.webservices"/>
<extension module="org.jboss.as.weld"/>
<extension module="org.wildfly.extension.batch.jberet"/>
<extension module="org.wildfly.extension.bean-validation"/>
<extension module="org.wildfly.extension.clustering.web"/>
<extension module="org.wildfly.extension.core-management"/>
<extension module="org.wildfly.extension.discovery"/>
<extension module="org.wildfly.extension.ee-security"/>
<extension module="org.wildfly.extension.elytron"/>
<extension module="org.wildfly.extension.elytron-oidc-client"/>
<extension module="org.wildfly.extension.health"/>
<extension module="org.wildfly.extension.io"/>
<extension module="org.wildfly.extension.metrics"/>
<extension module="org.wildfly.extension.microprofile.config-smallrye"/>
<extension module="org.wildfly.extension.microprofile.jwt-smallrye"/>
<extension module="org.wildfly.extension.microprofile.opentracing-smallrye"/>
<extension module="org.wildfly.extension.request-controller"/>
<extension module="org.wildfly.extension.security.manager"/>
<extension module="org.wildfly.extension.undertow"/>
</extensions>
<system-properties>
<property name="webbpm.mode" value="production"/>
<property name="authentication.method" value="form"/>
<property name="jboss.bind.address.management" value="0.0.0.0"/>
<property name="jboss.bind.address" value="0.0.0.0"/>
<property name="ldap.mapping.login.param" value="${env.WEBBPM_LDAP_LOGIN_ATTR:uid}"/>
<property name="ldap.mapping.org.code.param" value="${env.WEBBPM_LDAP_ORGANIZATION_ATTR:ou}"/>
<property name="jboss.as.management.blocking.timeout" value="900"/>
<property name="webbpm.cache.hazelcast.hosts" value="127.0.0.1"/>
<property name="webbpm.cache.hazelcast.outbound_port_definitions" value="5801-5820"/>
<property name="webbpm.security.session.active.count" value="20"/>
<property name="security.password.regex" value="^((?=(.*\d){1,})(?=.*[a-zа-яё])(?=.*[A-ZА-ЯЁ]).{8,})$"/>
<property name="gar.enable" value="false"/>
<property name="fias.enable" value="false"/>
<property name="bpmn.enable" value="false"/>
<property name="com.arjuna.ats.arjuna.allowMultipleLastResources" value="true"/>
<property name="config.data.executor.url" value="http://localhost:8080/config-data-executor/api"/>
<property name="config.data.executor.socket.timeout" value="10"/>
<property name="config.data.executor.connection.timeout" value="10"/>
<property name="configDirectory" value="C:\work\ervu-secret\config-data-executor\config-examples"/>
</system-properties>
<management>
<audit-log>
<formatters>
<json-formatter name="json-formatter"/>
</formatters>
<handlers>
<file-handler name="file" formatter="json-formatter" path="audit-log.log" relative-to="jboss.server.data.dir"/>
</handlers>
<logger log-boot="true" log-read-only="false" enabled="false">
<handlers>
<handler name="file"/>
</handlers>
</logger>
</audit-log>
<management-interfaces>
<http-interface http-authentication-factory="management-http-authentication">
<http-upgrade enabled="true" sasl-authentication-factory="management-sasl-authentication"/>
<socket-binding http="management-http"/>
</http-interface>
</management-interfaces>
<access-control provider="simple">
<role-mapping>
<role name="SuperUser">
<include>
<user name="$local"/>
</include>
</role>
</role-mapping>
</access-control>
</management>
<profile>
<subsystem xmlns="urn:jboss:domain:logging:8.0">
<console-handler name="CONSOLE">
<level name="INFO"/>
<formatter>
<named-formatter name="COLOR-PATTERN"/>
</formatter>
</console-handler>
<periodic-rotating-file-handler name="FILE" autoflush="true">
<formatter>
<named-formatter name="PATTERN"/>
</formatter>
<file relative-to="jboss.server.log.dir" path="server.log"/>
<suffix value=".yyyy-MM-dd"/>
<append value="true"/>
</periodic-rotating-file-handler>
<logger category="com.arjuna">
<level name="WARN"/>
</logger>
<logger category="io.jaegertracing.Configuration">
<level name="WARN"/>
</logger>
<logger category="org.jboss.as.config">
<level name="DEBUG"/>
</logger>
<logger category="sun.rmi">
<level name="WARN"/>
</logger>
<logger category="org.jooq.tools">
<level name="DEBUG"/>
<handlers>
<handler name="CONSOLE"/>
</handlers>
</logger>
<root-logger>
<level name="INFO"/>
<handlers>
<handler name="CONSOLE"/>
<handler name="FILE"/>
</handlers>
</root-logger>
<formatter name="PATTERN">
<pattern-formatter pattern="%d{yyyy-MM-dd HH:mm:ss,SSS} %-5p [%c] (%t) %s%e%n"/>
</formatter>
<formatter name="COLOR-PATTERN">
<pattern-formatter pattern="%K{level}%d{HH:mm:ss,SSS} %-5p [%c] (%t) %s%e%n"/>
</formatter>
</subsystem>
<subsystem xmlns="urn:jboss:domain:batch-jberet:3.0">
<default-job-repository name="in-memory"/>
<default-thread-pool name="batch"/>
<job-repository name="in-memory">
<in-memory/>
</job-repository>
<thread-pool name="batch">
<max-threads count="10"/>
<keepalive-time time="30" unit="seconds"/>
</thread-pool>
</subsystem>
<subsystem xmlns="urn:jboss:domain:bean-validation:1.0"/>
<subsystem xmlns="urn:jboss:domain:core-management:1.0"/>
<subsystem xmlns="urn:jboss:domain:datasources:7.0">
<datasources>
<xa-datasource jndi-name="java:/webbpm/AppDS" pool-name="AppDS" enabled="true" use-java-context="true">
<xa-datasource-property name="ServerName">10.10.31.118</xa-datasource-property>
<xa-datasource-property name="PortNumber">5432</xa-datasource-property>
<xa-datasource-property name="DatabaseName">ervu-eks</xa-datasource-property>
<driver>postgresql</driver>
<security>
<user-name>ervu-eks</user-name>
<password>ervu-eks</password>
</security>
<validation>
<valid-connection-checker class-name="org.jboss.jca.adapters.jdbc.extensions.postgres.PostgreSQLValidConnectionChecker" />
<validate-on-match>false</validate-on-match>
<background-validation>true</background-validation>
<background-validation-millis>5000</background-validation-millis>
<exception-sorter class-name="org.jboss.jca.adapters.jdbc.extensions.postgres.PostgreSQLExceptionSorter" />
</validation>
</xa-datasource>
<xa-datasource jndi-name="java:/webbpm/security-ds" pool-name="SECURITYDS" enabled="true" use-java-context="true">
<xa-datasource-property name="ServerName">10.10.31.118</xa-datasource-property>
<xa-datasource-property name="PortNumber">5432</xa-datasource-property>
<xa-datasource-property name="DatabaseName">ervu-eks</xa-datasource-property>
<driver>postgresql</driver>
<security>
<user-name>ervu-eks-sec</user-name>
<password>ervu-eks-sec</password>
</security>
<validation>
<valid-connection-checker class-name="org.jboss.jca.adapters.jdbc.extensions.postgres.PostgreSQLValidConnectionChecker" />
<validate-on-match>false</validate-on-match>
<background-validation>true</background-validation>
<background-validation-millis>5000</background-validation-millis>
<exception-sorter class-name="org.jboss.jca.adapters.jdbc.extensions.postgres.PostgreSQLExceptionSorter" />
</validation>
</xa-datasource>
<!-- -->
<datasource jndi-name="java:/webbpm/personRegistryDS" pool-name="personRegistryDS" enabled="true" use-java-context="true">
<connection-url>jdbc:postgresql://10.10.31.118:5432/person_registry</connection-url>
<driver>postgresql</driver>
<security>
<user-name>ervu</user-name>
<password>ervu</password>
</security>
<validation>
<valid-connection-checker class-name="org.jboss.jca.adapters.jdbc.extensions.postgres.PostgreSQLValidConnectionChecker"/>
<validate-on-match>false</validate-on-match>
<background-validation>true</background-validation>
<background-validation-millis>5000</background-validation-millis>
<exception-sorter class-name="org.jboss.jca.adapters.jdbc.extensions.postgres.PostgreSQLExceptionSorter"/>
</validation>
<timeout>
<query-timeout>300</query-timeout>
</timeout>
</datasource>
<drivers>
<driver name="h2" module="com.h2database.h2">
<xa-datasource-class>org.h2.jdbcx.JdbcDataSource</xa-datasource-class>
</driver>
<driver name="postgresql" module="org.postgresql">
<xa-datasource-class>org.postgresql.xa.PGXADataSource</xa-datasource-class>
</driver>
</drivers>
</datasources>
</subsystem>
<subsystem xmlns="urn:jboss:domain:deployment-scanner:2.0">
<deployment-scanner path="deployments" relative-to="jboss.server.base.dir" scan-interval="5000" runtime-failure-causes-rollback="${jboss.deployment.scanner.rollback.on.failure:false}"/>
</subsystem>
<subsystem xmlns="urn:jboss:domain:discovery:1.0"/>
<subsystem xmlns="urn:jboss:domain:distributable-web:2.0" default-session-management="default" default-single-sign-on-management="default">
<infinispan-session-management name="default" cache-container="web" granularity="SESSION">
<local-affinity/>
</infinispan-session-management>
<infinispan-single-sign-on-management name="default" cache-container="web" cache="sso"/>
<local-routing/>
</subsystem>
<subsystem xmlns="urn:jboss:domain:ee:6.0">
<spec-descriptor-property-replacement>false</spec-descriptor-property-replacement>
<concurrent>
<context-services>
<context-service name="default" jndi-name="java:jboss/ee/concurrency/context/default" use-transaction-setup-provider="true"/>
</context-services>
<managed-thread-factories>
<managed-thread-factory name="default" jndi-name="java:jboss/ee/concurrency/factory/default" context-service="default"/>
</managed-thread-factories>
<managed-executor-services>
<managed-executor-service name="default" jndi-name="java:jboss/ee/concurrency/executor/default" context-service="default" hung-task-termination-period="0" hung-task-threshold="60000" keepalive-time="5000"/>
</managed-executor-services>
<managed-scheduled-executor-services>
<managed-scheduled-executor-service name="default" jndi-name="java:jboss/ee/concurrency/scheduler/default" context-service="default" hung-task-termination-period="0" hung-task-threshold="60000" keepalive-time="3000"/>
</managed-scheduled-executor-services>
</concurrent>
</subsystem>
<subsystem xmlns="urn:jboss:domain:ee-security:1.0"/>
<subsystem xmlns="urn:jboss:domain:ejb3:9.0">
<session-bean>
<stateless>
<bean-instance-pool-ref pool-name="slsb-strict-max-pool"/>
</stateless>
<stateful default-access-timeout="5000" cache-ref="simple" passivation-disabled-cache-ref="simple"/>
<singleton default-access-timeout="5000"/>
</session-bean>
<pools>
<bean-instance-pools>
<strict-max-pool name="mdb-strict-max-pool" derive-size="from-cpu-count" instance-acquisition-timeout="5" instance-acquisition-timeout-unit="MINUTES"/>
<strict-max-pool name="slsb-strict-max-pool" derive-size="from-worker-pools" instance-acquisition-timeout="5" instance-acquisition-timeout-unit="MINUTES"/>
</bean-instance-pools>
</pools>
<caches>
<cache name="simple"/>
<cache name="distributable" passivation-store-ref="infinispan" aliases="passivating clustered"/>
</caches>
<passivation-stores>
<passivation-store name="infinispan" cache-container="ejb" max-size="10000"/>
</passivation-stores>
<async thread-pool-name="default"/>
<timer-service thread-pool-name="default" default-data-store="default-file-store">
<data-stores>
<file-data-store name="default-file-store" path="timer-service-data" relative-to="jboss.server.data.dir"/>
</data-stores>
</timer-service>
<remote cluster="ejb" connectors="http-remoting-connector" thread-pool-name="default">
<channel-creation-options>
<option name="MAX_OUTBOUND_MESSAGES" value="1234" type="remoting"/>
</channel-creation-options>
</remote>
<thread-pools>
<thread-pool name="default">
<max-threads count="10"/>
<keepalive-time time="60" unit="seconds"/>
</thread-pool>
</thread-pools>
<default-security-domain value="other"/>
<application-security-domains>
<application-security-domain name="other" security-domain="ApplicationDomain"/>
</application-security-domains>
<default-missing-method-permissions-deny-access value="true"/>
<statistics enabled="${wildfly.ejb3.statistics-enabled:${wildfly.statistics-enabled:false}}"/>
<log-system-exceptions value="true"/>
</subsystem>
<subsystem xmlns="urn:wildfly:elytron:15.1" final-providers="combined-providers" disallowed-providers="OracleUcrypto">
<providers>
<aggregate-providers name="combined-providers">
<providers name="elytron"/>
<providers name="openssl"/>
</aggregate-providers>
<provider-loader name="elytron" module="org.wildfly.security.elytron"/>
<provider-loader name="openssl" module="org.wildfly.openssl"/>
</providers>
<audit-logging>
<file-audit-log name="local-audit" path="audit.log" relative-to="jboss.server.log.dir" format="JSON"/>
</audit-logging>
<security-domains>
<security-domain name="ManagementDomain" default-realm="ManagementRealm" permission-mapper="default-permission-mapper">
<realm name="ManagementRealm" role-decoder="groups-to-roles"/>
<realm name="local" role-mapper="super-user-mapper"/>
</security-domain>
<security-domain name="ApplicationDomain" default-realm="ApplicationRealm" permission-mapper="default-permission-mapper">
<realm name="ApplicationRealm" role-decoder="groups-to-roles"/>
<realm name="local"/>
</security-domain>
</security-domains>
<security-realms>
<identity-realm name="local" identity="$local"/>
<properties-realm name="ApplicationRealm">
<users-properties path="application-users.properties" relative-to="jboss.server.config.dir" digest-realm-name="ApplicationRealm"/>
<groups-properties path="application-roles.properties" relative-to="jboss.server.config.dir"/>
</properties-realm>
<properties-realm name="ManagementRealm">
<users-properties path="mgmt-users.properties" relative-to="jboss.server.config.dir" digest-realm-name="ManagementRealm"/>
<groups-properties path="mgmt-groups.properties" relative-to="jboss.server.config.dir"/>
</properties-realm>
</security-realms>
<mappers>
<simple-permission-mapper name="default-permission-mapper" mapping-mode="first">
<permission-mapping>
<principal name="anonymous"/>
<permission-set name="default-permissions"/>
</permission-mapping>
<permission-mapping match-all="true">
<permission-set name="login-permission"/>
<permission-set name="default-permissions"/>
</permission-mapping>
</simple-permission-mapper>
<constant-realm-mapper name="local" realm-name="local"/>
<simple-role-decoder name="groups-to-roles" attribute="groups"/>
<constant-role-mapper name="super-user-mapper">
<role name="SuperUser"/>
</constant-role-mapper>
</mappers>
<permission-sets>
<permission-set name="login-permission">
<permission class-name="org.wildfly.security.auth.permission.LoginPermission"/>
</permission-set>
<permission-set name="default-permissions">
<permission class-name="org.wildfly.extension.batch.jberet.deployment.BatchPermission" module="org.wildfly.extension.batch.jberet" target-name="*"/>
<permission class-name="org.wildfly.transaction.client.RemoteTransactionPermission" module="org.wildfly.transaction.client"/>
<permission class-name="org.jboss.ejb.client.RemoteEJBPermission" module="org.jboss.ejb-client"/>
</permission-set>
</permission-sets>
<http>
<http-authentication-factory name="management-http-authentication" security-domain="ManagementDomain" http-server-mechanism-factory="global">
<mechanism-configuration>
<mechanism mechanism-name="DIGEST">
<mechanism-realm realm-name="ManagementRealm"/>
</mechanism>
</mechanism-configuration>
</http-authentication-factory>
<http-authentication-factory name="application-http-authentication" security-domain="ApplicationDomain" http-server-mechanism-factory="global">
<mechanism-configuration>
<mechanism mechanism-name="BASIC">
<mechanism-realm realm-name="ApplicationRealm"/>
</mechanism>
</mechanism-configuration>
</http-authentication-factory>
<provider-http-server-mechanism-factory name="global"/>
</http>
<sasl>
<sasl-authentication-factory name="management-sasl-authentication" sasl-server-factory="configured" security-domain="ManagementDomain">
<mechanism-configuration>
<mechanism mechanism-name="JBOSS-LOCAL-USER" realm-mapper="local"/>
<mechanism mechanism-name="DIGEST-MD5">
<mechanism-realm realm-name="ManagementRealm"/>
</mechanism>
</mechanism-configuration>
</sasl-authentication-factory>
<sasl-authentication-factory name="application-sasl-authentication" sasl-server-factory="configured" security-domain="ApplicationDomain">
<mechanism-configuration>
<mechanism mechanism-name="JBOSS-LOCAL-USER" realm-mapper="local"/>
<mechanism mechanism-name="DIGEST-MD5">
<mechanism-realm realm-name="ApplicationRealm"/>
</mechanism>
</mechanism-configuration>
</sasl-authentication-factory>
<configurable-sasl-server-factory name="configured" sasl-server-factory="elytron">
<properties>
<property name="wildfly.sasl.local-user.default-user" value="$local"/>
<property name="wildfly.sasl.local-user.challenge-path" value="${jboss.server.temp.dir}/auth"/>
</properties>
</configurable-sasl-server-factory>
<mechanism-provider-filtering-sasl-server-factory name="elytron" sasl-server-factory="global">
<filters>
<filter provider-name="WildFlyElytron"/>
</filters>
</mechanism-provider-filtering-sasl-server-factory>
<provider-sasl-server-factory name="global"/>
</sasl>
<tls>
<key-stores>
<key-store name="applicationKS">
<credential-reference clear-text="password"/>
<implementation type="JKS"/>
<file path="application.keystore" relative-to="jboss.server.config.dir"/>
</key-store>
</key-stores>
<key-managers>
<key-manager name="applicationKM" key-store="applicationKS" generate-self-signed-certificate-host="localhost">
<credential-reference clear-text="password"/>
</key-manager>
</key-managers>
<server-ssl-contexts>
<server-ssl-context name="applicationSSC" key-manager="applicationKM"/>
</server-ssl-contexts>
</tls>
</subsystem>
<subsystem xmlns="urn:wildfly:elytron-oidc-client:1.0"/>
<subsystem xmlns="urn:wildfly:health:1.0" security-enabled="false"/>
<subsystem xmlns="urn:jboss:domain:infinispan:13.0">
<cache-container name="ejb" default-cache="passivation" marshaller="PROTOSTREAM" aliases="sfsb" modules="org.wildfly.clustering.ejb.infinispan">
<local-cache name="passivation">
<expiration interval="0"/>
<file-store passivation="true" purge="false"/>
</local-cache>
</cache-container>
<cache-container name="web" default-cache="passivation" marshaller="PROTOSTREAM" modules="org.wildfly.clustering.web.infinispan">
<local-cache name="passivation">
<expiration interval="0"/>
<file-store passivation="true" purge="false"/>
</local-cache>
<local-cache name="sso">
<expiration interval="0"/>
</local-cache>
</cache-container>
<cache-container name="server" default-cache="default" marshaller="PROTOSTREAM" modules="org.wildfly.clustering.server">
<local-cache name="default">
<expiration interval="0"/>
</local-cache>
</cache-container>
<cache-container name="hibernate" marshaller="JBOSS" modules="org.infinispan.hibernate-cache">
<local-cache name="entity">
<heap-memory size="10000"/>
<expiration max-idle="100000"/>
</local-cache>
<local-cache name="local-query">
<heap-memory size="10000"/>
<expiration max-idle="100000"/>
</local-cache>
<local-cache name="timestamps">
<expiration interval="0"/>
</local-cache>
<local-cache name="pending-puts">
<expiration max-idle="60000"/>
</local-cache>
</cache-container>
</subsystem>
<subsystem xmlns="urn:jboss:domain:io:3.0">
<worker name="default"/>
<buffer-pool name="default"/>
</subsystem>
<subsystem xmlns="urn:jboss:domain:jaxrs:2.0"/>
<subsystem xmlns="urn:jboss:domain:jca:5.0">
<archive-validation enabled="true" fail-on-error="true" fail-on-warn="false"/>
<bean-validation enabled="true"/>
<default-workmanager>
<short-running-threads>
<core-threads count="50"/>
<queue-length count="50"/>
<max-threads count="50"/>
<keepalive-time time="10" unit="seconds"/>
</short-running-threads>
<long-running-threads>
<core-threads count="50"/>
<queue-length count="50"/>
<max-threads count="50"/>
<keepalive-time time="10" unit="seconds"/>
</long-running-threads>
</default-workmanager>
<cached-connection-manager/>
</subsystem>
<subsystem xmlns="urn:jboss:domain:jdr:1.0"/>
<subsystem xmlns="urn:jboss:domain:jmx:1.3">
<expose-resolved-model/>
<expose-expression-model/>
<remoting-connector/>
</subsystem>
<subsystem xmlns="urn:jboss:domain:jpa:1.1">
<jpa default-extended-persistence-inheritance="DEEP"/>
</subsystem>
<subsystem xmlns="urn:jboss:domain:jsf:1.1"/>
<subsystem xmlns="urn:jboss:domain:mail:4.0">
<mail-session name="default" jndi-name="java:jboss/mail/Default">
<smtp-server outbound-socket-binding-ref="mail-smtp"/>
</mail-session>
</subsystem>
<subsystem xmlns="urn:wildfly:metrics:1.0" security-enabled="false" exposed-subsystems="*" prefix="${wildfly.metrics.prefix:wildfly}"/>
<subsystem xmlns="urn:wildfly:microprofile-config-smallrye:2.0"/>
<subsystem xmlns="urn:wildfly:microprofile-jwt-smallrye:1.0"/>
<subsystem xmlns="urn:wildfly:microprofile-opentracing-smallrye:3.0" default-tracer="jaeger">
<jaeger-tracer name="jaeger">
<sampler-configuration sampler-type="const" sampler-param="1.0"/>
</jaeger-tracer>
</subsystem>
<subsystem xmlns="urn:jboss:domain:naming:2.0">
<remote-naming/>
</subsystem>
<subsystem xmlns="urn:jboss:domain:pojo:1.0"/>
<subsystem xmlns="urn:jboss:domain:remoting:4.0">
<http-connector name="http-remoting-connector" connector-ref="default" sasl-authentication-factory="application-sasl-authentication"/>
</subsystem>
<subsystem xmlns="urn:jboss:domain:request-controller:1.0"/>
<subsystem xmlns="urn:jboss:domain:resource-adapters:6.1"/>
<subsystem xmlns="urn:jboss:domain:sar:1.0"/>
<subsystem xmlns="urn:jboss:domain:security-manager:1.0">
<deployment-permissions>
<maximum-set>
<permission class="java.security.AllPermission"/>
</maximum-set>
</deployment-permissions>
</subsystem>
<subsystem xmlns="urn:jboss:domain:transactions:6.0">
<core-environment node-identifier="${jboss.tx.node.id:1}">
<process-id>
<uuid/>
</process-id>
</core-environment>
<recovery-environment socket-binding="txn-recovery-environment" status-socket-binding="txn-status-manager"/>
<coordinator-environment statistics-enabled="${wildfly.transactions.statistics-enabled:${wildfly.statistics-enabled:false}}"/>
<object-store path="tx-object-store" relative-to="jboss.server.data.dir"/>
</subsystem>
<subsystem xmlns="urn:jboss:domain:undertow:12.0" default-server="default-server" default-virtual-host="default-host" default-servlet-container="default" default-security-domain="other" statistics-enabled="${wildfly.undertow.statistics-enabled:${wildfly.statistics-enabled:false}}">
<buffer-cache name="default"/>
<server name="default-server">
<http-listener name="default" socket-binding="http" max-post-size="${env.MAX_POST_SIZE:104857600}" record-request-start-time="true" redirect-socket="https" enable-http2="true"/>
<https-listener name="https" socket-binding="https" ssl-context="applicationSSC" enable-http2="true"/>
<host name="default-host" alias="localhost">
<location name="/" handler="welcome-content"/>
<filter-ref name="cache-control" predicate="path-suffix['.bpmn'] or path-suffix['.bpmn2']"/>
<http-invoker http-authentication-factory="application-http-authentication"/>
</host>
</server>
<servlet-container name="default">
<jsp-config/>
<websockets/>
</servlet-container>
<handlers>
<file name="welcome-content" path="${jboss.home.dir}/welcome-content"/>
</handlers>
<filters>
<response-header name="cache-control" header-name="Cache-Control" header-value="no-store"/>
</filters>
<application-security-domains>
<application-security-domain name="other" security-domain="ApplicationDomain"/>
</application-security-domains>
</subsystem>
<subsystem xmlns="urn:jboss:domain:webservices:2.0" statistics-enabled="${wildfly.webservices.statistics-enabled:${wildfly.statistics-enabled:false}}">
<wsdl-host>${jboss.bind.address:127.0.0.1}</wsdl-host>
<endpoint-config name="Standard-Endpoint-Config"/>
<endpoint-config name="Recording-Endpoint-Config">
<pre-handler-chain name="recording-handlers" protocol-bindings="##SOAP11_HTTP ##SOAP11_HTTP_MTOM ##SOAP12_HTTP ##SOAP12_HTTP_MTOM">
<handler name="RecordingHandler" class="org.jboss.ws.common.invocation.RecordingServerHandler"/>
</pre-handler-chain>
</endpoint-config>
<client-config name="Standard-Client-Config"/>
</subsystem>
<subsystem xmlns="urn:jboss:domain:weld:4.0"/>
</profile>
<interfaces>
<interface name="management">
<inet-address value="${jboss.bind.address.management:0.0.0.0}"/>
</interface>
<interface name="public">
<inet-address value="${jboss.bind.address:0.0.0.0}"/>
</interface>
</interfaces>
<socket-binding-group name="standard-sockets" default-interface="public" port-offset="${jboss.socket.binding.port-offset:0}">
<socket-binding name="ajp" port="${jboss.ajp.port:8009}"/>
<socket-binding name="http" port="${jboss.http.port:8080}"/>
<socket-binding name="https" port="${jboss.https.port:8443}"/>
<socket-binding name="management-http" interface="management" port="${jboss.management.http.port:9990}"/>
<socket-binding name="management-https" interface="management" port="${jboss.management.https.port:9993}"/>
<socket-binding name="txn-recovery-environment" port="4712"/>
<socket-binding name="txn-status-manager" port="4713"/>
<outbound-socket-binding name="mail-smtp">
<remote-destination host="${jboss.mail.server.host:localhost}" port="${jboss.mail.server.port:25}"/>
</outbound-socket-binding>
</socket-binding-group>
</server>

66
distribution/pom.xml Normal file
View 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>ru.micord.ervu</groupId>
<artifactId>eks</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<groupId>ru.micord.ervu.eks</groupId>
<artifactId>distribution</artifactId>
<packaging>ear</packaging>
<properties>
<backendContext>/backend</backendContext>
</properties>
<dependencies>
<dependency>
<groupId>ru.micord.ervu.eks</groupId>
<artifactId>backend</artifactId>
<type>war</type>
</dependency>
<dependency>
<groupId>ru.micord.ervu.eks</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>ru.micord.ervu.eks</groupId>
<artifactId>frontend</artifactId>
<contextRoot>/</contextRoot>
<bundleFileName>frontend.war</bundleFileName>
</webModule>
<webModule>
<groupId>ru.micord.ervu.eks</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
View file

@ -0,0 +1 @@
registry=https://repo.micord.ru/repository/npm-all/

71
frontend/angular.json Normal file
View 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"
}

23
frontend/index.html Normal file
View file

@ -0,0 +1,23 @@
<!DOCTYPE html>
<html>
<head>
<title>ervu-eks</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-eks">
<div class="progress"></div>
</body>
</html>

View file

@ -0,0 +1,11 @@
<!DOCTYPE html>
<html>
<head>
<title>ervu-eks</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-eks">
<div class="progress"></div>
</body>
</html>

100
frontend/package.json Normal file
View file

@ -0,0 +1,100 @@
{
"name": "ervu-eks",
"version": "1.0.0",
"scripts": {
"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.2.2-micord.1",
"@webbpm/base-package": "3.185.0",
"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.5",
"esmarttokenjs": "2.2.1-cg",
"font-awesome": "4.7.0",
"google-libphonenumber": "3.0.9",
"inputmask": "5.0.5-cg.2",
"jquery": "3.7.1",
"js-year-calendar": "1.0.0-cg.2",
"jsgantt-improved": "2.0.10-cg",
"moment": "2.30.1",
"moment-timezone": "0.5.46",
"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.11",
"systemjs": "0.21.4",
"systemjs-plugin-babel": "0.0.25",
"tslib": "1.9.3",
"zone.js": "0.11.8"
},
"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.18.10",
"@babel/preset-env": "7.18.10",
"@types/bootstrap": "3.3.39",
"@types/eslint": "7.2.5",
"@types/jquery": "3.5.5",
"@types/node": "7.0.5",
"@types/selectize": "0.12.33",
"ajv": "8.8.2",
"angular-router-loader": "0.8.5",
"angular2-template-loader": "0.6.2",
"babel-loader": "9.1.2",
"codelyzer": "5.2.1",
"copy-webpack-plugin": "5.0.3",
"cross-env": "5.2.1",
"css-loader": "6.11.0",
"del": "2.2.2",
"file-loader": "6.2.0",
"html-webpack-plugin": "5.6.0",
"mini-css-extract-plugin": "2.9.1",
"mkdirp": "3.0.1",
"raw-loader": "4.0.2",
"style-loader": "3.3.4",
"terser-webpack-plugin": "5.3.10",
"tslint": "5.13.1",
"typescript": "3.2.4",
"typescript-parser": "2.6.1-cg.2",
"webpack": "5.90.1",
"webpack-cli": "5.0.2"
}
}

113
frontend/pom.xml Normal file
View 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>ru.micord.ervu</groupId>
<artifactId>eks</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<groupId>ru.micord.ervu.eks</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
View 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-eks" class="webbpm">
<div class="progress"></div>
</body>
</html>

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

View 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": "false",
"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
}

View file

@ -0,0 +1 @@
1.0.0-SNAPSHOT

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,342 @@
@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: var(--color-link);
&:hover,
&:focus,
&:active {
color: var(--color-link-hover);
text-decoration: none;
}
}
body.webbpm {
display: flex;
flex-direction: column;
color: var(--color-text-primary);
font-family: 'Segoe';
background-color: var(--white);
}
.webbpm .container {
padding: 70px 0 0;
}
body.webbpm [id="page"],
.webbpm .container .container-inside {
font-family: 'Segoe';
font-size: var(--size-text-primary);
}
.webbpm .logo {
height: auto;
width: auto;
}
.webbpm .header-logo {
display: flex;
flex-direction: row;
align-items: center;
margin-left: 40px;
.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;
& > * {
margin-right: 20px;
&:last-child {
margin-right: 0;
}
}
.nav-link {
display: flex;
align-items: center;
justify-content: center;
color: var(--white);
font-size: var(--size-text-primary);
width: 30px;
height: 30px;
padding: 0;
border: 0;
border-radius: 15px;
background-color: var(--color-text-primary);
outline: transparent;
&::after {
display: none;
}
&:hover,
&:focus,
&:active {
background-color: var(--color-link);
}
}
.logout .user-info {
display: flex;
flex-direction: column;
color: var(--black);
padding: 4px 20px;
background: transparent;
cursor: default;
& > * {
display: flex;
padding-bottom: 10px;
margin: 0 0 10px 0;
border-bottom: 1px solid #f1f5f9;
&:last-child {
margin-bottom: 0;
}
}
.user-fio {
padding-bottom: 0;
margin-bottom: 0;
border-bottom: 0;
}
.user-department {
color: #a0b1bc;
line-height: 1.2;
}
}
}
.webbpm .header {
display: flex;
font-family: 'Segoe';
width: 100%;
height: auto;
min-height: 70px;
border-bottom: 1px solid var(--bg-light);
background: var(--white);
box-shadow: 0px 15px 20px 0px rgb(0 0 0 / 4%);
& > div > * {
position: relative;
display: flex;
align-items: center;
}
.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: var(--white);
box-shadow: 0 8px 12px rgb(77 72 91 / 5%), 0 6px 10px rgb(77 72 91 / 0%);
.dropdown-menu-inner {
max-height: calc(100vh - 140px);
overflow-y: auto;
}
}
:is(process, admin-menu) .dropdown-menu.show {
top: 49px !important;
}
.logout .dropdown-menu.show {
width: 300px;
}
}
.webbpm .dropdown-menu-inner:hover {
background-color: transparent;
}
.webbpm .dropdown-item {
padding: 4px 20px;
&:hover,
&:focus,
&:active {
color: var(--color-link);
background-color: transparent;
outline: transparent;
}
}
.webbpm footer {
color: var(--color-text-primary);
font-size: var(--size-text-secondary);
left: 0;
right: 0;
padding: 15px 40px;
border-top: 1px solid var(--border-light);
a {
color: var(--color-text-primary);
&:hover {
color: var(--color-link);
}
}
}
/*-------------- Menu tasks -------------- */
.webbpm .task-list-tree-panel {
padding: 0 40px;
.task-list-filter {
font-family: 'Segoe';
box-shadow: none;
li:first-of-type {
font-family: 'SegoeSB';
font-weight: normal;
padding-left: 0;
}
li.ontime label div {
background-color: #31c980;
}
li.overdue label div {
background-color: var(--color-link);
}
}
}
.webbpm .task-list-workplace {
padding: 20px 40px 0 40px;
}
.webbpm .task-tbl :is(.tr.task-ontime, .tr.task-overdue) > .td.task::before {
top: 24px;
background-color: #31c980;
}
.webbpm .task-tbl .tr.task-overdue > .td.task::before {
background-color: var(--color-link);
}
.webbpm .task-tbl .thead {
display: table-header-group;
border: 0;
background: transparent;
}
.webbpm .task-tbl .th {
color: var(--color-text-primary);
font-family: 'SegoeSB';
font-size: var(--size-text-secondary);
font-weight: normal;
padding: 9px 12px;
border: 0;
background: var(--bg-light);
}
.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: var(--color-text-primary);
font-size: var(--size-text-primary);
padding: 16px 12px;
border: 0;
}
.webbpm .task-tbl .thead ~ .tr {
border-color: var(--border-light);
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 :is(.form-signin, .form-signup, .confirm) {
color: var(--color-text-primary);
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: var(--white);
}
.webbpm :is(.form-signin, .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 :is(.form-signin, .form-signup, .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 -------------- */

View file

@ -0,0 +1,846 @@
:root {
--white: #ffffff;
--black: #000000;
--color-text-primary: #404954;
--color-link: #1c92ea;
--color-link-hover: #1b84d2;
--bg-light: #f5f7fa;
--bg-secondary: #4c5969;
--border-light: #e3e6ed;
--size-text-primary: 16px;
--size-text-secondary: 14px;
}
* {
margin: 0;
padding: 0;
}
*, *:before, *:after {
-webkit-box-sizing: border-box;
-moz-box-sizing: border-box;
box-sizing: border-box;
}
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 .fl-left {
float: left;
}
.webbpm .fl-right {
float: right;
}
.webbpm .anchor {
float: none;
clear: both;
}
.webbpm :is(ul, ol) li {
list-style: none;
}
.webbpm :is(h1, h2, h3) {
margin: 0;
font-weight: normal;
}
.webbpm h1 {
font-size: 2.33em;
}
.webbpm h2 {
font-size: 2em;
}
.webbpm h3 {
font-size: 1.5em;
}
.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: var(--size-text-secondary);
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;
[ng-include="taskPageFile"] {
position: relative;
min-height: 100%;
height: 100%;
overflow: auto;
}
.container-inside {
font-family: Arial;
font-size: var(--size-text-secondary);
position: relative;
height: 100%;
overflow: auto;
}
}
.webbpm footer {
position: absolute;
color: var(--black);
font-size: 12px;
bottom: 0;
left: 15px;
right: 15px;
height: 50px;
padding: 15px 0;
border-top: 1px solid #c1c1c1;
background: transparent;
span + span {
padding-left: 20px;
}
}
/*--------- TOP MENU ----------*/
.webbpm .logo {
display: inline-block;
margin: 0;
float: left;
a {
width: 200px;
height: 67px;
position: absolute;
background: url("../img/logo.png") no-repeat 0 0;
}
}
.webbpm .header {
position: absolute;
color: var(--white);
font-family: Corbel;
font-size: var(--size-text-secondary);
top: 0;
left: 0;
right: 0;
height: 67px;
min-height: 67px;
line-height: normal;
border: 0;
padding: 0;
background: #b9c0ca;
z-index: 997;
.nav .nav-link {
color: var(--white);
float: none;
display: block;
line-height: 60px;
padding: 0 15px 0 60px;
text-shadow: none;
cursor: pointer;
}
.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: var(--size-text-secondary);
}
.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: var(--white);
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: var(--white);
}
.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: var(--size-text-secondary);
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: var(--white);
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: var(--black);
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: var(--white);
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: var(--white);
border-right: 0;
}
.webbpm .task-list > .task-list-tree-panel,
.webbpm .task-list > .task-list-workplace {
font-size: var(--size-text-secondary);
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);
ul {
margin: 0;
}
li {
display: inline-block;
padding: 12px 0px;
margin: 0;
&:first-of-type {
font-size: 1.4em;
font-weight: bold;
width: 197px;
padding: 0 15px;
}
&::before {
display: none;
}
label {
font-size: 1.3em;
font-weight: normal;
}
label div {
display: inline-block;
width: 10px;
height: 10px;
border-radius: 10px;
margin-right: 8px;
}
&.ontime label div {
background-color: #2da6a1;
}
&.overdue label div {
background-color: #9c5d7a;
}
label input[type="radio"] {
float: none;
display: none;
}
label span {
float: none;
color: var(--black);
font-weight: normal;
text-align: left;
min-width: auto;
padding: 5px 15px;
border-radius: 0;
background-color: transparent;
}
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 :is(.form-signin, .form-signup, .confirm) {
color: #333;
width: 580px;
padding: 80px 100px;
margin: 20px auto;
border: 1px solid var(--bg-light);
background: var(--white);
}
.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 :is(h1, h2) {
margin-left: 0;
}
.webbpm .form-signin label,
.webbpm .form-signup label {
margin-bottom: 0;
}
.webbpm .form-signin input {
width: 240px;
font-size: var(--size-text-secondary);
display: inline-block;
}
.webbpm .form-signin.esia input {
width: 160px;
margin-top: 40px;
}
.webbpm :is(.form-signin, .form-signup) .row {
display: flex;
margin: 0 0 20px;
}
.webbpm .registration-link,
.webbpm .login-link {
margin-right: 20px;
font-size: var(--size-text-secondary);
}
.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: var(--size-text-secondary);
}
.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 Поле телефона ------------ */

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

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

After

Width:  |  Height:  |  Size: 855 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 811 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 673 B

View file

@ -0,0 +1,10 @@
<svg width="16" height="16" viewBox="0 0 16 16" fill="none" xmlns="http://www.w3.org/2000/svg">
<g clip-path="url(#clip0_340_5833)">
<path fill-rule="evenodd" clip-rule="evenodd" d="M6.5 3.75C6.5 4.16421 6.16421 4.5 5.75 4.5C5.33579 4.5 5 4.16421 5 3.75C5 3.33579 5.33579 3 5.75 3C6.16421 3 6.5 3.33579 6.5 3.75ZM11 3.75C11 4.16421 10.6642 4.5 10.25 4.5C9.83579 4.5 9.5 4.16421 9.5 3.75C9.5 3.33579 9.83579 3 10.25 3C10.6642 3 11 3.33579 11 3.75ZM5.75 8.75C6.16421 8.75 6.5 8.41421 6.5 8C6.5 7.58579 6.16421 7.25 5.75 7.25C5.33579 7.25 5 7.58579 5 8C5 8.41421 5.33579 8.75 5.75 8.75ZM11 8C11 8.41421 10.6642 8.75 10.25 8.75C9.83579 8.75 9.5 8.41421 9.5 8C9.5 7.58579 9.83579 7.25 10.25 7.25C10.6642 7.25 11 7.58579 11 8ZM5.75 13C6.16421 13 6.5 12.6642 6.5 12.25C6.5 11.8358 6.16421 11.5 5.75 11.5C5.33579 11.5 5 11.8358 5 12.25C5 12.6642 5.33579 13 5.75 13ZM11 12.25C11 12.6642 10.6642 13 10.25 13C9.83579 13 9.5 12.6642 9.5 12.25C9.5 11.8358 9.83579 11.5 10.25 11.5C10.6642 11.5 11 11.8358 11 12.25Z" fill="#353535"/>
</g>
<defs>
<clipPath id="clip0_340_5833">
<rect width="16" height="16" fill="white"/>
</clipPath>
</defs>
</svg>

After

Width:  |  Height:  |  Size: 1.1 KiB

View file

@ -0,0 +1,3 @@
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path fill-rule="evenodd" clip-rule="evenodd" d="M14.4289 4.03093L14.5357 3.84641C14.7628 3.46915 15.2354 3.35853 15.5798 3.56346L15.593 3.57113L17.3235 4.56141C17.7982 4.83272 17.9624 5.4493 17.6908 5.91957C17.1401 6.8701 16.9763 7.94135 17.4902 8.83311C18.0042 9.72502 19.0132 10.12 20.11 10.12C20.656 10.12 21.11 10.5701 21.11 11.12V12.88C21.11 13.426 20.6598 13.88 20.11 13.88C19.0132 13.88 18.0042 14.275 17.4902 15.1669C16.9767 16.058 17.1399 17.1284 17.6896 18.0784C17.9642 18.5612 17.7961 19.1685 17.323 19.4389L15.593 20.4289L15.5798 20.4365C15.2354 20.6415 14.7628 20.5308 14.5357 20.1536L14.4308 19.9724L14.4297 19.9706C13.8837 19.0191 13.0383 18.3425 12.0087 18.3425C10.9799 18.3425 10.1319 19.0187 9.58109 19.9691L9.47424 20.1536C9.24711 20.5308 8.77457 20.6414 8.43014 20.4365L8.41699 20.4289L6.6865 19.4386C6.21174 19.1673 6.04759 18.5507 6.31911 18.0805C6.8698 17.1299 7.03367 16.0587 6.51976 15.1669C6.00578 14.275 4.99672 13.88 3.89998 13.88C3.35011 13.88 2.89998 13.426 2.89998 12.88V11.12C2.89998 10.574 3.35011 10.12 3.89998 10.12C4.99672 10.12 6.00578 9.72502 6.51976 8.83311C7.03367 7.94133 6.8698 6.87005 6.31911 5.9195C6.04759 5.44924 6.21222 4.83243 6.68699 4.56113L8.41699 3.57113L8.43014 3.56346C8.77458 3.35854 9.24714 3.46915 9.47426 3.8464L9.58016 4.02932C10.1262 4.98086 10.9716 5.65749 12.0012 5.65749C13.03 5.65749 13.878 4.9813 14.4289 4.03093ZM6.31911 5.9195C6.31895 5.91922 6.31879 5.91894 6.31863 5.91867L5.53998 6.36999L6.31925 5.91974C6.31921 5.91966 6.31916 5.91958 6.31911 5.9195ZM17.6896 18.0784C17.6902 18.0794 17.6908 18.0804 17.6913 18.0813L18.47 17.63L17.6886 18.0765C17.6889 18.0772 17.6893 18.0778 17.6896 18.0784ZM12.9865 2.92983C13.6989 1.73245 15.2596 1.28388 16.4929 2.01224L18.217 2.99885C19.5618 3.76765 20.0174 5.49069 19.2493 6.82024L19.2486 6.82132C18.8897 7.44056 18.9688 7.79392 19.0498 7.93437C19.1308 8.07497 19.3967 8.31999 20.11 8.31999C21.6439 8.31999 22.91 9.56986 22.91 11.12V12.88C22.91 14.414 21.6601 15.68 20.11 15.68C19.3967 15.68 19.1308 15.925 19.0498 16.0656C18.9688 16.2061 18.8897 16.5594 19.2486 17.1787L19.2514 17.1835C20.0153 18.5204 19.5627 20.2321 18.2165 21.0014L16.4929 21.9878C15.2596 22.7161 13.6989 22.2675 12.9865 21.0702L12.9811 21.0609L12.8711 20.8709L12.8692 20.8676C12.5152 20.25 12.171 20.1425 12.0087 20.1425C11.8451 20.1425 11.4977 20.252 11.1386 20.8713L11.0234 21.0702C10.3111 22.2675 8.75036 22.7161 7.51707 21.9878L5.79345 21.0014C4.4483 20.2328 3.99242 18.5095 4.7607 17.1797L4.76132 17.1787C5.12024 16.5594 5.04113 16.2061 4.96019 16.0656C4.87917 15.925 4.61323 15.68 3.89998 15.68C2.34984 15.68 1.09998 14.414 1.09998 12.88V11.12C1.09998 9.58602 2.34984 8.31999 3.89998 8.31999C4.61323 8.31999 4.87917 8.07497 4.96019 7.93437C5.04113 7.79392 5.12024 7.44056 4.76132 6.82132L4.7607 6.82024C3.99251 5.49069 4.44867 3.76737 5.79345 2.99857L7.51705 2.01224C8.75034 1.28388 10.3111 1.73245 11.0234 2.92983L11.0289 2.93906L11.1408 3.1324C11.4948 3.74995 11.8389 3.85749 12.0012 3.85749C12.1649 3.85749 12.5122 3.74829 12.8713 3.12867L12.9865 2.92983ZM9.89998 12C9.89998 10.8402 10.8402 9.89998 12 9.89998C13.1598 9.89998 14.1 10.8402 14.1 12C14.1 13.1598 13.1598 14.1 12 14.1C10.8402 14.1 9.89998 13.1598 9.89998 12ZM12 8.09998C9.84607 8.09998 8.09998 9.84607 8.09998 12C8.09998 14.1539 9.84607 15.9 12 15.9C14.1539 15.9 15.9 14.1539 15.9 12C15.9 9.84607 14.1539 8.09998 12 8.09998Z" fill="#353535"/>
</svg>

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 712 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 351 B

View file

@ -0,0 +1,9 @@
<div>
<button type="button"
[disabled]="!isEnabled()"
class="btn btn-secondary"
[ngbTooltip]="tooltip | emptyIfNull"
(click)="onClick()"
(focus)="onFocus()"
(blur)="onBlur()">{{caption}}</button>
</div>

Some files were not shown because too many files have changed in this diff Show more