Merge branch 'hotfix/1.9.9' into feature/SUPPORT-8643_error_code

# Conflicts:
#	frontend/src/ts/modules/security/guard/auth.guard.ts
This commit is contained in:
Eduard Tihomirov 2025-02-26 10:23:32 +03:00
commit 89ff7b8747
169 changed files with 1561 additions and 375 deletions

View file

@ -5,7 +5,7 @@
<parent>
<groupId>ru.micord.ervu.lkrp</groupId>
<artifactId>fl</artifactId>
<version>1.9.8-SNAPSHOT</version>
<version>1.9.9-SNAPSHOT</version>
</parent>
<groupId>ru.micord.ervu.lkrp.fl</groupId>
<artifactId>backend</artifactId>

View file

@ -0,0 +1,88 @@
package ru.micord.ervu.audit.config;
import java.util.HashMap;
import java.util.Map;
import org.apache.kafka.clients.CommonClientConfigs;
import org.apache.kafka.clients.admin.AdminClientConfig;
import org.apache.kafka.clients.admin.NewTopic;
import org.apache.kafka.clients.producer.ProducerConfig;
import org.apache.kafka.common.config.SaslConfigs;
import org.apache.kafka.common.serialization.StringSerializer;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.kafka.config.TopicBuilder;
import org.springframework.kafka.core.DefaultKafkaProducerFactory;
import org.springframework.kafka.core.KafkaAdmin;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.kafka.core.ProducerFactory;
/**
* @author Adel Kalimullin
*/
@Configuration
public class AuditKafkaConfig {
@Value("${audit.kafka.bootstrap.servers}")
private String bootstrapServers;
@Value("${audit.kafka.security.protocol}")
private String securityProtocol;
@Value("${audit.kafka.login.module:org.apache.kafka.common.security.scram.ScramLoginModule}")
private String loginModule;
@Value("${audit.kafka.username}")
private String username;
@Value("${audit.kafka.password}")
private String password;
@Value("${audit.kafka.sasl.mechanism}")
private String saslMechanism;
@Value("${audit.kafka.authorization.topic}")
private String authorizationTopic;
@Value("${audit.kafka.action.topic}")
private String actionTopic;
@Value("${audit.kafka.file.download.topic}")
private String fileDownloadTopic;
@Bean("auditProducerFactory")
public ProducerFactory<String, String> producerFactory() {
Map<String, Object> configProps = new HashMap<>();
configProps.put(ProducerConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
configProps.put(ProducerConfig.KEY_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
configProps.put(ProducerConfig.VALUE_SERIALIZER_CLASS_CONFIG, StringSerializer.class);
configProps.put(CommonClientConfigs.SECURITY_PROTOCOL_CONFIG, securityProtocol);
configProps.put(SaslConfigs.SASL_JAAS_CONFIG, loginModule + " required username=\""
+ username + "\" password=\"" + password + "\";");
configProps.put(SaslConfigs.SASL_MECHANISM, saslMechanism);
return new DefaultKafkaProducerFactory<>(configProps);
}
@Bean("auditTemplate")
public KafkaTemplate<String, String> kafkaTemplate() {
return new KafkaTemplate<>(producerFactory());
}
@Bean
public KafkaAdmin auditKafkaAdmin() {
Map<String, Object> configs = new HashMap<>();
configs.put(AdminClientConfig.BOOTSTRAP_SERVERS_CONFIG, bootstrapServers);
configs.put(AdminClientConfig.SECURITY_PROTOCOL_CONFIG, securityProtocol);
configs.put(SaslConfigs.SASL_JAAS_CONFIG, loginModule + " required username=\""
+ username + "\" password=\"" + password + "\";");
configs.put(SaslConfigs.SASL_MECHANISM, saslMechanism);
return new KafkaAdmin(configs);
}
@Bean
public NewTopic auditAuthTopic() {
return TopicBuilder.name(authorizationTopic).build();
}
@Bean
public NewTopic auditActionTopic() {
return TopicBuilder.name(actionTopic).build();
}
@Bean
public NewTopic auditDownloadTopic() {
return TopicBuilder.name(fileDownloadTopic).build();
}
}

View file

@ -0,0 +1,43 @@
package ru.micord.ervu.audit.constants;
import java.util.Map;
import java.util.Optional;
/**
* @author Adel Kalimullin
*/
public final class AuditConstants {
public static final String SUBSYSTEM_TYPE = "FL";
public static final String LOGOUT_EVENT_TYPE = "logout";
public static final String LOGIN_EVENT_TYPE = "login";
public static final String SUCCESS_STATUS = "success";
public static final String FAILURE_STATUS = "failure";
private static final Map<String, String> routeDescriptions = Map.of(
"/", "Главная",
"/mydata", "Мои данные",
"/subpoena", "Повестки",
"/restriction", "Временные меры"
);
private static final Map<String, String> downloadTypes = Map.of(
"1", "Выписка из реестра воинского учета ФЛ",
"2", "Выписка из реестра повесток ФЛ"
);
public static String getRouteDescription(String route) {
return Optional.ofNullable(routeDescriptions.get(route))
.orElseThrow(() -> new IllegalArgumentException("Invalid route :" + route));
}
public static String getDownloadType(String formatRegistry) {
return Optional.ofNullable(downloadTypes.get(formatRegistry))
.orElseThrow(
() -> new IllegalArgumentException("Invalid formatRegistry :" + formatRegistry));
}
private AuditConstants() {
}
}

View file

@ -0,0 +1,31 @@
package ru.micord.ervu.audit.controller;
import javax.servlet.http.HttpServletRequest;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import ru.micord.ervu.audit.model.AuditActionRequest;
import ru.micord.ervu.audit.service.AuditService;
/**
* @author Adel Kalimullin
*/
@RestController
@RequestMapping("/audit")
public class AuditController {
private final AuditService auditService;
public AuditController(AuditService auditService) {
this.auditService = auditService;
}
@PostMapping("/action")
public ResponseEntity<Void> auditAction(HttpServletRequest request,
@RequestBody AuditActionRequest actionEvent) {
auditService.processActionEvent(request, actionEvent);
return ResponseEntity.ok().build();
}
}

View file

@ -0,0 +1,53 @@
package ru.micord.ervu.audit.model;
/**
* @author Adel Kalimullin
*/
public class AuditActionEvent extends AuditEvent {
private String eventType;
private String description;
private String sourceUrl;
private String fileName;
public AuditActionEvent(
String esiaPersonId, String eventTime, String eventType,
String description, String sourceUrl, String fileName) {
super(esiaPersonId, eventTime);
this.eventType = eventType;
this.description = description;
this.sourceUrl = sourceUrl;
this.fileName = fileName;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public String getEventType() {
return eventType;
}
public void setEventType(String eventType) {
this.eventType = eventType;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getSourceUrl() {
return sourceUrl;
}
public void setSourceUrl(String sourceUrl) {
this.sourceUrl = sourceUrl;
}
}

View file

@ -0,0 +1,43 @@
package ru.micord.ervu.audit.model;
/**
* @author Adel Kalimullin
*/
public class AuditActionRequest {
private String route;
private String sourceUrl;
private String eventType;
private String fileName;
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public String getRoute() {
return route;
}
public void setRoute(String route) {
this.route = route;
}
public String getSourceUrl() {
return sourceUrl;
}
public void setSourceUrl(String sourceUrl) {
this.sourceUrl = sourceUrl;
}
public String getEventType() {
return eventType;
}
public void setEventType(String eventType) {
this.eventType = eventType;
}
}

View file

@ -0,0 +1,113 @@
package ru.micord.ervu.audit.model;
public class AuditAuthorizationEvent extends AuditEvent {
private String status;
private String eventType;
private String firstName;
private String lastName;
private String middleName;
private String snils;
private String serverIp;
private String serverHostName;
private String clientIp;
private String clientHostName;
public AuditAuthorizationEvent(
String esiaPersonId, String eventTime, String firstName,
String lastName, String middleName, String snils,
String status, String eventType, String serverIp,
String serverHostName, String clientIp, String clientHostName) {
super(esiaPersonId, eventTime);
this.status = status;
this.firstName = firstName;
this.lastName = lastName;
this.middleName = middleName;
this.snils = snils;
this.eventType = eventType;
this.serverIp = serverIp;
this.serverHostName = serverHostName;
this.clientIp = clientIp;
this.clientHostName = clientHostName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getMiddleName() {
return middleName;
}
public void setMiddleName(String middleName) {
this.middleName = middleName;
}
public String getSnils() {
return snils;
}
public void setSnils(String snils) {
this.snils = snils;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
public String getEventType() {
return eventType;
}
public void setEventType(String eventType) {
this.eventType = eventType;
}
public String getServerIp() {
return serverIp;
}
public void setServerIp(String serverIp) {
this.serverIp = serverIp;
}
public String getServerHostName() {
return serverHostName;
}
public void setServerHostName(String serverHostName) {
this.serverHostName = serverHostName;
}
public String getClientIp() {
return clientIp;
}
public void setClientIp(String clientIp) {
this.clientIp = clientIp;
}
public String getClientHostName() {
return clientHostName;
}
public void setClientHostName(String clientHostName) {
this.clientHostName = clientHostName;
}
}

View file

@ -0,0 +1,65 @@
package ru.micord.ervu.audit.model;
/**
* @author Adel Kalimullin
*/
public class AuditDownloadEvent extends AuditEvent {
private String downloadType;
private String fileName;
private String s3FileUrl;
private String fileSize;
private String status;
public AuditDownloadEvent(
String esiaPersonId, String eventTime, String downloadType,
String fileName, String s3FileUrl, String fileSize,
String status) {
super(esiaPersonId, eventTime);
this.downloadType = downloadType;
this.fileName = fileName;
this.s3FileUrl = s3FileUrl;
this.fileSize = fileSize;
this.status = status;
}
public String getS3FileUrl() {
return s3FileUrl;
}
public void setS3FileUrl(String s3FileUrl) {
this.s3FileUrl = s3FileUrl;
}
public String getDownloadType() {
return downloadType;
}
public void setDownloadType(String downloadType) {
this.downloadType = downloadType;
}
public String getFileName() {
return fileName;
}
public void setFileName(String fileName) {
this.fileName = fileName;
}
public String getFileSize() {
return fileSize;
}
public void setFileSize(String fileSize) {
this.fileSize = fileSize;
}
public String getStatus() {
return status;
}
public void setStatus(String status) {
this.status = status;
}
}

View file

@ -0,0 +1,38 @@
package ru.micord.ervu.audit.model;
import ru.micord.ervu.audit.constants.AuditConstants;
/**
* @author Adel Kalimullin
*/
public abstract class AuditEvent {
protected final String subsystem = AuditConstants.SUBSYSTEM_TYPE;
protected String esiaPersonId;
protected String eventTime;
protected AuditEvent(String esiaPersonId, String eventTime) {
this.esiaPersonId = esiaPersonId;
this.eventTime = eventTime;
}
public String getEsiaPersonId() {
return esiaPersonId;
}
public void setEsiaPersonId(String esiaPersonId) {
this.esiaPersonId = esiaPersonId;
}
public String getSubsystem() {
return subsystem;
}
public String getEventTime() {
return eventTime;
}
public void setEventTime(String eventTime) {
this.eventTime = eventTime;
}
}

View file

@ -0,0 +1,8 @@
package ru.micord.ervu.audit.service;
/**
* @author Adel Kalimullin
*/
public interface AuditKafkaPublisher {
void publishEvent(String topicName, String message);
}

View file

@ -0,0 +1,19 @@
package ru.micord.ervu.audit.service;
import javax.servlet.http.HttpServletRequest;
import ru.micord.ervu.audit.model.AuditActionRequest;
import ru.micord.ervu.security.esia.model.PersonModel;
/**
* @author Adel Kalimullin
*/
public interface AuditService {
void processActionEvent(HttpServletRequest request, AuditActionRequest auditActionRequest);
void processAuthEvent(HttpServletRequest request, PersonModel personModel, String status,
String eventType);
void processDownloadEvent(HttpServletRequest request, int fileSize, String fileName,
String formatRegistry, String status);
}

View file

@ -0,0 +1,42 @@
package ru.micord.ervu.audit.service.impl;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.kafka.core.KafkaTemplate;
import org.springframework.stereotype.Service;
import ru.micord.ervu.audit.service.AuditKafkaPublisher;
/**
* @author Adel Kalimullin
*/
@Service
public class BaseAuditKafkaPublisher implements AuditKafkaPublisher {
private static final Logger LOGGER = LoggerFactory.getLogger(BaseAuditKafkaPublisher.class);
private final KafkaTemplate<String, String> kafkaTemplate;
@Value("${audit.kafka.enabled}")
private boolean auditEnabled;
public BaseAuditKafkaPublisher(
@Qualifier("auditTemplate") KafkaTemplate<String, String> kafkaTemplate) {
this.kafkaTemplate = kafkaTemplate;
}
@Override
public void publishEvent(String topic, String message) {
if (auditEnabled) {
kafkaTemplate.send(topic, message)
.addCallback(
result -> {
},
ex -> LOGGER.error("Failed to send message to topic {}: {}", topic, ex.getMessage(),
ex
)
);
}
else {
LOGGER.info("Audit is disabled. Event not published.");
}
}
}

View file

@ -0,0 +1,114 @@
package ru.micord.ervu.audit.service.impl;
import javax.servlet.http.HttpServletRequest;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import ru.micord.ervu.audit.constants.AuditConstants;
import ru.micord.ervu.audit.model.AuditActionEvent;
import ru.micord.ervu.audit.model.AuditActionRequest;
import ru.micord.ervu.audit.model.AuditAuthorizationEvent;
import ru.micord.ervu.audit.model.AuditDownloadEvent;
import ru.micord.ervu.audit.service.AuditKafkaPublisher;
import ru.micord.ervu.audit.service.AuditService;
import ru.micord.ervu.security.esia.model.PersonModel;
import ru.micord.ervu.security.webbpm.jwt.service.JwtTokenService;
import ru.micord.ervu.util.DateUtils;
import ru.micord.ervu.util.NetworkUtils;
/**
* @author Adel Kalimullin
*/
@Service
public class BaseAuditService implements AuditService {
private final AuditKafkaPublisher auditPublisher;
private final JwtTokenService jwtTokenService;
private final ObjectMapper objectMapper;
@Value("${audit.kafka.authorization.topic}")
private String authorizationTopic;
@Value("${audit.kafka.action.topic}")
private String actionTopic;
@Value("${audit.kafka.file.download.topic}")
private String fileDownloadTopic;
public BaseAuditService(AuditKafkaPublisher auditPublisher, JwtTokenService jwtTokenService,
ObjectMapper objectMapper) {
this.auditPublisher = auditPublisher;
this.jwtTokenService = jwtTokenService;
this.objectMapper = objectMapper;
}
@Override
public void processActionEvent(
HttpServletRequest request, AuditActionRequest auditActionRequest) {
String userAccountId = jwtTokenService.getUserAccountId(request);
String description = AuditConstants.getRouteDescription(auditActionRequest.getRoute());
AuditActionEvent event = new AuditActionEvent(
userAccountId,
DateUtils.getClientTimeFromRequest(request),
auditActionRequest.getEventType(),
description,
auditActionRequest.getSourceUrl(),
auditActionRequest.getFileName()
);
String message = convertToMessage(event);
auditPublisher.publishEvent(actionTopic, message);
}
@Override
public void processAuthEvent(HttpServletRequest request, PersonModel personModel, String status,
String eventType) {
String serverIp = NetworkUtils.getServerIp();
String clientIp = NetworkUtils.getClientIp(request);
String serverHostName = NetworkUtils.getHostName(serverIp);
String clientHostName = NetworkUtils.getHostName(clientIp);
AuditAuthorizationEvent event = new AuditAuthorizationEvent(
personModel.getPrnsId(),
DateUtils.getClientTimeFromRequest(request),
personModel.getFirstName(),
personModel.getLastName(),
personModel.getMiddleName(),
personModel.getSnils(),
status,
eventType,
serverIp,
serverHostName,
clientIp,
clientHostName
);
String message = convertToMessage(event);
auditPublisher.publishEvent(authorizationTopic, message);
}
@Override
public void processDownloadEvent(
HttpServletRequest request, int fileSize, String fileName, String formatRegistry,
String status) {
String userAccountId = jwtTokenService.getUserAccountId(request);
AuditDownloadEvent event = new AuditDownloadEvent(
userAccountId,
DateUtils.getClientTimeFromRequest(request),
AuditConstants.getDownloadType(formatRegistry),
fileName,
null,
String.valueOf(fileSize),
status
);
String message = convertToMessage(event);
auditPublisher.publishEvent(fileDownloadTopic, message);
}
private String convertToMessage(Object event) {
try {
return objectMapper.writeValueAsString(event);
}
catch (JsonProcessingException e) {
throw new RuntimeException(e);
}
}
}

View file

@ -4,6 +4,7 @@ import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
import com.google.protobuf.ByteString;
import com.google.protobuf.InvalidProtocolBufferException;
import org.apache.kafka.clients.consumer.ConsumerRecord;
import org.apache.kafka.common.utils.Bytes;
@ -16,6 +17,8 @@ import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import ru.micord.ervu.audit.constants.AuditConstants;
import ru.micord.ervu.audit.service.AuditService;
import ru.micord.ervu.dto.ExtractEmptyRequestDto;
import ru.micord.ervu.dto.ExtractRequestDto;
import ru.micord.ervu.kafka.dto.EmptyExtract;
@ -28,6 +31,8 @@ import ru.micord.ervu.security.esia.token.EsiaTokensStore;
import ru.micord.ervu.security.webbpm.jwt.UserIdsPair;
import ru.micord.ervu.security.webbpm.jwt.util.SecurityUtil;
import javax.servlet.http.HttpServletRequest;
/**
* @author gulnaz
*/
@ -35,6 +40,7 @@ import ru.micord.ervu.security.webbpm.jwt.util.SecurityUtil;
public class ExtractController {
private final PersonalDataService personalDataService;
private final ReplyingKafkaService<Object, Bytes> replyingKafkaService;
private final AuditService auditService;
@Value("${ervu.kafka.registry.extract.empty.request.topic}")
private String registryExtractEmptyRequestTopic;
@ -45,13 +51,18 @@ public class ExtractController {
@Value("${ervu.kafka.registry.extract.type.header:empty}")
private String registryExtractTypeHeader;
public ExtractController(PersonalDataService personalDataService, ReplyingKafkaService<Object, Bytes> replyingKafkaService) {
public ExtractController(
PersonalDataService personalDataService,
ReplyingKafkaService<Object, Bytes> replyingKafkaService,
AuditService auditService
) {
this.personalDataService = personalDataService;
this.replyingKafkaService = replyingKafkaService;
this.auditService = auditService;
}
@GetMapping(value = "/extract/{formatRegistry}")
public ResponseEntity<Resource> getExtract(@PathVariable String formatRegistry) {
public ResponseEntity<Resource> getExtract(HttpServletRequest servletRequest, @PathVariable String formatRegistry) {
UserIdsPair userIdsPair = SecurityUtil.getUserIdsPair();
String ervuId = userIdsPair.getErvuId();
ConsumerRecord<String, Bytes> record;
@ -81,17 +92,28 @@ public class ExtractController {
registryExtractReplyTopic, emptyRequest);
}
byte[] bytes = record.value().get();
String fileName = null;
int size = 0;
try {
Extract extract = ervuId == null || isEmpty ? new EmptyExtract(bytes) : new FullExtract(bytes);
String encodedFilename = URLEncoder.encode(extract.getFileName(), StandardCharsets.UTF_8);
InputStreamResource resource = new InputStreamResource(extract.getFile().newInput());
fileName = extract.getFileName();
String encodedFilename = URLEncoder.encode(fileName, StandardCharsets.UTF_8);
ByteString file = extract.getFile();
InputStreamResource resource = new InputStreamResource(file.newInput());
size = file.size();
auditService.processDownloadEvent(servletRequest, size, fileName, formatRegistry,
AuditConstants.SUCCESS_STATUS
);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename*=UTF-8''" + encodedFilename)
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(resource);
}
catch (InvalidProtocolBufferException e) {
auditService.processDownloadEvent(servletRequest, size, fileName, formatRegistry,
AuditConstants.FAILURE_STATUS
);
throw new RuntimeException("Failed to parse data", e);
}
}

View file

@ -10,15 +10,14 @@ import proto.ervu.rp.summons.RecruitmentInfo;
import ru.micord.ervu.dto.Restriction;
import ru.micord.ervu.dto.SubpoenaResponseDto;
import org.springframework.stereotype.Component;
import proto.ervu.rp.summons.MeasuresTemporary;
import proto.ervu.rp.summons.ResponseDataAddress;
import proto.ervu.rp.summons.SummonsInfo;
import proto.ervu.rp.summons.SummonsResponseData;
import static ru.micord.ervu.util.DateUtil.convertToLocalDate;
import static ru.micord.ervu.util.DateUtils.convertToLocalDate;
import static java.util.Objects.requireNonNull;
import static ru.micord.ervu.util.DateUtil.convertToString;
import static ru.micord.ervu.util.DateUtil.getDaysTill;
import static ru.micord.ervu.util.DateUtils.convertToString;
import static ru.micord.ervu.util.DateUtils.getDaysTill;
/**
* @author gulnaz

View file

@ -4,8 +4,8 @@ import java.util.ArrayList;
import java.util.List;
import static org.springframework.util.StringUtils.hasText;
import static ru.micord.ervu.util.DateUtil.convertToLocalDate;
import static ru.micord.ervu.util.DateUtil.convertToString;
import static ru.micord.ervu.util.DateUtils.convertToLocalDate;
import static ru.micord.ervu.util.DateUtils.convertToString;
/**
* @author gulnaz

View file

@ -26,6 +26,8 @@ import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.support.MessageSourceAccessor;
import org.springframework.security.core.context.SecurityContext;
import ru.micord.ervu.audit.constants.AuditConstants;
import ru.micord.ervu.audit.service.AuditService;
import ru.micord.ervu.kafka.model.Document;
import ru.micord.ervu.kafka.model.Person;
import ru.micord.ervu.kafka.model.Response;
@ -75,6 +77,9 @@ public class EsiaAuthService {
@Autowired
private PersonalDataService personalDataService;
@Autowired
private AuditService auditService;
@Autowired
private SecurityHelper securityHelper;
@ -233,16 +238,25 @@ public class EsiaAuthService {
Thread.currentThread().getId(), signSecret, requestAccessToken, verifySecret);
}
PersonModel personModel = null;
String ervuId = null;
String ervuId = null, status = null;
try {
personModel = personalDataService.getPersonModel(esiaAccessTokenStr);
Response ervuIdResponse = getErvuIdResponse(personModel);
ervuId = ervuIdResponse.getErvuId();
status = AuditConstants.SUCCESS_STATUS;
}
catch (EsiaException | JsonProcessingException e) {
throw new EsiaException(e);
catch (Exception e) {
status = AuditConstants.FAILURE_STATUS;
if (e instanceof EsiaException || e instanceof JsonProcessingException) {
throw new EsiaException(e);
}
}
finally {
if (personModel != null) {
auditService.processAuthEvent(
request, personModel, status, AuditConstants.LOGIN_EVENT_TYPE
);
}
createTokenAndAddCookie(response, prnOid, ervuId, expiresIn);
}
}
@ -349,9 +363,12 @@ public class EsiaAuthService {
}
public String logout(HttpServletRequest request, HttpServletResponse response) {
PersonModel personModel = null;
try {
securityHelper.clearAccessCookies(response);
String userId = jwtTokenService.getUserAccountId(request);
String accessToken = EsiaTokensStore.getAccessToken(userId);
personModel = personalDataService.getPersonModel(accessToken);
securityHelper.clearAccessCookies(response);
EsiaTokensStore.removeAccessToken(userId);
EsiaTokensStore.removeRefreshToken(userId);
String logoutUrl = esiaConfig.getEsiaBaseUri() + esiaConfig.getEsiaLogoutUrl();
@ -359,10 +376,19 @@ public class EsiaAuthService {
URL url = new URL(logoutUrl);
Map<String, String> params = mapOf(
"client_id", esiaConfig.getClientId(),
"redirect_url", redirectUrl);
"redirect_url", redirectUrl
);
auditService.processAuthEvent(
request, personModel, AuditConstants.SUCCESS_STATUS, AuditConstants.LOGOUT_EVENT_TYPE
);
return buildUrl(url, params);
}
catch (Exception e) {
if (personModel != null){
auditService.processAuthEvent(
request, personModel, AuditConstants.FAILURE_STATUS, AuditConstants.LOGOUT_EVENT_TYPE
);
}
throw new EsiaException(e);
}
}

View file

@ -1,19 +1,39 @@
package ru.micord.ervu.util;
import java.time.LocalDate;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.time.temporal.ChronoUnit;
import org.springframework.util.StringUtils;
import javax.servlet.http.HttpServletRequest;
/**
* @author gulnaz
*/
public final class DateUtil {
public final class DateUtils {
public static final DateTimeFormatter DEFAULT_FORMATTER = DateTimeFormatter.ofPattern("dd.MM.yyyy");
private DateUtil() {}
private static final DateTimeFormatter DATE_TIME_WITH_TIMEZONE_FORMATTER =
DateTimeFormatter.ofPattern("yyyy-MM-dd'T'HH:mm:ss.SSSX");
private DateUtils() {
}
public static String getClientTimeFromRequest(HttpServletRequest request) {
String clientTimeZone = request.getHeader("Client-Time-Zone");
ZoneId zoneId;
try {
zoneId = ZoneId.of(clientTimeZone);
}
catch (Exception e) {
zoneId = ZoneId.systemDefault();
}
return ZonedDateTime.now(zoneId).format(DATE_TIME_WITH_TIMEZONE_FORMATTER);
}
public static LocalDate convertToLocalDate(String date) {
return StringUtils.hasText(date)

View file

@ -0,0 +1,53 @@
package ru.micord.ervu.util;
import java.net.InetAddress;
import java.net.UnknownHostException;
import javax.servlet.http.HttpServletRequest;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.util.StringUtils;
/**
* @author Adel Kalimullin
*/
public final class NetworkUtils {
private static final Logger LOGGER = LoggerFactory.getLogger(NetworkUtils.class);
private static final String IP_HEADER = "X-Forwarded-For";
private static final String UNKNOWN = "unknown";
private NetworkUtils() {
}
public static String getServerIp() {
try {
InetAddress inetAddress = InetAddress.getLocalHost();
return inetAddress.getHostAddress();
}
catch (UnknownHostException e) {
LOGGER.error("Failed to get local IP address", e);
return UNKNOWN;
}
}
public static String getClientIp(HttpServletRequest request) {
String ip = request.getHeader(IP_HEADER);
if (StringUtils.hasText(ip) && !ip.equalsIgnoreCase(UNKNOWN)) {
return ip.split(",")[0].trim();
}
else {
return request.getRemoteAddr();
}
}
public static String getHostName(String ip) {
try {
InetAddress inetAddress = InetAddress.getByName(ip);
return inetAddress.getHostName();
}
catch (UnknownHostException e) {
LOGGER.error("Unknown host for IP {}", ip, e);
return UNKNOWN;
}
}
}

View file

@ -1 +1 @@
error.unknown=The system is temporarily unavailable. Please try again later.
error.unknown=The system is temporarily unavailable. Please try again later.

View file

@ -788,6 +788,16 @@ JBPM использует 3 корневых категории логирова
- `ERVU_KAFKA_REGISTRY_EXTRACT_REQUEST_TOPIC` - топик для отправки запроса на получение выписки из Реестра воинского учета при наличии ErvuId
- `ERVU_KAFKA_REGISTRY_EXTRACT_REPLY_TOPIC` - топик для получения выписки из Реестра воинского учета
- `ERVU_KAFKA_EXTRACT_HEADER_CLASS` - класс для идентификации в заголовке запроса на получение выписки из Реестра повесток/Реестра воинского учета
- `AUDIT_KAFKA_AUTHORIZATION_TOPIC` - топик для отправки аудита в журнал авторизации
- `AUDIT_KAFKA_ACTION_TOPIC` - топик для отправки аудита в журнал действий пользователя
- `AUDIT_KAFKA_FILE_DOWNLOAD_TOPIC` - топик для отправки аудита в журнал загрузки ЮЛ и ФЛ
- `AUDIT_KAFKA_BOOTSTRAP_SERVERS` - список пар хост:порт, использующихся для установки первоначального соединения с кластером Kafka
- `AUDIT_KAFKA_SECURITY_PROTOCOL` - протокол, используемый для взаимодействия с брокерами
- `AUDIT_KAFKA_DOC_LOGIN_MODULE` - имя класса для входа в систему для SASL-соединений в формате, используемом конфигурационными файлами JAAS
- `AUDIT_KAFKA_USERNAME` - пользователь для подключения к Kafka
- `AUDIT_KAFKA_PASSWORD` - пароль для подключения к Kafka
- `AUDIT_KAFKA_SASL_MECHANISM` - механизм SASL, используемый для клиентских подключений
- `AUDIT_KAFKA_ENABLED` - флажок для включения записи аудита в кафку
# Прочее

View file

@ -33,6 +33,16 @@ ERVU_KAFKA_REGISTRY_EXTRACT_REQUEST_TOPIC=ervu.extract.info.request
ERVU_KAFKA_REGISTRY_EXTRACT_REPLY_TOPIC=ervu.extract.info.response
ERVU_KAFKA_EXTRACT_HEADER_CLASS=request@urn://rostelekom.ru/ERVU-extractFromRegistryTR/1.0.3
ERVU_KAFKA_DOC_LOGIN_MODULE=org.apache.kafka.common.security.scram.ScramLoginModule
AUDIT_KAFKA_AUTHORIZATION_TOPIC=ervu.lkrp.auth.events
AUDIT_KAFKA_ACTION_TOPIC=ervu.lkrp.action.events
AUDIT_KAFKA_FILE_DOWNLOAD_TOPIC=ervu.lkrp.import.file
AUDIT_KAFKA_BOOTSTRAP_SERVERS=
AUDIT_KAFKA_SECURITY_PROTOCOL=
AUDIT_KAFKA_DOC_LOGIN_MODULE=
AUDIT_KAFKA_USERNAME=
AUDIT_KAFKA_PASSWORD=
AUDIT_KAFKA_SASL_MECHANISM=
AUDIT_KAFKA_ENABLED=false
ESIA_TOKEN_CLEAR_CRON=0 0 */1 * * *
COOKIE_PATH=/fl

View file

@ -80,6 +80,16 @@
<property name="ervu.kafka.registry.extract.reply.topic" value="ervu.extract.info.response"/>
<property name="ervu.kafka.extract.header.class" value="request@urn://rostelekom.ru/ERVU-extractFromRegistryTR/1.0.3"/>
<property name="esia.token.clear.cron" value="0 0 */1 * * *"/>
<property name="audit.kafka.bootstrap.servers" value="localhost:9092"/>
<property name="audit.kafka.authorization.topic" value="ervu.lkrp.auth.events"/>
<property name="audit.kafka.file.download.topic" value="ervu.lkrp.import.file"/>
<property name="audit.kafka.action.topic" value="ervu.lkrp.action.events"/>
<property name="audit.kafka.security.protocol" value="SASL_PLAINTEXT"/>
<property name="audit.kafka.doc.login.module" value="org.apache.kafka.common.security.scram.ScramLoginModule"/>
<property name="audit.kafka.sasl.mechanism" value="SCRAM-SHA-256"/>
<property name="audit.kafka.username" value="user1"/>
<property name="audit.kafka.password" value="Blfi9d2OFG"/>
<property name="audit.kafka.enabled" value="false"/>
</system-properties>
<management>
<audit-log>

View file

@ -4,7 +4,7 @@
<parent>
<groupId>ru.micord.ervu.lkrp</groupId>
<artifactId>fl</artifactId>
<version>1.9.8-SNAPSHOT</version>
<version>1.9.9-SNAPSHOT</version>
</parent>
<groupId>ru.micord.ervu.lkrp.fl</groupId>

View file

@ -1748,9 +1748,9 @@
}
},
"@webbpm/base-package": {
"version": "3.187.2",
"resolved": "https://repo.micord.ru/repository/npm-all/@webbpm/base-package/-/base-package-3.187.2.tgz",
"integrity": "sha512-qDW+Yjm/gyTIM/4N7uQasQR1zk2tGGAF6rJFpSUSb1A7PYreXPqSAShzWJJJ1YZ9CCz2dAXSQzm6JjUJKu2VUg==",
"version": "3.187.3",
"resolved": "https://repo.micord.ru/repository/npm-all/@webbpm/base-package/-/base-package-3.187.3.tgz",
"integrity": "sha512-uhESrMdBnxeWXX5LNENvzzq0k0t4jHcuD1JmRAF0WIFfKqhZNMsmd0xxf2CYMUSJrWESyjX2wiqpKFhDHd0+/A==",
"requires": {
"tslib": "^1.9.0"
}

View file

@ -26,7 +26,7 @@
"@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.187.2",
"@webbpm/base-package": "3.187.3",
"ag-grid-angular": "29.0.0-micord.4",
"ag-grid-community": "29.0.0-micord.4",
"angular-calendar": "0.28.28",

View file

@ -4,7 +4,7 @@
<parent>
<groupId>ru.micord.ervu.lkrp</groupId>
<artifactId>fl</artifactId>
<version>1.9.8-SNAPSHOT</version>
<version>1.9.9-SNAPSHOT</version>
</parent>
<groupId>ru.micord.ervu.lkrp.fl</groupId>

View file

@ -17,5 +17,6 @@
"password_pattern_error": "Пароль должен содержать заглавные или прописные буквы и как минимум 1 цифру",
"show.client.errors": false,
"available_task.single_fetch": true,
"cert_check_url": "https://lkrp-dev2.micord.ru"
"cert_check_url": "https://lkrp-dev2.micord.ru",
"unknown.error.msg": "Система временно недоступна. Пожалуйста, повторите попытку позже."
}

View file

@ -0,0 +1,42 @@
import {AnalyticalScope, Behavior, Control, NotNull} from "@webbpm/base-package";
import {ElementRef} from "@angular/core";
import {AuditService} from "./service/AuditService";
import {LinkEventTypeEnum} from "./component/enum/LinkEventTypeEnum";
@AnalyticalScope(Control)
export class LinkClickHandler extends Behavior {
@NotNull()
public eventType: LinkEventTypeEnum;
private control: Control;
private auditService: AuditService;
private el: ElementRef;
initialize() {
this.control = this.getScript(Control);
this.el = this.control.getEl();
super.initialize();
}
bindEvents() {
super.bindEvents();
if (this.el) {
this.el.nativeElement.addEventListener('click',
(event: MouseEvent) => this.onClickFunction(event));
}
}
unbindEvents() {
super.unbindEvents()
if (this.el) {
this.el.nativeElement.removeEventListener('click',
(event: MouseEvent) => this.onClickFunction(event));
}
}
private onClickFunction(event: MouseEvent) {
const target = event.target as HTMLElement;
if (target.tagName === 'A') {
this.auditService.logActionAudit(this.eventType);
}
}
}

View file

@ -29,8 +29,12 @@ export class ExtractLoadService extends Behavior {
this.errorEvent.subscribe(() => console.log("error event occurred", this.errorEvent));
this.onClickFunction = () => {
console.log("click event occurred");
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
this.httpClient.get('extract/' + this.formatRegistry, {
responseType: 'blob',
headers: {
"Client-Time-Zone": timeZone,
},
observe: 'response'
}).toPromise()
.then((response) => {

View file

@ -0,0 +1,5 @@
export enum LinkEventTypeEnum {
NAVIGATION_TO_SOURCE = "Переход на другие источники",
DOWNLOAD_TEMPLATE = "Скачивание шаблона",
DOWNLOAD_EXAMPLE = "Скачивание примера заполнения формы"
}

View file

@ -0,0 +1,43 @@
import {Injectable} from "@angular/core";
import {HttpClient} from "@angular/common/http";
import {Router} from "@angular/router";
@Injectable({
providedIn: 'root'
})
export class AuditService {
constructor(private httpClient: HttpClient, private router: Router) {
}
public logActionAudit(eventType: string, fileName?: string): void {
const currentRoute = this.router.url;
const sourceUrl = window.location.href;
const timeZone = Intl.DateTimeFormat().resolvedOptions().timeZone;
const data: AuditAction = {
eventType: eventType,
sourceUrl: sourceUrl,
route: currentRoute,
fileName: fileName
};
this.httpClient.post("audit/action", data, {
headers: {
"Client-Time-Zone": timeZone,
}
}).toPromise();
}
}
export interface AuditAction {
eventType: string;
sourceUrl: string;
route: string;
fileName?: string;
}
export class AuditConstants {
public static readonly OPEN_PAGE_EVENT = "Открытие страницы";
}

View file

@ -24,6 +24,7 @@ import {LogOutComponent} from "./component/logout.component";
import {LoadForm} from "../../ervu/component/container/LoadForm";
import {InMemoryStaticGrid} from "../../ervu/component/grid/InMemoryStaticGrid";
import {AuthenticationService} from "../security/authentication.service";
import {AuditService} from "../../ervu/service/AuditService";
import {HomeLandingComponent} from "./component/home-landing.component";
registerLocaleData(localeRu);
@ -64,7 +65,7 @@ export function checkAuthentication(authService: AuthenticationService): () => P
DIRECTIVES
],
providers: [
AuthenticationService,
AuthenticationService, AuditService,
{
provide: APP_INITIALIZER,
useFactory: checkAuthentication,

View file

@ -2,6 +2,7 @@ import {Injectable} from '@angular/core';
import {HttpClient} from '@angular/common/http';
import {CookieService} from "ngx-cookie";
import {AppConfigService} from "@webbpm/base-package";
import {map, tap} from "rxjs/operators";
@Injectable({providedIn: 'root'})
export class AuthenticationService {
@ -22,4 +23,13 @@ export class AuthenticationService {
public isAuthenticated(): boolean {
return this.cookieService.get('webbpm.ervu-lkrp-fl') != null;
}
public redirectToEsia() {
return this.http.get<string>("esia/url").pipe(
tap(url => {
window.open(url, "_self");
}),
map(() => true)
);
}
}

View file

@ -32,7 +32,7 @@ export abstract class AuthGuard implements CanActivate {
}
if (error) {
let errorMessage =
'Произошла неизвестная ошибка. Обратитесь к системному администратору';
this.messageService.getUnknowErrorMessage();
let errorCode = this.extractCode(errorDescription);
if (errorCode) {
errorMessage = EsiaErrorDetail.getDescription(errorCode);
@ -57,15 +57,10 @@ export abstract class AuthGuard implements CanActivate {
return false;
}
else {
return this.httpClient.get<string>("esia/url")
.toPromise()
.then(url => {
window.open(url, "_self");
return true;
}).catch((reason)=> {
console.error(reason);
return false;
});
return this.authenticationService.redirectToEsia().toPromise().catch((reason) => {
console.error(reason);
return false;
});
}
}).catch((reason) => {
console.error(reason);

View file

@ -8,6 +8,7 @@ import {
Router
} from "@angular/router";
import {ProgressIndicationService} from "@webbpm/base-package";
import {AuditConstants, AuditService} from "../../../ervu/service/AuditService";
@Component({
moduleId: module.id,
@ -21,7 +22,8 @@ export class WebbpmComponent {
constructor(private router: Router,
private progressIndicationService: ProgressIndicationService,
private cd: ChangeDetectorRef) {
private cd: ChangeDetectorRef,
private auditService: AuditService) {
router.events.subscribe((event: Event) => {
if (event instanceof NavigationStart) {
progressIndicationService.showProgressBar();
@ -29,9 +31,15 @@ export class WebbpmComponent {
this.cd.markForCheck();
}
else if (event instanceof NavigationEnd
|| event instanceof NavigationError
|| event instanceof NavigationCancel) {
|| event instanceof NavigationError
|| event instanceof NavigationCancel) {
progressIndicationService.hideProgressBar();
if (event instanceof NavigationEnd
&& event.url != '/home'
&& event.url != '/access-denied') {
this.auditService.logActionAudit(AuditConstants.OPEN_PAGE_EVENT);
}
}
})
}

View file

@ -1,14 +1,14 @@
import {HTTP_INTERCEPTORS} from "@angular/common/http";
import {
FormDirtyInterceptor,
HttpSecurityErrorInterceptor,
HttpSecurityInterceptor
} from "@webbpm/base-package";
import {AbsoluteUrlCsrfInterceptor} from "./absolute-url-csrf.interceptor";
import {ErvuHttpSecurityErrorInterceptor} from "./ervu-http-security-error-interceptor";
export const DEFAULT_HTTP_INTERCEPTOR_PROVIDERS = [
{provide: HTTP_INTERCEPTORS, useClass: HttpSecurityInterceptor, multi: true},
{provide: HTTP_INTERCEPTORS, useClass: HttpSecurityErrorInterceptor, multi: true},
{provide: HTTP_INTERCEPTORS, useClass: ErvuHttpSecurityErrorInterceptor, multi: true},
{provide: HTTP_INTERCEPTORS, useClass: FormDirtyInterceptor, multi: true},
{provide: HTTP_INTERCEPTORS, useClass: AbsoluteUrlCsrfInterceptor, multi: true}
];

View file

@ -2,10 +2,11 @@ import {HTTP_INTERCEPTORS} from "@angular/common/http";
import {FormDirtyInterceptor, HttpSecurityInterceptor} from "@webbpm/base-package";
import {DevHttpSecurityErrorInterceptor} from "./http-security-error-interceptor.dev";
import {AbsoluteUrlCsrfInterceptor} from "./absolute-url-csrf.interceptor";
import {ErvuHttpSecurityErrorInterceptor} from "./ervu-http-security-error-interceptor";
export const DEFAULT_HTTP_INTERCEPTOR_PROVIDERS = [
{provide: HTTP_INTERCEPTORS, useClass: HttpSecurityInterceptor, multi: true},
{provide: HTTP_INTERCEPTORS, useClass: DevHttpSecurityErrorInterceptor, multi: true},
{provide: HTTP_INTERCEPTORS, useClass: ErvuHttpSecurityErrorInterceptor, multi: true},
{provide: HTTP_INTERCEPTORS, useClass: FormDirtyInterceptor, multi: true},
{provide: HTTP_INTERCEPTORS, useClass: AbsoluteUrlCsrfInterceptor, multi: true},
];

View file

@ -0,0 +1,43 @@
import {
HttpEvent,
HttpHandler,
HttpInterceptor,
HttpRequest, HttpResponse
} from "@angular/common/http";
import {
HttpSecurityErrorInterceptor,
MessagesService,
UserService
} from "@webbpm/base-package";
import {Injectable} from "@angular/core";
import {Router} from "@angular/router";
import {from, Observable} from "rxjs";
import {catchError, map} from "rxjs/operators";
import {AuthenticationService} from "../../security/authentication.service";
@Injectable()
export class ErvuHttpSecurityErrorInterceptor extends HttpSecurityErrorInterceptor
implements HttpInterceptor {
private authService: AuthenticationService;
constructor(router: Router, messagesService: MessagesService, userService: UserService,
authService: AuthenticationService) {
super(router, messagesService, userService);
this.authService = authService;
}
protected processAuthError(req: HttpRequest<any>, next: HttpHandler,
error: any): Observable<HttpEvent<any>> {
if (this.authService.isAuthenticated()) {
return super.processAuthError(req, next, error);
}
else {
return from(this.authService.redirectToEsia()).pipe(
map(() => new HttpResponse<any>()),
catchError((err) => {
throw err;
})
);
}
}
}

View file

@ -1,19 +1,20 @@
import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from "@angular/common/http";
import {HttpSecurityErrorInterceptor, MessagesService, UserService} from "@webbpm/base-package";
import {MessagesService, UserService} from "@webbpm/base-package";
import {Injectable} from "@angular/core";
import {Router} from "@angular/router";
import {EMPTY, Observable} from "rxjs";
import {catchError} from "rxjs/operators";
import {ErvuHttpSecurityErrorInterceptor} from "./ervu-http-security-error-interceptor";
import {AuthenticationService} from "../../security/authentication.service";
@Injectable()
export class DevHttpSecurityErrorInterceptor extends HttpSecurityErrorInterceptor
export class DevHttpSecurityErrorInterceptor extends ErvuHttpSecurityErrorInterceptor
implements HttpInterceptor {
private router: Router;
constructor(router: Router, messagesService: MessagesService, userService: UserService) {
super(router, messagesService, userService);
this.router = router;
constructor(router: Router, messagesService: MessagesService, userService: UserService,
authService: AuthenticationService) {
super(router, messagesService, userService, authService);
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {

View file

@ -4,7 +4,7 @@
<modelVersion>4.0.0</modelVersion>
<groupId>ru.cg.webbpm.packages.base</groupId>
<artifactId>resources</artifactId>
<version>3.187.2</version>
<version>3.187.3</version>
<organization>
<name>Micord</name>
</organization>
@ -28,13 +28,13 @@
<jooq.version>3.19.3</jooq.version>
<jupiter.version>5.10.2</jupiter.version>
<enforcer.manageVersions>true</enforcer.manageVersions>
<webbpm-platform.version>3.187.2</webbpm-platform.version>
<webbpm-platform.version>3.187.3</webbpm-platform.version>
<h2.version>1.4.200</h2.version>
<build.timestamp>0115092226</build.timestamp>
<build.timestamp>0224072420</build.timestamp>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<junit.platform.version>1.10.0</junit.platform.version>
<enforcer.manageExclusions>true</enforcer.manageExclusions>
<revision>3.187.2</revision>
<revision>3.187.3</revision>
<metadata.ts.filename>typescript.metadata.json</metadata.ts.filename>
<package.repository.url>https://repo.micord.ru</package.repository.url>
<maven.build.timestamp.format>MMddHHmmss</maven.build.timestamp.format>
@ -47,19 +47,19 @@
<dependency>
<groupId>ru.cg.webbpm.packages.base</groupId>
<artifactId>converters</artifactId>
<version>3.187.2</version>
<version>3.187.3</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>ru.cg.webbpm.packages.base</groupId>
<artifactId>backend</artifactId>
<version>3.187.2</version>
<version>3.187.3</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>ru.cg.webbpm.packages.base</groupId>
<artifactId>frontend</artifactId>
<version>3.187.2</version>
<version>3.187.3</version>
<scope>compile</scope>
</dependency>
</dependencies>

View file

@ -60,7 +60,7 @@
<ul>
<li>&#x41e;&#x431;&#x440;&#x430;&#x437;&#x435;&#x446; &#x432;&#x43d;&#x435;&#x448;&#x43d;&#x435;&#x439; &#x441;&#x441;&#x44b;&#x43b;&#x43a;&#x438;: <code>https://www.wildberries.ru/catalog/${sku}/detail.aspx</code></li>
<li>&#x41e;&#x431;&#x440;&#x430;&#x437;&#x435;&#x446; &#x432;&#x43d;&#x443;&#x442;&#x440;&#x435;&#x43d;&#x43d;&#x435;&#x439; &#x441;&#x441;&#x44b;&#x43b;&#x43a;&#x438;: <code>products/ru.cg.webbpm.packages.base:resources:jar:3.187.2</code></li>
<li>&#x41e;&#x431;&#x440;&#x430;&#x437;&#x435;&#x446; &#x432;&#x43d;&#x443;&#x442;&#x440;&#x435;&#x43d;&#x43d;&#x435;&#x439; &#x441;&#x441;&#x44b;&#x43b;&#x43a;&#x438;: <code>products/ru.cg.webbpm.packages.base:resources:jar:3.187.3</code></li>
</ul>
</li>
<li>

View file

@ -4,17 +4,17 @@
<description>Base webbpm package</description>
<groupId>ru.cg.webbpm.packages.base</groupId>
<artifactId>resources</artifactId>
<version>3.187.2</version>
<studioVersion>3.187.2</studioVersion>
<version>3.187.3</version>
<studioVersion>3.187.3</studioVersion>
<backendModule>
<groupId>ru.cg.webbpm.packages.base</groupId>
<artifactId>backend</artifactId>
<version>3.187.2</version>
<version>3.187.3</version>
</backendModule>
<frontendModule>
<packageName>@webbpm/base-package</packageName>
<version>3.187.2</version>
<version>3.187.3</version>
</frontendModule>
</packageInfo>

View file

@ -8,11 +8,11 @@
<documentation>component/buttons/Кнопка.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/buttons/Кнопка_отмены.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/buttons/Кнопка_очистки_фильтра.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/buttons/Кнопка_удаления.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/buttons/Кнопкаагрузки.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/buttons/Кнопка_вызова_ошибки.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -7,11 +7,11 @@
<documentation>component/buttons/Кнопка_выполнения_бизнес-процесса.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/buttons/Кнопка_выполнения_SQL.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/buttons/Кнопка_для_фильтрации.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/buttons/Кнопкаавигации.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/buttons/Кнопка_сохранения.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/buttons/Кнопка_выбора.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/buttons/Кнопка_подписи.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/buttons/Кнопкаапуска_бизнес-процесса.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/buttons/reporting/Кнопка_печати_из_графа_сущности.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/buttons/reporting/Кнопка_печати_отчета_из_формы.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/containers/Сворачиваемая_панель.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/containers/Диалог.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/containers/Контейнер_с_кнопками.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/containers/Группа_полей.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/containers/Набор_фильтров.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/containers/Форма.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/containers/Горизонтальный_контейнер.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/containers/Контейнер_вкладок.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/containers/Вкладка.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/containers/Вертикальный_контейнер.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/containers/Окно.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/editable-grids/EditableGrid.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -7,11 +7,11 @@
<localization>META-INF/components/localization/editable-grids/autocomplete</localization>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -7,11 +7,11 @@
<localization>META-INF/components/localization/editable-grids/check-box</localization>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -7,11 +7,11 @@
<localization>META-INF/components/localization/editable-grids/combo-box</localization>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -7,11 +7,11 @@
<localization>META-INF/components/localization/editable-grids/date-time-picker</localization>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -7,11 +7,11 @@
<localization>META-INF/components/localization/editable-grids/money-field</localization>
<internal>true</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -7,11 +7,11 @@
<localization>META-INF/components/localization/editable-grids/number-field</localization>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -7,11 +7,11 @@
<localization>META-INF/components/localization/editable-grids/one-to-many</localization>
<internal>true</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -7,11 +7,11 @@
<localization>META-INF/components/localization/editable-grids/one-to-many</localization>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -6,11 +6,11 @@
<localization>META-INF/components/localization/editable-grids/read-only</localization>
<internal>true</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -5,11 +5,11 @@
<category>editable-grids</category>
<internal>true</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>Статичный_выпадающий_список_колонки_таблицы.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -7,11 +7,11 @@
<localization>META-INF/components/localization/editable-grids/text-area</localization>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -7,11 +7,11 @@
<localization>META-INF/components/localization/editable-grids/text-field</localization>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -7,11 +7,11 @@
<localization>META-INF/components/localization/editable-grids/time-picker</localization>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/fields/ФИАС.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/fields/Поле_ввода_с_подбором_значения.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/fields/Флаг.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/fields/Выпадающий_список.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/fields/Дата.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/fields/EditableOneToMany.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/fields/Файл.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/fields/Файл.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/fields/ManyToMany.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/fields/ManyToManyField.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/fields/Денежное_поле.html</documentation>
<internal>true</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/fields/Числовое_поле.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/fields/OneToMany.html</documentation>
<internal>true</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

View file

@ -8,11 +8,11 @@
<documentation>component/fields/OneToMany.html</documentation>
<internal>false</internal>
<versions>
<studioVersion>3.187.2</studioVersion>
<studioVersion>3.187.3</studioVersion>
<packageVersions>
<entry>
<key>ru.cg.webbpm.packages.base.resources</key>
<value>3.187.2</value>
<value>3.187.3</value>
</entry>
</packageVersions>
</versions>

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