first commit

This commit is contained in:
Булат Хайруллин 2024-06-25 13:45:54 +03:00
commit 9c03bc4475
901 changed files with 140656 additions and 0 deletions

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"
}

10
frontend/bs-config.json Normal file
View file

@ -0,0 +1,10 @@
{
"port": 8000,
"open": false,
"files": [
"./**/*.{html,htm,css,js}"
],
"server": {
"baseDir": "./"
}
}

23
frontend/index.html Normal file
View file

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

View file

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

10696
frontend/package-lock.json generated Normal file

File diff suppressed because it is too large Load diff

101
frontend/package.json Normal file
View file

@ -0,0 +1,101 @@
{
"name": "webbpm-frontend",
"version": "1.0.0",
"scripts": {
"lite": "node ./node_modules/lite-server/bin/lite-server",
"cleanup": "npm run cleanup-ngc && node ./node_modules/rimraf/bin ./build ./dist",
"cleanup-ngc": "node ./node_modules/rimraf/bin ./src/ts/**/*.js ./src/ts/**/*.json ./src/ts/page.routing.ts",
"cleanup-and-ngc": "npm run cleanup && npm run ngc",
"ngc": "node --max-old-space-size=14336 ./node_modules/@angular/compiler-cli/src/main -p tsconfig.aot.json",
"build-webpack": "node --max-old-space-size=14336 ./node_modules/webpack/bin/webpack --config webpack.aot.config.js --progress --profile",
"save-ts-metadata": "node save.ts.metadata.js",
"tsc": "node ./node_modules/typescript/bin/tsc",
"tsc-watch": "node ./node_modules/typescript/bin/tsc --watch",
"ts-watch": "node node_modules/cross-env/dist/bin/cross-env.js TSC_NONPOLLING_WATCHER=true npm run tsc-watch",
"ts": "npm install && npm run tsc",
"compile": "npm run ts-watch",
"install-compile": "npm install && npm run ts-watch"
},
"dependencies": {
"@angular/animations": "7.2.15",
"@angular/common": "7.2.15",
"@angular/compiler": "7.2.15",
"@angular/core": "7.2.15",
"@angular/forms": "7.2.15",
"@angular/http": "7.2.15",
"@angular/platform-browser": "7.2.15",
"@angular/platform-browser-dynamic": "7.2.15",
"@angular/router": "7.2.15",
"@ng-bootstrap/ng-bootstrap": "4.1.1",
"@webbpm/base-package": "3.173.1",
"ag-grid-angular": "29.0.0-micord.4",
"ag-grid-community": "29.0.0-micord.4",
"angular-calendar": "0.28.28",
"autonumeric": "4.5.10-cg",
"bootstrap": "4.3.1",
"bootstrap-icons": "1.10.3",
"cadesplugin_api": "2.0.4-micord.1",
"chart.js": "3.8.0-cg.1",
"chartjs-adapter-moment": "1.0.0",
"core-js": "2.4.1",
"date-fns": "2.29.3",
"downloadjs": "1.4.8",
"eonasdan-bootstrap-datetimepicker": "4.17.47-micord.4",
"esmarttokenjs": "2.2.1-cg",
"font-awesome": "4.7.0",
"google-libphonenumber": "3.0.9",
"inputmask": "5.0.5-cg.2",
"jquery": "3.3.1",
"js-year-calendar": "1.0.0-cg.2",
"jsgantt-improved": "2.0.10-cg",
"moment": "2.17.1",
"moment-timezone": "0.5.11",
"ngx-cookie": "3.0.1",
"ngx-international-phone-number": "1.0.6",
"ngx-toastr": "10.2.0-cg",
"popper.js": "1.14.7",
"reflect-metadata": "0.1.13",
"rxjs": "6.4.0",
"rxjs-compat": "6.4.0",
"selectize": "0.12.4-cg.10",
"systemjs": "0.21.4",
"systemjs-plugin-babel": "0.0.25",
"tslib": "1.9.3",
"zone.js": "0.8.29"
},
"devDependencies": {
"@angular-devkit/build-optimizer": "0.13.9",
"@angular-devkit/core": "7.3.9",
"@angular/cli": "7.3.9",
"@angular/compiler-cli": "7.2.15",
"@angular/platform-server": "7.2.15",
"@babel/core": "7.9.6",
"@babel/preset-env": "7.9.6",
"@types/bootstrap": "3.3.39",
"@types/jquery": "2.0.49",
"@types/node": "7.0.5",
"@types/selectize": "0.12.33",
"angular-router-loader": "0.8.5",
"angular2-template-loader": "0.6.2",
"babel-loader": "8.1.0",
"codelyzer": "5.2.1",
"copy-webpack-plugin": "5.0.3",
"cross-env": "5.2.1",
"css-loader": "2.1.0",
"del": "2.2.2",
"file-loader": "3.0.1",
"html-webpack-plugin": "4.5.2",
"lite-server": "2.3.0",
"mini-css-extract-plugin": "0.6.0",
"mkdirp": "0.5.1",
"raw-loader": "1.0.0",
"style-loader": "0.23.1",
"terser-webpack-plugin": "1.2.4",
"tslint": "5.13.1",
"typescript": "3.2.4",
"typescript-parser": "2.6.1-cg-fork",
"webpack": "4.32.2",
"webpack-bundle-analyzer": "3.3.2",
"webpack-cli": "3.3.2"
}
}

113
frontend/pom.xml Normal file
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>ervu_dashboard</groupId>
<artifactId>ervu_dashboard</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<groupId>ervu_dashboard.ervu_dashboard</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_dashboard" 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": "%enable.version.in.url%",
"guard.confirm_exit": false,
"message_service_error_timeout": "",
"message_service_warning_timeout": "",
"message_service_success_timeout": "",
"message_service_info_timeout": "",
"jivo_chat_widget_api_url": "https://code.jivo.ru/widget/{ID}",
"jivo_chat_widget_enabled": false,
"password_pattern": "^((?=(.*\\d){1,})(?=.*[a-zа-яё])(?=.*[A-ZА-ЯЁ]).{8,})$",
"password_pattern_error": "Пароль должен содержать заглавные или прописные буквы и как минимум 1 цифру",
"show.client.errors": false,
"available_task.single_fetch": true
}

View file

@ -0,0 +1 @@
%project.version%

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

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 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,4 @@
<div class="error_message">
<div class="error_title">403</div>
<div class="error_body">Доступ запрещен</div>
</div>

View file

@ -0,0 +1,34 @@
<div *ngIf="(currentSession | async)?.authorities.includes('BPMN.ADMIN.PROCESS_INSTANCE.LIST') ||
(currentSession | async)?.authorities.includes('USER_MANAGEMENT.USER.LIST') ||
(currentSession | async)?.authorities.includes('USER_MANAGEMENT.GROUP.LIST') ||
(currentSession | async)?.authorities.includes('USER_MANAGEMENT.ROLE.LIST') ||
(currentSession | async)?.authorities.includes('USER_MANAGEMENT.ORG_UNIT.LIST') ||
(currentSession | async)?.authorities.includes('USER_MANAGEMENT.AUTHORITY.LIST')" ngbDropdown class="nav-item" [placement]="placement">
<button class="nav-link bi bi-gear-fill" id="adminDropdownMenu" ngbDropdownToggle title="Администрирование"></button>
<div ngbDropdownMenu aria-labelledby="adminDropdownMenu">
<button *ngIf="(currentSession | async)?.authorities.includes('BPMN.ADMIN.PROCESS_INSTANCE.LIST')"
routerLink="/process/instance" ngbDropdownItem>
Экземпляры процессов
</button>
<button *ngIf="(currentSession | async)?.authorities.includes('USER_MANAGEMENT.USER.LIST')"
routerLink="/user-management/users" ngbDropdownItem>
Пользователи
</button>
<button *ngIf="(currentSession | async)?.authorities.includes('USER_MANAGEMENT.GROUP.LIST')"
routerLink="/user-management/groups" ngbDropdownItem>
Группы
</button>
<button *ngIf="(currentSession | async)?.authorities.includes('USER_MANAGEMENT.ROLE.LIST')"
routerLink="/user-management/roles" ngbDropdownItem>
Роли
</button>
<button *ngIf="(currentSession | async)?.authorities.includes('USER_MANAGEMENT.ORG_UNIT.LIST')"
routerLink="/user-management/org-units" ngbDropdownItem>
Организации
</button>
<button *ngIf="(currentSession | async)?.authorities.includes('USER_MANAGEMENT.AUTHORITY.LIST')"
routerLink="/user-management/authorities" ngbDropdownItem>
Безопасность действий
</button>
</div>
</div>

View file

@ -0,0 +1,10 @@
<footer>
<div id="webbpm-footer">
<div class="fl-left"><span>Web-BPM</span></div>
<div class="fl-right">
<span><a href="mailto:info@micord.ru">Поддержка</a></span>
<span><a href="http://www.micord.ru" target="_blank">Компания "Микорд"</a></span>
<span><application-version></application-version></span>
</div>
</div>
</footer>

View file

@ -0,0 +1,13 @@
<nav class="header" *ngIf="currentSession | async as session" id="webbpm-header">
<div class="header-logo">
<div class="logo"><a routerLink="/"></a></div>
</div>
<div class="header-menu">
<process *ngIf="session.authorities.includes('BPMN.USER.START_PROCESS')"></process>
<div *ngIf="session.authorities.includes('BPMN.USER.TASK_LIST')">
<button class="nav-link bi bi-file-text-fill" (click)="openTaskList()" title="Задачи"></button>
</div>
<admin-menu [placement]="'bottom'"></admin-menu>
<div ngbDropdown class="logout" log-out></div>
</div>
</nav>

View file

@ -0,0 +1 @@
<span id="version-footer">Версия: {{applicationVersion}}</span>

View file

@ -0,0 +1,27 @@
<div class="confirm">
<div class="form-logo">
<div></div>
</div>
<div class="info">
<div>
<h2>Подтверждение почты</h2>
<div *ngIf="verificationStatus.toString() === 'VERIFYING'">
Подтверждение...
</div>
<div *ngIf="verificationStatus.toString() === 'VERIFIED'">
<div class="alert alert-success">
Адрес электронной почты успешно подтвержден
</div>
</div>
<div *ngIf="verificationStatus.toString() === 'FAILED'">
<div class="alert alert-danger">{{ errorMessage }}</div>
</div>
<div *ngIf="(currentSession | async) == null">
<a href="#/login"><span class="fa fa-lock"></span>Войти</a><br/>
<a href="#/registration">Зарегистрироваться</a>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,9 @@
<button class="nav-link bi bi-person-fill" ngbDropdownToggle title="Пользователь"></button>
<div ngbDropdownMenu *ngIf="currentSession | async as session">
<div class="user-info">
<div class="user-fio">{{session.fullUserName}}</div>
<div class="user-login">{{session.username}}</div>
<div class="user-department">{{getOrgUnitName()}}</div>
</div>
<button ngbDropdownItem *ngIf="isLogoutButtonVisible()" (click)="logout()">Выход</button>
</div>

View file

@ -0,0 +1,51 @@
<div class="form-signin">
<form #formComponent="ngForm">
<div class="alert alert-success" [hidden]="!confirmationSent">На ваш почтовый адрес было
отправлено письмо. Подтвердите почту, чтобы войти в систему
</div>
<div class="alert alert-danger" [hidden]="!errorMessage">{{errorMessage}}</div>
<h2>Вход</h2>
<div class="row registration">
<span>Еще нет аккаунта?</span><a href="#/registration">Зарегистрироваться</a>
</div>
<div class="row">
<label>Пользователь</label>
<div class="input-group">
<input type="text" name="username" class="form-control" placeholder="Пользователь" required autofocus
[(ngModel)]="username" maxlength="100">
</div>
</div>
<div class="row">
<label>Пароль</label>
<div class="input-group">
<input
[type]="passwordType ? 'text' : 'password'"
name="password"
class="form-control field-password-view"
placeholder="" required
[(ngModel)]="password"
maxlength="100"
>
<div class="input-group-append">
<span class="input-group-text">
<i
(click)="togglePasswordType()"
class="fa"
[ngClass]="{
'fa-eye': passwordType,
'fa-eye-slash': !passwordType
}"
></i>
</span>
</div>
</div>
</div>
<div class="login-btn-box">
<!--<esia-login-button></esia-login-button>-->
<button type="submit" class="btn btn-primary" (click)="formComponent.form.valid && login()">Войти</button>
<div class="password"><a href="#/reset-password">Забыли пароль?</a></div>
</div>
</form>
</div>

View file

@ -0,0 +1,103 @@
<div class="form-signup">
<div class="form-logo">
<div></div>
</div>
<div class="form-new-password">
<form #formComponent="ngForm">
<div [hidden]="!errorMessage" class="alert alert-danger">{{ errorMessage }}</div>
<p class="has-account">Вспомнили пароль?
<a href="#/login"><span class="fa fa-lock"></span>Войти</a></p>
<p class="has-account">Задайте новый пароль</p>
<div class="row">
<label>Пароль</label>
<div class="input-group">
<input
#passwordInput="ngModel"
[(ngModel)]="password"
[type]="passwordType ? 'text' : 'password'"
class="form-control"
maxlength="32"
minlength="6"
name="password"
pattern="^(?=.*[a-zA-Z])(?=.*[0-9])[a-zA-Z0-9]+$"
required
(change)="validPasswords()"
>
<div class="input-group-append">
<span class="input-group-text">
<i
(click)="togglePasswordType()"
class="fa"
[ngClass]="{
'fa-eye': passwordType,
'fa-eye-slash': !passwordType
}"
></i>
</span>
</div>
</div>
<div *ngIf="passwordInput.invalid && (passwordInput.dirty || passwordInput.touched)">
<div *ngIf="passwordInput.errors.required" class="msg-alert">Поле обязательно
</div>
<div *ngIf="passwordInput.errors.minlength" class="msg-alert">Пароль должен
содержать как минимум 6 символов
</div>
<div *ngIf="passwordInput.errors.pattern" class="msg-alert">Пароль должен
содержать заглавные и прописные буквы и как минимум 1 цифру
</div>
</div>
</div>
<div class="row">
<label>Подтверждение пароля</label>
<div class="input-group">
<input
#confirmPasswordInput="ngModel"
[(ngModel)]="confirmPassword"
[type]="confirmPasswordType ? 'text' : 'password'"
class="form-control"
maxlength="32"
minlength="6"
name="confirmPassword"
pattern="^(?=.*[a-zA-Z])(?=.*[0-9])[a-zA-Z0-9]+$"
required
(change)="validPasswords()"
>
<div class="input-group-append">
<span class="input-group-text">
<i
(click)="toggleConfirmPasswordType()"
class="fa"
[ngClass]="{
'fa-eye': confirmPasswordType,
'fa-eye-slash': !confirmPasswordType
}"
></i>
</span>
</div>
</div>
<div *ngIf="confirmPasswordInput.invalid && (confirmPasswordInput.dirty || confirmPasswordInput.touched)">
<div *ngIf="confirmPasswordInput.errors.required" class="msg-alert">Поле обязательно
</div>
<div *ngIf="confirmPasswordInput.errors.minlength" class="msg-alert">Пароль должен
содержать как минимум 6 символов
</div>
<div *ngIf="confirmPasswordInput.errors.pattern" class="msg-alert">Пароль должен
содержать заглавные и прописные буквы и как минимум 1 цифру
</div>
</div>
</div>
<div class="reset-password-btn-box">
<button
(click)="formComponent.form.valid && validPasswords() && changePassword()"
[disabled]="!formComponent.form.valid && !validPasswords()"
class="btn btn-primary"
type="submit"
>
Изменить пароль
</button>
</div>
</form>
</div>
</div>

View file

@ -0,0 +1,12 @@
<div ngbDropdown class="nav-item">
<button class="nav-link bi bi-clipboard-plus-fill" id="startProcessDropdownMenu" ngbDropdownToggle title="Создать"></button>
<div ngbDropdownMenu aria-labelledby="startProcessDropdownMenu">
<div class="dropdown-menu-inner">
<div *ngFor="let process of processList">
<button (click)="startProcess(process.processDefId)" ngbDropdownItem>
{{ process.name }}
</button>
</div>
</div>
<div>
</div><!-- TODO: move to directive or something else -->

View file

@ -0,0 +1,3 @@
<div class="modal-body">
<div class="progress"></div>
</div>

View file

@ -0,0 +1,121 @@
<div class="form-signup">
<div class="form-logo">
<div></div>
</div>
<div class="form-register">
<form #formComponent="ngForm">
<div [hidden]="!errorMessage" class="alert alert-danger">{{ errorMessage }}</div>
<h2>Регистрация</h2>
<p class="has-account">Уже зарегистрированы?
<a href="#/login"><span class="fa fa-lock"></span>Войти</a></p>
<div class="row">
<label>Имя</label>
<input
#name="ngModel"
[(ngModel)]="username"
class="form-control"
maxlength="100"
name="username"
required
type="text"
>
<div *ngIf="name.invalid && (name.dirty || name.touched)">
<div *ngIf="name.errors.required" class="msg-alert">Поле обязательно</div>
</div>
</div>
<div class="row">
<label>Адрес эл. почты</label>
<input
#emailInput="ngModel"
[(ngModel)]="email"
class="form-control"
email
maxlength="100"
name="email"
required
type="email"
>
<div *ngIf="emailInput.invalid && (emailInput.dirty || emailInput.touched)">
<div *ngIf="emailInput.errors.required" class="msg-alert">Поле обязательно</div>
<div *ngIf="emailInput.errors.email" class="msg-alert">Неверный формат адреса
эл. почты
</div>
</div>
</div>
<div class="row">
<label>Номер телефона</label>
<international-phone-number
#phoneInput="ngModel"
[(ngModel)]="phoneNumber"
[defaultCountry]="'ru'"
[pattern]="'^\\+(?!7 ?\\d{11})[0-9 ]+$'"
maxlength="20"
minlength="8"
name="phoneNumber"
placeholder="+79991112233"
(focusout)="phoneInputFocusOut()"
required
></international-phone-number>
<div *ngIf="phoneInput.invalid && (phoneInput.dirty || phoneIsTouched)">
<div *ngIf="phone.selectedCountry">
<div *ngIf="phoneHasOnlyDialCode()" class="msg-alert">Поле обязательно</div>
<div *ngIf="!phoneHasOnlyDialCode()" class="msg-alert">Введите корректный номер</div>
</div>
<div *ngIf="!phone.selectedCountry">
<div *ngIf="phoneInput.errors.required" class="msg-alert">Поле обязательно</div>
<div *ngIf="!phoneInput.errors.required" class="msg-alert">Введите код страны</div>
</div>
</div>
</div>
<div class="row">
<label>Пароль</label>
<div class="input-group">
<input
#passwordInput="ngModel"
[(ngModel)]="password"
[type]="fieldType ? 'text' : 'password'"
class="form-control"
maxlength="32"
minlength="8"
name="password"
[pattern]="passwordPattern"
required
>
<div class="input-group-append">
<span class="input-group-text">
<i
(click)="toggleFieldType()"
class="fa"
[ngClass]="{
'fa-eye': fieldType,
'fa-eye-slash': !fieldType
}"
></i>
</span>
</div>
</div>
<div *ngIf="passwordInput.invalid && (passwordInput.dirty || passwordInput.touched)">
<div *ngIf="passwordInput.errors.required" class="msg-alert">Поле обязательно
</div>
<div *ngIf="passwordInput.errors.minlength" class="msg-alert">Пароль должен
содержать как минимум 8 символов
</div>
<div *ngIf="passwordInput.errors.pattern" class="msg-alert" [innerText]="passwordPatternErrorMessage">
</div>
</div>
</div>
<div class="register-btn-box">
<button
(click)="formComponent.form.valid && register()"
[disabled]="!formComponent.form.valid"
class="btn btn-primary"
type="submit"
>
Зарегистрироваться
</button>
</div>
<div *ngIf="consent" [innerHTML]="consent" class="consent"></div>
</form>
</div>
</div>

View file

@ -0,0 +1,45 @@
<div class="form-signup">
<div class="form-logo">
<div></div>
</div>
<div class="form-reset-password">
<form #formComponent="ngForm">
<div [hidden]="!errorMessage" class="alert alert-danger">{{ errorMessage }}</div>
<p class="has-account">Вспомнили пароль?
<a href="#/login"><span class="fa fa-lock"></span>Войти</a></p>
<p class="has-account">Укажите адрес эл. почты, который был указан при регистрации,
на него пришлем временный пароль. Пароль сможете поменять в личном кабинете.
</p>
<div class="row">
<label>Адрес эл. почты</label>
<input
#emailInput="ngModel"
[(ngModel)]="email"
class="form-control"
email
maxlength="100"
name="email"
required
type="email"
>
<div *ngIf="emailInput.invalid && (emailInput.dirty || emailInput.touched)">
<div *ngIf="emailInput.errors.required" class="msg-alert">Поле обязательно</div>
<div *ngIf="emailInput.errors.email" class="msg-alert">Неверный формат адреса эл. почты
</div>
</div>
</div>
<div class="reset-password-btn-box">
<button
(click)="formComponent.form.valid && resetPassword()"
[disabled]="!formComponent.form.valid"
class="btn btn-primary"
type="submit"
>
Восстановить
</button>
</div>
</form>
</div>
</div>

View file

@ -0,0 +1,3 @@
<div id="page">
<router-outlet></router-outlet>
</div>

View file

@ -0,0 +1,49 @@
<div class="inner">
<div class="task-list">
<div class="task-list-tree-panel">
<div class="task-list-filter">
<ul>
<li>Фильтры</li>
<li>
<label>
<input type="radio" name="task-filter" value="All" [(ngModel)]="showMode" (ngModelChange)="filterVisibleTasks()">
<span>Все [{{tasks.length}}]</span>
</label></li>
<li class="ontime">
<label>
<input type="radio" name="task-filter" value="OnTime" [(ngModel)]="showMode" (ngModelChange)="filterVisibleTasks()">
<span><div></div>Без превышения срока [{{onTimeTasks.length}}]</span>
</label></li>
<li class="overdue">
<label>
<input type="radio" name="task-filter" value="Overdue" [(ngModel)]="showMode" (ngModelChange)="filterVisibleTasks()">
<span><div></div>С превышением срока [{{overdueTasks.length}}]</span>
</label></li>
</ul>
</div>
</div>
<div class="task-list-workplace">
<div class="alert alert-danger" [hidden]="!errorMessage">{{errorMessage}}</div>
<div class="table task-tbl">
<div class="thead">
<div class="tr">
<div class="th">Процесс</div>
<div class="th">Версия</div>
<div class="th">Задача</div>
<div class="th">Дата создания</div>
<div class="th">Срок</div>
</div>
</div>
<div *ngFor="let task of visibleTasks"
class="tr" [ngClass]="{'task-overdue': isOverdue(task), 'task-ontime': isOnTime(task)}"
(click)="startTask(task)">
<div class="td">{{ task.processName }}</div>
<div class="td">{{ task.processVersion }}</div>
<div class="td task">{{ task.name }}</div>
<div class="td">{{ task.createdOn | date:'dd.MM.yyyy HH:mm:ss' }}</div>
<div class="td">{{ task.expirationTime | date:'dd.MM.yyyy HH:mm:ss' }}</div>
</div>
</div>
</div>
</div>
</div>

View file

@ -0,0 +1,8 @@
<div class="task-not-found-page">
<div class="task-not-found-container">
<div>:(</div>
<div><h2>Ошибка</h2>
Данная задача не существует.<br/>
Перейти к <a (click)="goToTaskList()" tabindex>списку задач</a></div>
</div>
</div>

View file

@ -0,0 +1,3 @@
<div id="page">
<router-outlet></router-outlet>
</div>

View file

@ -0,0 +1 @@
<router-outlet></router-outlet>

View file

@ -0,0 +1,6 @@
<div id="home">
<div class="inner">
<div class="task-list">
</div>
</div>
</div>

View file

@ -0,0 +1 @@
<div #pageContent></div>

View file

@ -0,0 +1,12 @@
<div class="wrapper">
<app-header *ngIf="headerVisible">
</app-header>
<div class="container">
<div class="container-inside" id="webbpm-angular-application-container">
<router-outlet></router-outlet>
</div>
</div>
<app-footer *ngIf="footerVisible">
</app-footer>
</div>

View file

@ -0,0 +1,8 @@
import "../../src/resources/css/style.css";
import {platformBrowser} from '@angular/platform-browser';
import {enableProdMode} from "@angular/core";
import {WebbpmModuleNgFactory} from "./modules/webbpm/webbpm.module.ngfactory";
window['dev_mode'] = false;
enableProdMode();
platformBrowser().bootstrapModuleFactory(WebbpmModuleNgFactory);

7
frontend/src/ts/main.ts Normal file
View file

@ -0,0 +1,7 @@
import {platformBrowserDynamic} from "@angular/platform-browser-dynamic";
import {enableProdMode} from "@angular/core";
import {WebbpmModule} from "./modules/webbpm/webbpm.module";
window['dev_mode'] = true;
enableProdMode();
platformBrowserDynamic().bootstrapModule(WebbpmModule);

View file

@ -0,0 +1,72 @@
import {NgModule} from "@angular/core";
import {RouterModule, Routes} from "@angular/router";
import {AccessDeniedComponent} from "./component/access-denied.component";
import {LoginComponent} from "./component/login.component";
import {AuthenticationGuard, ConfirmExitGuard, SignedInGuard, ProcessInstanceRouteResolver} from "@webbpm/base-package";
import {RegisterComponent} from "./component/register.component";
import {ConfirmUserEmailComponent} from "./component/confirm-user-email.component";
import {ResetPasswordComponent} from "./component/reset-password.component";
import {NewPasswordComponent} from "./component/new-password.component";
import {TaskListComponent} from "./component/task-list.component";
const appRoutes: Routes = [
{
path: 'login',
component: LoginComponent,
canActivate: [SignedInGuard]
},
{
path: 'access-denied',
component: AccessDeniedComponent,
canActivate: [AuthenticationGuard, ConfirmExitGuard]
},
{
path: 'registration',
component: RegisterComponent,
canActivate: [SignedInGuard]
},
{
path: 'confirm',
component: ConfirmUserEmailComponent
},
{
path: 'reset-password',
component: ResetPasswordComponent
},
{
path: 'new-password',
component: NewPasswordComponent
},
{
path: 'process',
canActivate: [AuthenticationGuard, ConfirmExitGuard],
children: [
{
path: 'instance',
loadChildren: 'generated-sources/page-process-instance-list.module#PageprocessinstancelistModule',
resolve: {
processInstanceId: ProcessInstanceRouteResolver
}
},
{
path: 'instance/:processInstanceId',
loadChildren: 'generated-sources/page-process-instance.module#PageprocessinstanceModule',
resolve: {
processInstanceId: ProcessInstanceRouteResolver
}
},
{
path: 'tasks',
component: TaskListComponent,
}
]
}
];
@NgModule({
imports: [RouterModule.forChild(appRoutes)],
exports: [RouterModule]
})
export class AppRoutingModule {
}

View file

@ -0,0 +1,80 @@
import {forwardRef, NgModule} from "@angular/core";
import {NgbModule} from "@ng-bootstrap/ng-bootstrap";
import {CommonModule, registerLocaleData} from "@angular/common";
import localeRu from '@angular/common/locales/ru';
import {FormsModule} from "@angular/forms";
import {AgGridModule} from "ag-grid-angular";
import {
BpmnModule,
ComponentsModule,
CoreModule,
ProgressIndicationService,
SecurityModule
} from "@webbpm/base-package";
import {AdminMenuComponent} from "./component/admin-menu.component";
import {AppHeaderComponent} from "./component/app-header.component";
import {AppFooterComponent} from "./component/app-footer.component";
import {LogOutComponent} from "./component/logout.component";
import {LoginComponent} from "./component/login.component";
import {AccessDeniedComponent} from "./component/access-denied.component";
import {ApplicationVersionComponent} from "./component/application-version.component";
import {RouterModule} from "@angular/router";
import {RegisterComponent} from "./component/register.component";
import {ConfirmUserEmailComponent} from "./component/confirm-user-email.component";
import {InternationalPhoneNumberModule} from "ngx-international-phone-number";
import {ResetPasswordComponent} from "./component/reset-password.component";
import {NewPasswordComponent} from "./component/new-password.component";
import {AppProgressIndicationComponent} from "./component/app-progress-indication.component";
import {AppProgressIndicationService} from "./service/app-progress-indication.service";
import {TaskListComponent} from "./component/task-list.component";
import {ProcessListComponent} from "./component/process-list.component";
import {TaskComponent} from "./component/task.component";
import {TaskNotFoundComponent} from "./component/task-not-found.component";
registerLocaleData(localeRu);
export const DIRECTIVES = [
forwardRef(() => AppHeaderComponent),
forwardRef(() => AppFooterComponent),
forwardRef(() => AdminMenuComponent),
forwardRef(() => ApplicationVersionComponent),
forwardRef(() => LogOutComponent),
forwardRef(() => LoginComponent),
forwardRef(() => AccessDeniedComponent),
forwardRef(() => RegisterComponent),
forwardRef(() => ConfirmUserEmailComponent),
forwardRef(() => ResetPasswordComponent),
forwardRef(() => NewPasswordComponent),
forwardRef(() => AppProgressIndicationComponent),
forwardRef(() => TaskListComponent),
forwardRef(() => ProcessListComponent),
forwardRef(() => TaskComponent),
forwardRef(() => TaskNotFoundComponent)
];
@NgModule({
imports: [
CommonModule,
FormsModule,
CoreModule,
NgbModule,
BpmnModule,
SecurityModule,
ComponentsModule,
AgGridModule,
RouterModule,
InternationalPhoneNumberModule
],
declarations: [
DIRECTIVES
],
exports: [
DIRECTIVES
],
providers: [
{ provide: ProgressIndicationService, useClass: AppProgressIndicationService }
],
bootstrap: [],
entryComponents: [AppProgressIndicationComponent, TaskNotFoundComponent]
})
export class AppModule {
}

View file

@ -0,0 +1,11 @@
import {ChangeDetectionStrategy, Component} from "@angular/core";
@Component({
moduleId: module.id,
selector: "access-denied",
templateUrl: "../../../../../src/resources/template/app/component/access_denied.html",
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AccessDeniedComponent {
}

View file

@ -0,0 +1,23 @@
import {ChangeDetectionStrategy, Component, Input} from "@angular/core";
import {UserService, Session} from "@webbpm/base-package";
import {NgbDropdownConfig, Placement} from "@ng-bootstrap/ng-bootstrap";
import {Observable} from "rxjs";
@Component({
moduleId: module.id,
selector: 'admin-menu',
templateUrl: '../../../../../src/resources/template/app/component/admin_menu.html',
providers: [NgbDropdownConfig],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AdminMenuComponent {
@Input()
public placement: Placement = 'bottom';
public currentSession: Observable<Session>;
constructor(protected userService: UserService, public config: NgbDropdownConfig) {
this.config.placement = this.placement;
this.currentSession = this.userService.getCurrentSession();
}
}

View file

@ -0,0 +1,9 @@
import {Component} from "@angular/core";
@Component({
moduleId: module.id,
selector: "app-footer",
templateUrl: "../../../../../src/resources/template/app/component/app_footer.html"
})
export class AppFooterComponent {
}

View file

@ -0,0 +1,24 @@
import {ChangeDetectionStrategy, Component} from "@angular/core";
import {Router} from "@angular/router";
import {UserService, Session} from "@webbpm/base-package";
import {Observable} from "rxjs";
@Component({
moduleId: module.id,
selector: "app-header",
templateUrl: "../../../../../src/resources/template/app/component/app_header.html",
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AppHeaderComponent {
public currentSession: Observable<Session>;
constructor(protected userService: UserService,
protected router: Router) {
this.currentSession = this.userService.getCurrentSession();
}
public openTaskList(): void {
this.router.navigateByUrl("/process/tasks");
}
}

View file

@ -0,0 +1,11 @@
import {ChangeDetectionStrategy, Component} from "@angular/core";
@Component({
moduleId: module.id,
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'progress-indication-dialog-content',
templateUrl: '../../../../../src/resources/template/app/component/progress-indication.html'
})
export class AppProgressIndicationComponent {
}

View file

@ -0,0 +1,24 @@
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input} from "@angular/core";
import {HttpClient} from "@angular/common/http";
@Component({
moduleId: module.id,
selector: "application-version",
templateUrl: "../../../../../src/resources/template/app/component/application_version.html",
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ApplicationVersionComponent {
@Input()
public applicationVersion: string;
constructor(private httpClient: HttpClient, private cd: ChangeDetectorRef) {
this.loadAppVersion(); //TODO: check version url
}
private loadAppVersion() {
this.httpClient.get("version").toPromise().then((version: any) => {
this.applicationVersion = version.number;
this.cd.markForCheck();
})
}
}

View file

@ -0,0 +1,51 @@
import {ActivatedRoute, Router} from "@angular/router";
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input} from "@angular/core";
import {Session, UserService} from "@webbpm/base-package";
import {Observable} from "rxjs";
enum VerificationStatus {
VERIFYING = "VERIFYING",
VERIFIED = "VERIFIED",
FAILED = "FAILED"
}
@Component({
moduleId: module.id,
selector: "confirm",
changeDetection: ChangeDetectionStrategy.OnPush,
templateUrl: "../../../../../src/resources/template/app/component/confirm-user-email.html"
})
export class ConfirmUserEmailComponent {
public verificationStatus = VerificationStatus.VERIFYING;
public currentSession: Observable<Session>;
@Input()
public errorMessage: string;
constructor(private router: Router, private userService: UserService,
private route: ActivatedRoute, private cd: ChangeDetectorRef) {
this.currentSession = this.userService.getCurrentSession();
}
ngOnInit() {
const link: string = this.route.snapshot.queryParamMap.get("link");
// remove link from url to prevent http referer leakage
this.router.navigate([], { relativeTo: this.route, replaceUrl: true });
this.userService.confirm(
link,
(reason) => {
this.verificationStatus = VerificationStatus.FAILED;
if (reason.status === 404) {
this.errorMessage = 'Ссылка недействительна. Требуется повторная регистрация.';
}
else {
this.errorMessage = 'Произошла ошибка, обратитесь в службу технической поддержки!';
}
this.cd.markForCheck();
})
.then(() => {
this.verificationStatus = VerificationStatus.VERIFIED;
this.cd.markForCheck();
});
}
}

View file

@ -0,0 +1,68 @@
import {Component, Input} from "@angular/core";
import {ActivatedRoute, Router} from "@angular/router";
import {UserService, Credentials} from "@webbpm/base-package";
@Component({
moduleId: module.id,
selector: "login",
templateUrl: "../../../../../src/resources/template/app/component/login.html"
})
export class LoginComponent {
@Input()
public username: string;
@Input()
public password: string;
public passwordType: boolean;
@Input()
public errorMessage: string;
@Input()
public confirmationSent: boolean;
constructor(private router: Router, private userService: UserService,
private route: ActivatedRoute) {
}
ngOnInit() {
this.confirmationSent = this.route.snapshot.queryParamMap.get('confirmationSent') === 'true';
this.router.navigate([], { relativeTo: this.route, replaceUrl: true });
}
public login(): void {
let credentials: Credentials = new Credentials();
credentials.username = this.username;
credentials.password = this.password;
this.userService.login(credentials, "Password")
.then(() => this.router.navigateByUrl("/"),
(reason: any) => {
switch (reason.status) {
case 401: {
this.errorMessage = "Неправильный логин или пароль";
break;
}
case 404: {
this.errorMessage = "Приложение стартует. Пожалуйста, подождите...";
break;
}
default: {
this.errorMessage =
"Произошла неизвестная ошибка, обратитесь в службу технической поддержки!";
break;
}
}
}
);
}
public goToRegister(): void {
this.router.navigateByUrl("/register");
}
togglePasswordType(): void {
this.passwordType = !this.passwordType;
}
}

View file

@ -0,0 +1,37 @@
import {Component} from "@angular/core";
import {UserService, Session, AuthenticationMethodService} from "@webbpm/base-package";
import {Observable} from "rxjs";
@Component({
moduleId: module.id,
selector: "[log-out]",
templateUrl: "../../../../../src/resources/template/app/component/log_out.html"
})
export class LogOutComponent {
public currentSession: Observable<Session>;
constructor(private userService: UserService, private authenticationMethodService: AuthenticationMethodService) {
this.currentSession = userService.getCurrentSession();
}
public logout(): void {
this.userService.logout();
}
public getCurrentUserName(): string {
return this.userService.getCurrentUserName();
}
public getFullUserName(): string {
return this.userService.getFullUserName();
}
public isLogoutButtonVisible(): boolean {
return this.authenticationMethodService.isFormAuth();
}
public getOrgUnitName(): string {
return this.userService.getOrgUnitName();
}
}

View file

@ -0,0 +1,79 @@
import {ActivatedRoute, Router} from "@angular/router";
import {Component, Input} from "@angular/core";
import {Session, UserPasswordResetRequestDto, UserService} from "@webbpm/base-package";
import {Observable} from "rxjs";
@Component({
moduleId: module.id,
selector: "newPassword",
templateUrl: "../../../../../src/resources/template/app/component/new_password.html"
})
export class NewPasswordComponent {
public currentSession: Observable<Session>;
private token: string;
@Input()
public password: string;
public passwordType: boolean;
@Input()
public confirmPassword: string;
public confirmPasswordType: boolean;
@Input()
public errorMessage: string;
constructor(private router: Router, private userService: UserService,
private route: ActivatedRoute) {
this.currentSession = this.userService.getCurrentSession();
}
ngOnInit() {
this.token = this.route.snapshot.queryParamMap.get("token");
this.router.navigate([], {relativeTo: this.route, replaceUrl: true});
if (this.token == undefined || this.token === '') {
this.errorMessage = 'Ссылка недействительна. Требуется повторить восстановление пароля.';
return;
}
}
public changePassword(): void {
let dto: UserPasswordResetRequestDto = new UserPasswordResetRequestDto();
dto.password = this.password;
dto.passwordConfirm = this.confirmPassword;
this.userService.changePassword(dto, this.token)
.then(() => this.router.navigateByUrl("/"),
() => {
this.errorMessage =
'Произошла неизвестная ошибка, обратитесь в службу технической поддержки!';
});
}
togglePasswordType(): void {
this.passwordType = !this.passwordType;
}
toggleConfirmPasswordType(): void {
this.confirmPasswordType = !this.confirmPasswordType;
}
validPasswords(): boolean {
if (this.password === undefined || this.confirmPassword === undefined) {
return false;
}
let eq = this.password === this.confirmPassword;
if (!eq) {
this.errorMessage = 'Введенные пароли не совпадают. Убедитесь, что данные, ' +
'введенные в поле "Подтверждение пароля", совпадают с теми, ' +
'которые указаны в поле "Пароль".';
}
else {
this.errorMessage = '';
}
return eq;
}
}

View file

@ -0,0 +1,44 @@
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input} from "@angular/core";
import {Process, TaskService, ProcessDefinitionResource, ProcessService} from "@webbpm/base-package";
@Component({
moduleId: module.id,
selector: 'process',
templateUrl: '../../../../../src/resources/template/app/component/process_list.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ProcessListComponent {
@Input()
public processList: Process[];
@Input()
public errorMessage: string;
constructor(private processDefinitionResource: ProcessDefinitionResource,
private taskService: TaskService,
private cd: ChangeDetectorRef,
private processService: ProcessService) {
this.processList = [];
this.loadProcessList();
}
loadProcessList() {
this.processDefinitionResource.list().then(
(processList) => {
this.processList = processList;
this.cd.markForCheck();
},
() => {
this.errorMessage = "Error load process list";
this.cd.markForCheck();
})
}
startProcess(processDefinitionId: string) {
this.processService.start(processDefinitionId, null).then(
(processInstanceId: number) => {
this.taskService.startAndOpenNextTask(processInstanceId);
}
);
}
}

View file

@ -0,0 +1,71 @@
import {Component, Input, ViewChild} from "@angular/core";
import {AppConfigService, UserDto, UserService} from "@webbpm/base-package";
import {Router} from "@angular/router";
import {PhoneNumberComponent} from "ngx-international-phone-number";
@Component({
moduleId: module.id,
selector: "register",
templateUrl: "../../../../../src/resources/template/app/component/register.html"
})
export class RegisterComponent {
public passwordPattern: string;
public passwordPatternErrorMessage: string;
public errorMessage: string;
@Input()
public username: string;
@Input()
public email: string;
@ViewChild(PhoneNumberComponent)
public phone: PhoneNumberComponent;
public phoneNumber: string;
public phoneIsTouched: boolean = false;
@Input()
public password: string;
public fieldType: boolean;
@Input()
public consent: string;
constructor(private router: Router, private userService: UserService,private appConfigService: AppConfigService) {
this.passwordPattern = appConfigService.getParamValue("password_pattern");
this.passwordPatternErrorMessage = appConfigService.getParamValue("password_pattern_error");
}
public register(): void {
let user: UserDto = new UserDto();
user.username = this.username;
user.email = this.email;
user.name = this.username;
user.phone = this.phone.value;
user.password = this.password;
this.userService.register(user)
.then(() => this.router.navigateByUrl("/login?confirmationSent=true"),
(reason: any) => {
if (reason.status === 409) {
this.errorMessage = 'Пользователь с данным почтовым адресом уже существует';
}
else {
this.errorMessage = 'Произошла неизвестная ошибка, обратитесь в службу технической поддержки!';
}
});
}
toggleFieldType(): void {
this.fieldType = !this.fieldType;
}
phoneHasOnlyDialCode(): boolean {
return this.phone.phoneNumber.trim() === this.phone.getSelectedCountryDialCode().trim()
}
phoneInputFocusOut(): void {
this.phoneIsTouched = true;
}
}

View file

@ -0,0 +1,30 @@
import {Component, Input} from "@angular/core";
import {UserService} from "@webbpm/base-package";
import {Router} from "@angular/router";
@Component({
moduleId: module.id,
selector: "resetPassword",
templateUrl: "../../../../../src/resources/template/app/component/reset_password.html"
})
export class ResetPasswordComponent {
@Input()
public email: string;
@Input()
public errorMessage: string;
constructor(private router: Router, private userService: UserService) {
}
resetPassword(): void {
this.userService.resetPassword(this.email)
.then(() => this.router.navigateByUrl("/"),
(reason: any) => {
this.errorMessage =
'Произошла неизвестная ошибка, обратитесь в службу технической поддержки!';
});
}
}

View file

@ -0,0 +1,121 @@
import {Component, Input} from "@angular/core";
import {Task, TaskService, TaskStatus, TaskReference, TaskResource, ProcessVariable, UserService, RolesService, ProcessService} from "@webbpm/base-package";
@Component({
moduleId: module.id,
selector: "task-list",
templateUrl: "../../../../../src/resources/template/app/component/task_list.html"
})
export class TaskListComponent {
@Input()
public tasks: Task[];
@Input()
public onTimeTasks: Task[];
@Input()
public overdueTasks: Task[];
@Input()
public visibleTasks: Task[];
@Input()
public showMode: string;
@Input()
public errorMessage: string;
@Input()
public roles: string[];
constructor(private taskService: TaskService,
private $taskResource: TaskResource,
private userService: UserService,
private rolesService: RolesService,
private processService: ProcessService) {
this.tasks = [];
this.onTimeTasks = [];
this.overdueTasks = [];
this.visibleTasks = [];
this.roles = [];
this.showMode = 'All';
this.rolesService.getRoles().then((roles: string[]) => {
this.roles = roles;
});
this.loadTasks();
}
private loadTasks(): void {
this.$taskResource.list()
.then(
(tasks: Task[]) => this.initTasks(tasks),
() => this.errorMessage = "Error load tasks"
);
}
public startTask(task: Task): Promise<any> {
let taskRef: TaskReference = new TaskReference();
taskRef.processInstanceId = task.processInstanceId;
taskRef.taskId = task.id;
if (task.status == TaskStatus.InProgress) {
return this.taskService.openTask(taskRef);
}
else if (task.status == TaskStatus.Reserved || task.status == TaskStatus.Ready) {
return this.taskService.startAndOpenTask(taskRef);
}
}
private initTasks(tasks: Task[]): void {
this.tasks = tasks;
this.filterOnTimeTasks();
this.filterOverdueTasks();
this.filterVisibleTasks();
}
public isVisible(task: Task): boolean {
return this.showMode == 'All' ||
this.showMode == 'OnTime' && !this.isOverdue(task) ||
this.showMode == 'Overdue' && this.isOverdue(task);
}
public isOverdue(task: Task): boolean {
if (!task.expirationTime) {
return false;
}
return new Date(task.expirationTime).getTime() <= new Date().getTime();
}
public isOnTime(task: Task): boolean {
return !this.isOverdue(task);
}
public startProcess(processDefinitionId: string, processVars?: Array<ProcessVariable | any>) {
this.processService.start(processDefinitionId, processVars).then(
(processInstanceId: number) => {
this.taskService.startAndOpenNextTask(processInstanceId);
}
);
}
public filterOnTimeTasks() {
this.onTimeTasks = this.tasks
.filter((task: Task) => this.isOnTime(task));
}
public filterOverdueTasks() {
this.overdueTasks = this.tasks
.filter((task: Task) => this.isOverdue(task));
}
public filterVisibleTasks() {
this.visibleTasks = this.tasks
.filter((task: Task) => this.isVisible(task));
}
public hasRole(role: string) {
return this.roles.includes(role);
}
}

View file

@ -0,0 +1,18 @@
import {ChangeDetectionStrategy, Component} from "@angular/core";
import {Router} from "@angular/router";
@Component({
moduleId: module.id,
selector: 'task-not-found',
templateUrl: '../../../../../src/resources/template/app/component/task_not_found.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class TaskNotFoundComponent {
constructor(private router: Router) {
}
goToTaskList() {
return this.router.navigateByUrl("/process/tasks");
}
}

View file

@ -0,0 +1,9 @@
import {Component} from "@angular/core";
@Component({
moduleId: module.id,
selector: "[task]",
templateUrl: "../../../../../src/resources/template/app/component/task.html"
})
export class TaskComponent {
}

View file

@ -0,0 +1,298 @@
import {Injectable, OnDestroy} from "@angular/core";
import {HttpClient} from "@angular/common/http";
import {UserService, AppConfigService, Session} from "@webbpm/base-package";
import {
JivoProfileDto
} from "../../../generated/dto/jivoprofile/JivoProfileDto";
import {Observable, Subscription} from "rxjs";
declare function jivo_InitProfile(name, email, phone);
declare function jivo_ClearHistory();
@Injectable()
export class AppJivoChatWidgetService implements OnDestroy {
public static LIVE_CHAT_WIDGET_API_URL: string = "jivo_chat_widget_api_url";
public static LIVE_CHAT_WIDGET_ENABLE: string = "jivo_chat_widget_enabled";
public static LIVE_CHAT_WIDGET_DEFAULT_VALUE: boolean = false;
private JIVO_CSS: string = `
/* hide the original widget - that there were no two labels on the screen*/
#jivo_chat_widget{
display: none;
}
/* the default style - for offline messages if no one is online */
#jivo_custom_widget{
position: fixed;
z-index: 300000;
cursor: pointer;
display: block;
right: 60px;
bottom: 10px;
width: 60px;
height: 60px;
background-image: url('data:image/png;base64,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');
background-size: 60px 60px;
background-repeat: no-repeat;
}
#jivo_custom_widget:hover{
bottom: 13px;
}
/* if there are operators online - show other label*/
#jivo_custom_widget.jivo_online{
height: 60px;
width: 60px;
background-size: 60px 60px;
background-repeat: no-repeat;
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAGQAAABkCAYAAABw4pVUAAAABmJLR0QA/wD/AP+gvaeTAAAI/ElEQVR4nO2cbXBU1RnH/8+5m2wgAoLYZDcJbHZj2wGsOMzIlFYFx1K0hal0ygdnqtNOlbYfmJpkE8SXWYug7CaIzPiBtjNtx9FBnFoS6ehYUcCClharCB3B3Wxe7yaAYG0DSfbe8/RDsBNSAvfe3Lt52fP7kmT3f57nufe/59yce88eQKFQKBQKhUKhUCgUCoVCoVAoFAqFQqFQKBTjBBrrAiyzi7WKdEdJAXxBFhwQLGZCcBEDU8BcBAAg6iPgAiT1SZLnSFImC0PvqKzowRoyx/gILDEuDZmb6K70wVwMphslYT4YNxJhLgDNYUiTGW0gfCQYxwE+aprir60PB1rdrNsNxoUhVZsz10ufXElESxlYCqAiR6nbCdjP4LdFVuxJbgiczlHeERkzQyq39ZSIAWONJFpNwK1w/ul3CxOEA8z8ioC2KxUtPTUWReTckEiDvkhKPEiE+wAU5Tq/RQaI0MTMv2qpDe4FEecqcW4MYaZwIrMaRI8D/LWc5HSPDwl4IlUb2J0LYzw3pLKxaxVJsXECGjGcD8Hi0Za60j1eJvHMkKotneVSiG0Avu9VjjGBeI/J2rq2aGnai/DC9YjMVNmQqZFCnMBkMwMAmL6rQR4LJ/RqMLv+gXY14Jcb9NkG43cAvuNm3HHMm5J9P2yt+1K3WwFdMyTSoC8CUzODg27FnCB0CcErkzVl/3AjmCtDViTRfQcz3spDMwCgTEo6EInrK9wINmpDIgn9HoZ8DcB0F+qZqFzDhKbKxq5Vow00qiErFM/cLohfx/id4OWaPmYsT9cF33EawLEhVQ2ZeZL5EIAZTmNMUs5JwpLW2uDHTho7GrKqtn/il8wvQplxOWYKxsuhWNrRqOHIELO/OA7gJidt84QFotj/lJOGtoesULxzoSBxBF5MKicXkgi3pGqDR+w0sn1SBYmEk3Z5iGAgYbuRHXEk0X0HgDvtJslbGMtC8cztdprYMkQSP2ivIoUAP2BHb/kaUr61Y1ahqXVBzTns0mcY2WD7w3PPWRFb7iF+UyyHt2ZkwfxLIWXFlN5zfpa0GMCBK+i7QXyv8PtnFMiC6cy0BkDXSGJifo8Ef0P4e4uy0ihj4FEA/W4fxGUo0nyF37Iq9lkVShJLiL17YCbAP0jWlTUBAGIsUE+HEeNl4eLu3QCvHKpl4FNDGos66ufoYCY8AUKMXq7anNknC/gogNJh4d9MhYIrsIZMxFggRjqATZVx/W9EeB0eP6gj5iUAdlnRWu4hBF7suKKrxWZqTkbLmiKNXRXhhP5BuDjTF47rWxEj6dOMnwEwh+kf66ifo4fjmbvDDZlT4eLMmXCDvjq5IXCaiNYPC8+CtZ9iDZmRhP5kuDhzIZzQj1fFeyLpuuAbAL3k1XH9r14b5866IUzlzsq5Okz8NgCwpGoMTjgLQHio6unM/JPVFV0ATgzVm8z7LhbVCGA2gJlgbAMANgZjDaE9WVeSCj2VCTHwCIBCAPOYzHoAIPo/veswkeVlTZYNYfB1zsqxFN1/8ee/hrxomj7+z8Xf/UPVRPKLv4fqB38X5iXaL9pqPjoPIDsk56Be8nC9F1g+d5YMWbSDCzDspLgJMb6HGIsCWdjIwO8BHAH4gXRtsC0U71wIIDJUL0D3AABB/ARMewnYLxj3DwYTq4eFL61KdH49FS09BeL7AfwdwItZw9gMZmIxGMtjii6ew6ti+WIWTuifwdubiRtaosFL7v+EnklfS4b/LQJuHqbtl0Isba0pfW/oi1WNXTdLSX8BMHWY/p+FWXnbxxvKPx36YiShr2PgWdeOYGR6W6LBa6wI7RhyEsANjkuyADE1M+jXknBag1zIwGMAykaQZxm8jVn8iQQzMVYAqMbIPbkbRBsl0fvC5Fkg/jFytwijrSUaDFkRWjYkktD3MWDrNoBiEAb2paPBZVa0li/qkuH4KVi+I4CDNrSWlX92VI0CDHrXqtayIbOmBd4F8LmjivKbs7K3b69VsWVDjqylLIDfOCopj2GiF1pjlX1W9fYeNJl4FpdMrhRXQWoGdthpYMuQlvXBdgJ22qspf2Hg+eT6wHE7bWw/ijV8BfUALN3bz3MukInH7TaybUjbQ9dnAK6x2y7fIOb6lvXBdtvtHGVjpnBD5lXkzyp3exDvaakJrnLyjStnq0eIWPj99wJ01FH7yc1JH+hHTr/+5ng5T3LddZ9nZfYuAB1OY0w2CKRLg759sjZ4xmmMUa2v6qifoxPE3QTSRxNnUsDokTDuGu1mBKNe8JaKlh7TNOOWPB++0oB5azpaMepz4NrD/YvLhJoAfNOtmBOEgwSx2q2NBlxbEtpZXXF2Tm9gGcDrkR+zeWZg+8zpgWVu7vrgyfKXSGPXEmnS80QIexF/HJBm4rXp2jLX74B7smg6VVN2aGqx7yYCNgE470WOMaKfGU8OaOZ8L8wAcrCTQ9WWznKp0UYw3YeJu2reYOAFjbWNybqSlJeJcrb5TCSur2DCa7nK5xL9DOwkNje11FV8kouElpeSjj6TmYQ51jswWYMZLQK8gwzx21zvoZUzQ9jQfj4+tksbkXYAr4Dwh3Rv4BBiJMeiiJycotAz6WuF4W8HMC0X+SzyGUDvMGG/AO9L1QTez+W+WCORkx6iGYWPsH0zTjLTdoBLiRAm5jCDKkEosRnnNBjdICQZOAamjyTzsbYLgROX9IJam1E9wvv/suI9EUnmcdhbinqMC7Q7078o6Rn+RjCmTy0qwmwUyCJiMY2YpjFhCksQSA5IaP/WSBoSOKMV9nYn192Qi++AuIbnPYQht8KeGR+ILC1PRksuezHVY8HzGBzvJyWezgsiCX0tE9vZ/+OPMIuWjofdQccKz4asSKJ7AUMeBjDFgryXmerTdYHnvKpnouBJDwk/fXYGQ74ES2bQTiHlV5UZg7h+DSnf2jEFZl8zgHlXkA0QoUma1JCuDxx2u4aJjKuGfGXL6WlZc2A3gNsu8/YpAAeJqbnfZzR3VlecdTP3ZMFVQ0zRvwAQrwL8BkBZYjrLmtkhB7TUeNxnXaFQKBQKhUKhUCgUCoVCoVAoFAqFQqFQKBSKK/Bf/RI0U2U6gOkAAAAASUVORK5CYII=');
}`;
private JIVO_JS: string = `
/* Callback function that is called immediately after JivoChat is loaded */
function jivo_onLoadCallback() {
window.jivo_cstm_widget = document.createElement('div');
jivo_cstm_widget.setAttribute('id', 'jivo_custom_widget');
document.body.appendChild(jivo_cstm_widget);
/* Adds handlers click on the icon - to maximize the window when clicked */
jivo_cstm_widget.onclick = function () {
jivo_api.open();
}
/* Change the CSS class if there are agents online */
if (jivo_config.chat_mode === "online") {
jivo_cstm_widget.setAttribute("class", "jivo_online");
}
/* Show the user a shortcut */
window.jivo_cstm_widget.style.display = 'block';
}
/*
Callback function jivo_onOpen and jivo_onClose called whenever the chat window JivoChat
is expanded or collapsed by the user or by the proactive invitations rule.
*/
function jivo_onOpen() {
/* If chat is deployed - hide shortcut */
if (jivo_cstm_widget)
jivo_cstm_widget.style.display = 'none';
}
function jivo_onClose() {
/* If chat is minimized - show label */
if (jivo_cstm_widget)
jivo_cstm_widget.style.display = 'block';
}
function jivo_ClearHistory() {
jivo_api.clearHistory();
jivo_api.close();
}
function jivo_InitProfile(name, email, phone) {
let chatMode = jivo_api.chatMode();
let args = {
name: name ? name : '',
email: email ? email : '',
phone: phone ? phone : ''
};
if (chatMode === 'offline') {
jivo_api.sendOfflineMessage(args);
}
else {
jivo_api.setContactInfo(args);
}
}
`;
private currentUserName: string;
private currentEmail: string;
private currentPhone: string;
private jsLoaded = false;
private jivoWidgetApiUrl: string;
private currentSession: Observable<Session>;
private currentSessionSubscription: Subscription;
private session: Session;
private sessionInitJivoChatWidget: boolean;
constructor(private httpClient: HttpClient,
private appConfigService: AppConfigService,
private userService: UserService) {
this.currentSession = userService.getCurrentSession();
this.currentSessionSubscription =
this.currentSession.subscribe(val => {
if (!this.sessionInitJivoChatWidget || !this.session) {
this.initJivoChatWidget();
this.sessionInitJivoChatWidget = true;
}
if (this.session && !val) {
// logout
this.clearJivoProfile();
this.session = null;
this.sessionInitJivoChatWidget = false;
}
if (val) {
this.session = val;
}
});
}
private initJivoChatWidget() {
let liveChatWidgetEnabled = this.getValueFromAppConfig(
AppJivoChatWidgetService.LIVE_CHAT_WIDGET_ENABLE,
AppJivoChatWidgetService.LIVE_CHAT_WIDGET_DEFAULT_VALUE);
if (liveChatWidgetEnabled) {
this.jivoWidgetApiUrl = this.getValueFromAppConfig(
AppJivoChatWidgetService.LIVE_CHAT_WIDGET_API_URL,
'');
if (!this.jivoWidgetApiUrl) {
throw new Error(
`The configuration file does not contain the 'Jivo API url'. Please check that '${AppJivoChatWidgetService.LIVE_CHAT_WIDGET_API_URL}' has data`);
}
let userId = this.userService.getCurrentUserId();
if (userId) {
this.clearJivoProfile();
this.httpClient.get('profile/jivo/' + userId)
.toPromise()
.then((profile: JivoProfileDto) => {
this.currentUserName = profile.username;
this.currentEmail = profile.email;
this.currentPhone = profile.phone;
let initProfileFunction = () => this.initProfile(this.currentUserName,
this.currentEmail,
this.currentPhone);
this.loadScripts(initProfileFunction);
});
}
else {
let initProfileFunction = () => this.initProfile(this.currentUserName, this.currentEmail,
this.currentPhone);
this.loadScripts(initProfileFunction);
}
}
}
private clearJivoProfile() {
this.clearProfileData();
try {
jivo_ClearHistory();
}
catch (ignore) {
}
}
private clearProfileData() {
this.currentUserName = '';
this.currentEmail = '';
this.currentPhone = '';
}
private loadScripts(initProfileFunction): void {
let afterLoadMainJs = () => {
this.loadScriptJsContent('jivoCustomJs', this.JIVO_JS, initProfileFunction);
}
this.loadScript('jivoWidget', this.jivoWidgetApiUrl, true, afterLoadMainJs);
this.loadStyleContent('jivoCustomCss', this.JIVO_CSS);
}
private initProfile(username: string, email: string, phone: string): void {
try {
jivo_InitProfile(username, email, phone);
}
catch (e) {
if (e instanceof ReferenceError) {
if (!this.jsLoaded) {
this.jsLoaded = true;
setTimeout(() => {
jivo_InitProfile(username, email, phone);
}, 3000);
}
}
}
}
private loadScript(elementId, js, async, callback): void {
let jivoJsLink = document.getElementById(elementId);
if (!jivoJsLink) {
let node = document.createElement('script');
node.src = js;
node.type = 'text/javascript';
node.id = elementId;
node.async = async;
node.charset = 'utf-8';
if (callback) {
node.onload = function () {
callback();
};
}
document.getElementsByTagName('head')[0].appendChild(node);
}
else {
if (callback) {
callback();
}
}
}
private loadScriptJsContent(elementId, js, callback): void {
let jivoJs = document.getElementById(elementId);
if (!jivoJs) {
let node = document.createElement('script');
node.innerHTML = js;
node.type = 'text/javascript';
node.id = elementId;
node.charset = 'utf-8';
if (callback) {
callback();
}
document.getElementsByTagName('head')[0].appendChild(node);
}
else {
if (callback) {
callback();
}
}
}
private loadStyleContent(elementId, styleCss): void {
let jivoCss = document.getElementById(elementId);
if (!jivoCss) {
const style = document.createElement('style');
style.id = elementId;
style.innerHTML = styleCss;
document.getElementsByTagName('head')[0].appendChild(style);
}
}
private getValueFromAppConfig(key, defaultValue) {
let enabled = this.appConfigService.getParamValue(key);
if (enabled === undefined) {
return defaultValue;
}
return enabled;
}
ngOnDestroy(): void {
this.currentSessionSubscription.unsubscribe();
}
}

View file

@ -0,0 +1,95 @@
import {Injectable} from "@angular/core";
import {AppProgressIndicationComponent} from "../component/app-progress-indication.component";
import {NgbModal, NgbModalOptions, NgbModalRef} from "@ng-bootstrap/ng-bootstrap";
@Injectable()
export class AppProgressIndicationService {
private static readonly EVENT_INTERCEPTOR = (event) => {
event.preventDefault();
event.stopPropagation();
};
private counter: number = 0;
private focused: any;
private ngbModalRef: NgbModalRef;
private options: NgbModalOptions = {
backdrop: 'static',
keyboard: false,
windowClass: 'modal-center loader'
};
constructor(private ngbModal: NgbModal) {
}
public showProgressBar(): boolean {
if (this.counter == 0) {
this.disableEvents();
this.saveFocus();
this.showProgressIndicator();
}
++this.counter;
return this.counter == 1;
}
public hideProgressBar(): boolean {
if (this.counter == 0) {
return false;
}
if (this.counter == 1) {
this.hideProgressIndicator();
this.restoreFocus();
this.enableEvents();
}
--this.counter;
return this.counter == 0;
}
public unconditionallyHideProgressBar(): void {
this.enableEvents();
this.hideProgressIndicator();
}
public restoreProgressBar(): void {
if (this.counter > 0 && this.ngbModalRef == null) {
this.disableEvents();
this.showProgressIndicator();
}
}
private showProgressIndicator() {
this.ngbModalRef = this.ngbModal.open(AppProgressIndicationComponent, this.options);
}
private hideProgressIndicator() {
this.ngbModalRef.dismiss('cancel');
this.ngbModalRef = null;
}
private saveFocus() {
this.focused = $(':focus');
}
private restoreFocus() {
if (this.focused) {
this.focused.focus();
this.focused = null;
}
}
private disableEvents() {
let body = $('body');
body.keydown(AppProgressIndicationService.EVENT_INTERCEPTOR);
body.keyup(AppProgressIndicationService.EVENT_INTERCEPTOR);
body.contextmenu(AppProgressIndicationService.EVENT_INTERCEPTOR)
}
private enableEvents() {
let body = $('body');
body.off('keydown', AppProgressIndicationService.EVENT_INTERCEPTOR);
body.off('keyup', AppProgressIndicationService.EVENT_INTERCEPTOR);
body.off('contextmenu', AppProgressIndicationService.EVENT_INTERCEPTOR);
}
}

View file

@ -0,0 +1,9 @@
import {Component} from "@angular/core";
@Component({
moduleId: module.id,
selector: "[preview-container]",
templateUrl: "../../../../../src/resources/template/preview/preview_container.html"
})
export class PreviewContainerComponent {
}

View file

@ -0,0 +1,9 @@
import {Component} from "@angular/core";
@Component({
moduleId: module.id,
selector: "[preview]",
templateUrl: "../../../../../src/resources/template/preview/preview.html"
})
export class PreviewComponent {
}

View file

@ -0,0 +1,20 @@
import {NgModule} from "@angular/core";
import {RouterModule, Routes} from "@angular/router";
import {PreviewComponent} from "./component/preview.component";
import {DYNAMIC_ROUTING} from "../../page.routing";
const previewRoutes: Routes = [
{
path: 'preview',
component: PreviewComponent,
children: DYNAMIC_ROUTING,
}
];
@NgModule({
imports: [RouterModule.forRoot(previewRoutes, {useHash: true})],
exports: [RouterModule]
})
export class PreviewRoutingModule {
}

View file

@ -0,0 +1,7 @@
import {platformBrowserDynamic} from "@angular/platform-browser-dynamic";
import {enableProdMode} from "@angular/core";
import {PreviewModule} from "./preview.module";
window['dev_mode'] = true;
enableProdMode();
platformBrowserDynamic().bootstrapModule(PreviewModule);

View file

@ -0,0 +1,53 @@
import {NgModule, NgZone} from "@angular/core";
import {FormsModule} from "@angular/forms";
import {BrowserModule} from "@angular/platform-browser";
import {AgGridModule} from "ag-grid-angular";
import {PreviewComponent} from "./component/preview.component";
import {PreviewRoutingModule} from "./preview-routing.module";
import {PreviewContainerComponent} from "./component/preview-container.component";
import {NgbModule} from "@ng-bootstrap/ng-bootstrap";
import {ToastNoAnimationModule} from "ngx-toastr";
import {AppModule} from "../app/app.module";
import {ComponentsModule, CoreModule, SecurityModule} from "@webbpm/base-package";
import {TaskParamsProvider} from "@webbpm/base-package";
import {HTTP_INTERCEPTORS} from "@angular/common/http";
import {HttpPreviewInterceptor} from "./service/http-preview-interceptor.service";
export const HTTP_INTERCEPTOR_PROVIDERS = [
{ provide: HTTP_INTERCEPTORS, useClass: HttpPreviewInterceptor, multi: true }
];
let IMPORTS = [
BrowserModule,
FormsModule,
NgbModule,
ToastNoAnimationModule.forRoot(),
AgGridModule,
CoreModule,
ComponentsModule,
AppModule,
SecurityModule,
PreviewRoutingModule
];
@NgModule({
imports: IMPORTS,
declarations: [
PreviewContainerComponent,
PreviewComponent
],
exports: [],
providers: [
TaskParamsProvider,
HTTP_INTERCEPTOR_PROVIDERS
],
bootstrap: [
PreviewContainerComponent
]
})
export class PreviewModule {
constructor(zone: NgZone) {
window['zoneImpl'] = zone;
}
}

View file

@ -0,0 +1,16 @@
import {Injectable} from "@angular/core";
import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from "@angular/common/http";
import {EMPTY, Observable} from "rxjs";
import {catchError} from "rxjs/operators";
@Injectable()
export class HttpPreviewInterceptor implements HttpInterceptor {
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
return next.handle(req).pipe(catchError(() => {
return EMPTY;
}
));
}
}

View file

@ -0,0 +1,9 @@
import {Component} from "@angular/core";
@Component({
moduleId: module.id,
selector: "home",
templateUrl: './../../../../../src/resources/template/webbpm/home.html'
})
export class HomeComponent {
}

View file

@ -0,0 +1,36 @@
import {Component} from "@angular/core";
import {
Event,
NavigationCancel,
NavigationEnd,
NavigationError,
NavigationStart,
Router
} from "@angular/router";
import {ProgressIndicationService} from "@webbpm/base-package";
import {AppJivoChatWidgetService} from "../../app/service/app-jivo-chat.service";
@Component({
moduleId: module.id,
selector: '[webbpm]',
templateUrl: './../../../../../src/resources/template/webbpm/webbpm.html'
})
export class WebbpmComponent {
public headerVisible: boolean = true;
public footerVisible: boolean = true;
constructor(private router: Router,
private progressIndicationService: ProgressIndicationService,
private appJivoChatWidgetService: AppJivoChatWidgetService) {
router.events.subscribe((event: Event) => {
if (event instanceof NavigationStart) {
progressIndicationService.showProgressBar();
}
else if (event instanceof NavigationEnd
|| event instanceof NavigationError
|| event instanceof NavigationCancel) {
progressIndicationService.hideProgressBar();
}
})
}
}

View file

@ -0,0 +1,10 @@
import {ErrorHandler, Injectable, Injector} from '@angular/core';
import {BaseErrorHandler} from "@webbpm/base-package";
@Injectable({providedIn: 'root'})
export class GlobalErrorHandler extends BaseErrorHandler implements ErrorHandler {
constructor(injector: Injector) {
super(injector);
}
}

View file

@ -0,0 +1,35 @@
import {ErrorHandler, Injectable, Injector} from '@angular/core';
import {BaseErrorHandler} from "@webbpm/base-package";
//todo: will be used after angular update in dev mode
@Injectable({providedIn: 'root'})
export class GlobalErrorHandler extends BaseErrorHandler implements ErrorHandler {
constructor(injector: Injector) {
super(injector);
}
handleError(error) {
const chunkFailedMessage = /Loading chunk [\d]+ failed/;
if (chunkFailedMessage.test(error.message)) {
window.location.reload();
}
else if (!this.isPreviewPage()) {
super.handleError(error);
}
}
internalHandleError(error) {
if (this.isPreviewPage()) {
return;
}
else {
super.internalHandleError(error);
}
}
private isPreviewPage() {
return window.location.hash.includes("webbpm-preview");
}
}

View file

@ -0,0 +1,12 @@
import {HTTP_INTERCEPTORS} from "@angular/common/http";
import {
FormDirtyInterceptor,
HttpSecurityErrorInterceptor,
HttpSecurityInterceptor
} from "@webbpm/base-package";
export const DEFAULT_HTTP_INTERCEPTOR_PROVIDERS = [
{provide: HTTP_INTERCEPTORS, useClass: HttpSecurityInterceptor, multi: true},
{provide: HTTP_INTERCEPTORS, useClass: HttpSecurityErrorInterceptor, multi: true},
{provide: HTTP_INTERCEPTORS, useClass: FormDirtyInterceptor, multi: true}
];

View file

@ -0,0 +1,9 @@
import {HTTP_INTERCEPTORS} from "@angular/common/http";
import {FormDirtyInterceptor, HttpSecurityInterceptor} from "@webbpm/base-package";
import {DevHttpSecurityErrorInterceptor} from "./http-security-error-interceptor.dev";
export const DEFAULT_HTTP_INTERCEPTOR_PROVIDERS = [
{provide: HTTP_INTERCEPTORS, useClass: HttpSecurityInterceptor, multi: true},
{provide: HTTP_INTERCEPTORS, useClass: DevHttpSecurityErrorInterceptor, multi: true},
{provide: HTTP_INTERCEPTORS, useClass: FormDirtyInterceptor, multi: true}
];

View file

@ -0,0 +1,31 @@
import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from "@angular/common/http";
import {HttpSecurityErrorInterceptor, 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";
@Injectable()
export class DevHttpSecurityErrorInterceptor extends HttpSecurityErrorInterceptor
implements HttpInterceptor {
private router: Router;
constructor(router: Router, messagesService: MessagesService, userService: UserService) {
super(router, messagesService, userService);
this.router = router;
}
intercept(req: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (window.location.hash.includes("webbpm-preview")) {
return next.handle(req).pipe(catchError(() => {
return EMPTY;
}
));
}
else {
return super.intercept(req, next);
}
}
}

View file

@ -0,0 +1,88 @@
import {RouterModule, Routes} from "@angular/router";
import {NgModule} from "@angular/core";
import {
AuthenticationGuard,
ConfirmExitGuard
} from "@webbpm/base-package";
const routes: Routes = [
{
path: 'user-management',
canActivate: [AuthenticationGuard],
children: [
{
path: 'users',
loadChildren: 'generated-sources/page-user-management-users.module#PageusermanagementusersModule',
canActivate: [ConfirmExitGuard]
},
{
path: 'users/new',
loadChildren: 'generated-sources/page-user-management-user-create.module#PageusermanagementusercreateModule',
canActivate: [ConfirmExitGuard]
},
{
path: 'users/:id',
loadChildren: 'generated-sources/page-user-management-user-edit.module#PageusermanagementusereditModule',
canActivate: [ConfirmExitGuard]
},
{
path: 'org-units',
loadChildren: 'generated-sources/page-user-management-org-units.module#PageusermanagementorgunitsModule',
canActivate: [ConfirmExitGuard]
},
{
path: 'org-units/new',
loadChildren: 'generated-sources/page-user-management-org-unit.module#PageusermanagementorgunitModule',
canActivate: [ConfirmExitGuard]
},
{
path: 'org-units/:id',
loadChildren: 'generated-sources/page-user-management-org-unit.module#PageusermanagementorgunitModule',
canActivate: [ConfirmExitGuard]
},
{
path: 'roles',
loadChildren: 'generated-sources/page-user-management-roles.module#PageusermanagementrolesModule',
canActivate: [ConfirmExitGuard]
},
{
path: 'roles/new',
loadChildren: 'generated-sources/page-user-management-role.module#PageusermanagementroleModule',
canActivate: [ConfirmExitGuard]
},
{
path: 'roles/:id',
loadChildren: 'generated-sources/page-user-management-role.module#PageusermanagementroleModule',
canActivate: [ConfirmExitGuard]
},
{
path: 'groups',
loadChildren: 'generated-sources/page-user-management-groups.module#PageusermanagementgroupsModule',
canActivate: [ConfirmExitGuard]
},
{
path: 'groups/new',
loadChildren: 'generated-sources/page-user-management-group-create.module#PageusermanagementgroupcreateModule',
canActivate: [ConfirmExitGuard]
},
{
path: 'groups/:id',
loadChildren: 'generated-sources/page-user-management-group-edit.module#PageusermanagementgroupeditModule',
canActivate: [ConfirmExitGuard]
},
{
path: 'authorities',
loadChildren: 'generated-sources/page-user-management-authorities.module#PageusermanagementauthoritiesModule',
canActivate: [ConfirmExitGuard]
}
]
}
];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class UserManagementRoutingModule {
}

View file

@ -0,0 +1,51 @@
import {NgModule} from "@angular/core";
import {RouterModule, Routes} from "@angular/router";
import {HomeComponent} from "./component/home.component";
import {
AuthenticationGuard,
HistoryLocationGuard,
TaskPageRouteResolver,
ConfirmExitGuard
} from "@webbpm/base-package";
import {DYNAMIC_ROUTING} from "../../page.routing";
import {TaskComponent} from "./../app/component/task.component";
import {TaskNotFoundComponent} from "./../app/component/task-not-found.component";
const webbpmRoutes: Routes = [
{
path: '',
component: HomeComponent,
canActivate: [AuthenticationGuard, ConfirmExitGuard],
pathMatch: 'full',
},
{
path: 'process/:processInstanceId/task/:taskId',
component: TaskComponent,
children: DYNAMIC_ROUTING,
canActivate: [HistoryLocationGuard],
resolve: {
taskPage: TaskPageRouteResolver
},
runGuardsAndResolvers: "always"
},
{
path: 'process/task-not-found',
component: TaskNotFoundComponent,
canActivate: [AuthenticationGuard]
},
{
path: '**',
redirectTo: '',
}
];
@NgModule({
imports: [RouterModule.forRoot(webbpmRoutes, {
useHash: true,
onSameUrlNavigation: "reload"
})],
exports: [RouterModule]
})
export class WebbpmRoutingModule {
}

View file

@ -0,0 +1,62 @@
import {ErrorHandler, NgModule} from "@angular/core";
import {BrowserModule} from "@angular/platform-browser";
import {BrowserAnimationsModule} from "@angular/platform-browser/animations";
import {FormsModule} from "@angular/forms";
import {AgGridModule} from "ag-grid-angular";
import {WebbpmComponent} from "./component/webbpm.component";
import {WebbpmRoutingModule} from "./webbpm-routing.module";
import {NgbModule} from "@ng-bootstrap/ng-bootstrap";
import {ToastNoAnimationModule} from "ngx-toastr";
import {AppModule} from "../app/app.module";
import {HomeComponent} from "./component/home.component";
import {
BpmnModule,
ComponentsModule,
CoreModule,
SecurityModule,
} from "@webbpm/base-package";
import {TaskParamsProvider} from "@webbpm/base-package";
import {ProcessInstanceParamsProvider} from "@webbpm/base-package";
import {AppRoutingModule} from "../app/app-routing.module";
import {AppJivoChatWidgetService} from "../app/service/app-jivo-chat.service";
import {UserManagementRoutingModule} from "./user-management-routing.module";
import {GlobalErrorHandler} from "./handler/global-error.handler.prod";
import {DEFAULT_HTTP_INTERCEPTOR_PROVIDERS} from "./interceptor/default-interceptors.prod";
let IMPORTS = [
BrowserAnimationsModule,
BrowserModule,
FormsModule,
NgbModule,
ToastNoAnimationModule.forRoot(),
AgGridModule,
AppRoutingModule,
UserManagementRoutingModule,
BpmnModule,
CoreModule,
ComponentsModule,
SecurityModule,
AppModule,
WebbpmRoutingModule
];
@NgModule({
imports: IMPORTS,
declarations: [
WebbpmComponent,
HomeComponent
],
exports: [],
providers: [
TaskParamsProvider,
ProcessInstanceParamsProvider,
AppJivoChatWidgetService,
{provide: ErrorHandler, useClass: GlobalErrorHandler},
DEFAULT_HTTP_INTERCEPTOR_PROVIDERS
],
bootstrap: [
WebbpmComponent
]
})
export class WebbpmModule {
}

3
frontend/src/ts/page.routing.d.ts vendored Normal file
View file

@ -0,0 +1,3 @@
import {Routes} from "@angular/router";
declare const DYNAMIC_ROUTING: Routes;

View file

@ -0,0 +1,59 @@
/** IE9, IE10 and IE11 requires all of the following polyfills. **/
import 'core-js/es6/symbol';
import 'core-js/es6/object';
import 'core-js/es6/function';
import 'core-js/es6/parse-int';
import 'core-js/es6/parse-float';
import 'core-js/es6/number';
import 'core-js/es6/math';
import 'core-js/es6/string';
import 'core-js/es6/date';
import 'core-js/es6/array';
import 'core-js/es6/regexp';
import 'core-js/es6/map';
import 'core-js/es6/weak-map';
import 'core-js/es6/set';
import 'core-js/es6/promise';
import 'core-js/es7';
/** IE10 and IE11 requires the following for NgClass support on SVG elements */
// import 'classlist.js'; // Run `npm install --save classlist.js`.
/** IE10 and IE11 requires the following for the Reflect API. */
// import 'core-js/es6/reflect';
/** Evergreen browsers require these. **/
// Used for reflect-metadata in JIT. If you use AOT (and only Angular decorators), you can remove.
/**
* Required to support Web Animations `@angular/platform-browser/animations`.
* Needed for: All but Chrome, Firefox and Opera. http://caniuse.com/#feat=web-animation
**/
// import 'web-animations-js'; // Run `npm install --save web-animations-js`.
/**
* By default, zone.js will patch all possible macroTask and DomEvents
* user can disable parts of macroTask/DomEvents patch by setting following flags
*/
// (window as any).__Zone_disable_requestAnimationFrame = true; // disable patch requestAnimationFrame
// (window as any).__Zone_disable_on_property = true; // disable patch onProperty such as onclick
// (window as any).__zone_symbol__BLACK_LISTED_EVENTS = ['scroll', 'mousemove']; // disable patch specified eventNames
/*
* in IE/Edge developer tools, the addEventListener will also be wrapped by zone.js
* with the following flag, it will bypass `zone.js` patch for IE/Edge
*/
// (window as any).__Zone_enable_cross_context_check = true;
/***************************************************************************************************
* Zone JS is required by default for Angular itself.
*/
import 'zone.js/dist/zone';// Included with Angular CLI.
/***************************************************************************************************
* APPLICATION IMPORTS
*/

38
frontend/src/ts/vendor.ts Normal file
View file

@ -0,0 +1,38 @@
// Angular
import '@angular/core';
import '@angular/common';
import '@angular/common/http';
import '@angular/forms';
import '@angular/platform-browser';
import '@angular/router';
import '@angular/animations';
import '@webbpm/base-package';
// RxJS
import 'rxjs';
import 'rxjs-compat';
import 'rxjs/operators';
import 'rxjs/internal-compatibility';
import 'rxjs/ajax';
import 'rxjs/testing';
import 'rxjs/webSocket';
//jquery
import 'jquery';
//popper
import 'popper.js';
//bootstrap
import '@ng-bootstrap/ng-bootstrap';
import 'bootstrap';
//mask
import 'inputmask';
//grid
import 'ag-grid-community';
import 'ag-grid-angular';
//selectize
import 'selectize';
//datepicker
import 'eonasdan-bootstrap-datetimepicker';
import 'moment-timezone';
//ngx-toastr
import 'ngx-toastr';
//ngx-cookie
import 'ngx-cookie';

View file

@ -0,0 +1,93 @@
(function(global) {
System.config({
transpiler: 'plugin-babel',
paths: {
'npm:': 'node_modules/',
'generated-sources': 'build_dev/js/generated-sources'
},
map: {
'webbpm': 'build_dev/js',
'@angular/core': 'npm:@angular/core/bundles/core.umd.js',
'@angular/common': 'npm:@angular/common/bundles/common.umd.js',
'@angular/common/http': 'npm:@angular/common/bundles/common-http.umd.js',
'@angular/common/locales': 'npm:@angular/common/locales',
'@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
'@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
'@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
'@angular/router': 'npm:@angular/router/bundles/router.umd.js',
'@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
'@angular/animations': 'npm:@angular/animations/bundles/animations.umd.js',
'@angular/animations/browser': 'npm:@angular/animations/bundles/animations-browser.umd.js',
'@angular/platform-browser/animations':'npm:@angular/platform-browser/bundles/platform-browser-animations.umd.js',
'@ng-bootstrap/ng-bootstrap': 'npm:@ng-bootstrap/ng-bootstrap/bundles/ng-bootstrap.umd.js',
'bootstrap': 'npm:bootstrap',
'@webbpm/base-package': 'npm:@webbpm/base-package/bundles/webbpm-base-package.umd.js',
'angular-calendar': 'npm:angular-calendar/bundles/angular-calendar.umd.js',
'angular-calendar/date-adapters/date-fns': 'npm:angular-calendar/date-adapters/date-fns/index.js',
'angular-draggable-droppable': 'npm:angular-draggable-droppable/bundles/angular-draggable-droppable.umd.js',
'angular-resizable-element': 'npm:angular-resizable-element/bundles/angular-resizable-element.umd.js',
'calendar-utils': 'npm:calendar-utils/bundles/calendar-utils.umd.js',
'calendar-utils/date-adapters/date-fns': 'npm:calendar-utils/date-adapters/date-fns/index.js',
'date-fns': 'npm:date-fns',
'ngx-cookie': 'npm:ngx-cookie/bundles/ngx-cookie.umd.js',
'moment': 'npm:moment',
'moment-timezone': 'npm:moment-timezone',
'positioning': 'npm:positioning/dist/positioning.js',
'rxjs': 'npm:rxjs',
'rxjs-compat': 'npm:rxjs-compat',
'rxjs/operators': 'npm:rxjs/operators',
'rxjs/internal-compatibility': 'npm:rxjs/internal-compatibility',
'rxjs/ajax': 'npm:rxjs/ajax',
'rxjs/testing': 'npm:rxjs/testing',
'rxjs/webSocket': 'npm:rxjs/webSocket',
'jquery': 'npm:jquery/dist/jquery.js',
'popper.js': 'npm:popper.js/dist/umd/popper.js',
'sifter': 'npm:sifter/sifter.min.js',
'microplugin': 'npm:microplugin/src/microplugin.js',
'selectize': 'npm:selectize/dist/js/selectize.min.js',
'ngx-toastr': 'npm:ngx-toastr/bundles/ngx-toastr.umd.min.js',
'eonasdan-bootstrap-datetimepicker': 'npm:eonasdan-bootstrap-datetimepicker/src/js/bootstrap-datetimepicker.js',
'autonumeric': 'npm:autonumeric',
'jsgantt-improved': 'npm:jsgantt-improved/dist/jsgantt.js',
'js-year-calendar': 'npm:js-year-calendar/dist/js-year-calendar.js',
'ag-grid-angular': 'npm:ag-grid-angular/bundles/ag-grid-angular.umd.js',
'ag-grid-community': 'npm:ag-grid-community/dist/ag-grid-community.cjs.js',
'inputmask': 'npm:inputmask',
'downloadjs': 'npm:downloadjs/download.js',
'esmarttokenjs': 'npm:esmarttokenjs/esmarttoken.js',
'cadesplugin_api': 'npm:cadesplugin_api/index.js',
'plugin-babel': 'npm:systemjs-plugin-babel/plugin-babel.js',
'systemjs-babel-build': 'npm:systemjs-plugin-babel/systemjs-babel-browser.js',
'chart.js': 'npm:chart.js/dist',
'chartjs-adapter-moment': 'npm:chartjs-adapter-moment/dist/chartjs-adapter-moment.js',
'tslib': 'npm:tslib/tslib.js',
'ngx-international-phone-number': 'npm:ngx-international-phone-number/ngx-international-phone-number.umd.js',
'google-libphonenumber': 'npm:google-libphonenumber/dist/libphonenumber.js'
},
packages: {
'webbpm': { main: 'main', defaultExtension: 'js'},
'@angular/common/locales': { defaultExtension: 'js'},
'date-fns': { main: 'index.js', defaultExtension: 'js'},
'rxjs': { main: 'index.js', defaultExtension: 'js' },
'rxjs-compat': { main: "index.js", defaultExtension: 'js'},
'rxjs/operators': { main: "index.js", defaultExtension: 'js'},
'rxjs/internal-compatibility':{ main: "index.js", defaultExtension: 'js'},
'rxjs/ajax': { main: "index.js", defaultExtension: 'js'},
'rxjs/testing': { main: "index.js", defaultExtension: 'js'},
'rxjs/webSocket': { main: "index.js", defaultExtension: 'js'},
'moment': { main: 'min/locales.min', defaultExtension: 'js' },
'moment-es6': { main: 'index.js', defaultExtension: 'js' },
'moment-timezone': { main: 'builds/moment-timezone-with-data.min', defaultExtension: 'js' },
'bootstrap': { main: 'dist/js/bootstrap', defaultExtension: 'js'},
'lib': { format: 'register', defaultExtension: 'js' },
'autonumeric': {
main: 'dist/autoNumeric.js'
},
'chart.js': { main: 'chart.js', defaultExtension: 'js' },
'inputmask': {
main: 'dist/inputmask.js',
defaultExtension: 'js'
}
}
});
})(this);

View file

@ -0,0 +1,92 @@
(function(global) {
System.config({
transpiler: 'plugin-babel',
paths: {
'npm:': 'node_modules/',
'generated-sources': 'build_dev/js/generated-sources'
},
map: {
'preview': 'build_dev/js',
'@angular/core': 'npm:@angular/core/bundles/core.umd.js',
'@angular/common': 'npm:@angular/common/bundles/common.umd.js',
'@angular/common/http': 'npm:@angular/common/bundles/common-http.umd.js',
'@angular/common/locales': 'npm:@angular/common/locales',
'@angular/compiler': 'npm:@angular/compiler/bundles/compiler.umd.js',
'@angular/platform-browser': 'npm:@angular/platform-browser/bundles/platform-browser.umd.js',
'@angular/platform-browser-dynamic': 'npm:@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
'@angular/router': 'npm:@angular/router/bundles/router.umd.js',
'@angular/forms': 'npm:@angular/forms/bundles/forms.umd.js',
'@angular/animations': 'npm:@angular/animations/bundles/animations.umd.js',
'@angular/animations/browser': 'npm:@angular/animations/bundles/animations-browser.umd.js',
'@angular/platform-browser/animations':'npm:@angular/platform-browser/bundles/platform-browser-animations.umd.js',
'@ng-bootstrap/ng-bootstrap': 'npm:@ng-bootstrap/ng-bootstrap/bundles/ng-bootstrap.umd.js',
'bootstrap': 'npm:bootstrap',
'@webbpm/base-package': 'npm:@webbpm/base-package/bundles/webbpm-base-package.umd.js',
'angular-calendar': 'npm:angular-calendar/bundles/angular-calendar.umd.js',
'angular-calendar/date-adapters/date-fns': 'npm:angular-calendar/date-adapters/date-fns/index.js',
'angular-draggable-droppable': 'npm:angular-draggable-droppable/bundles/angular-draggable-droppable.umd.js',
'angular-resizable-element': 'npm:angular-resizable-element/bundles/angular-resizable-element.umd.js',
'calendar-utils': 'npm:calendar-utils/bundles/calendar-utils.umd.js',
'calendar-utils/date-adapters/date-fns': 'npm:calendar-utils/date-adapters/date-fns/index.js',
'date-fns': 'npm:date-fns',
'ngx-cookie': 'npm:ngx-cookie/bundles/ngx-cookie.umd.js',
'moment': 'npm:moment',
'moment-timezone': 'npm:moment-timezone',
'positioning': 'npm:positioning/dist/positioning.js',
'rxjs': 'npm:rxjs',
'rxjs-compat': 'npm:rxjs-compat',
'rxjs/operators': 'npm:rxjs/operators',
'rxjs/internal-compatibility': 'npm:rxjs/internal-compatibility',
'rxjs/ajax': 'npm:rxjs/ajax',
'rxjs/testing': 'npm:rxjs/testing',
'rxjs/webSocket': 'npm:rxjs/webSocket',
'jquery': 'npm:jquery/dist/jquery.js',
'popper.js': 'npm:popper.js/dist/umd/popper.js',
'sifter': 'npm:sifter/sifter.min.js',
'microplugin': 'npm:microplugin/src/microplugin.js',
'selectize': 'npm:selectize/dist/js/selectize.min.js',
'ngx-toastr': 'npm:ngx-toastr/bundles/ngx-toastr.umd.min.js',
'eonasdan-bootstrap-datetimepicker': 'npm:eonasdan-bootstrap-datetimepicker/src/js/bootstrap-datetimepicker.js',
'autonumeric': 'npm:autonumeric',
'jsgantt-improved': 'npm:jsgantt-improved/dist/jsgantt.js',
'js-year-calendar': 'npm:js-year-calendar/dist/js-year-calendar.js',
'ag-grid-angular': 'npm:ag-grid-angular/bundles/ag-grid-angular.umd.js',
'ag-grid-community': 'npm:ag-grid-community/dist/ag-grid-community.cjs.js',
'inputmask': 'npm:inputmask',
'downloadjs': 'npm:downloadjs/download.js',
'esmarttokenjs': 'npm:esmarttokenjs/esmarttoken.js',
'cadesplugin_api': 'npm:cadesplugin_api/index.js',
'plugin-babel': 'npm:systemjs-plugin-babel/plugin-babel.js',
'systemjs-babel-build': 'npm:systemjs-plugin-babel/systemjs-babel-browser.js',
'chart.js': 'npm:chart.js/dist',
'chartjs-adapter-moment': 'npm:chartjs-adapter-moment/dist/chartjs-adapter-moment.js',
'tslib': 'npm:tslib/tslib.js',
'ngx-international-phone-number': 'npm:ngx-international-phone-number/ngx-international-phone-number.umd.js',
'google-libphonenumber': 'npm:google-libphonenumber/dist/libphonenumber.js'
},
packages: {
'preview': { main: './modules/preview/preview.main', defaultExtension: 'js'},
'@angular/common/locales': { defaultExtension: 'js'},
'date-fns': { main: 'index.js', defaultExtension: 'js'},
'rxjs': { main: 'index.js', defaultExtension: 'js' },
'rxjs-compat': { main: "index.js", defaultExtension: 'js'},
'rxjs/operators': { main: "index.js", defaultExtension: 'js'},
'rxjs/internal-compatibility':{ main: "index.js", defaultExtension: 'js'},
'rxjs/ajax': { main: "index.js", defaultExtension: 'js'},
'rxjs/testing': { main: "index.js", defaultExtension: 'js'},
'rxjs/webSocket': { main: "index.js", defaultExtension: 'js'},
'moment': { main: 'min/locales.min', defaultExtension: 'js' },
'moment-timezone': { main: 'builds/moment-timezone-with-data.min', defaultExtension: 'js' },
'bootstrap': { main: 'dist/js/bootstrap', defaultExtension: 'js'},
'lib': { format: 'register', defaultExtension: 'js' },
'autonumeric': {
main: 'dist/autoNumeric.js'
},
'chart.js': { main: 'chart.js', defaultExtension: 'js' },
'inputmask': {
main: 'dist/inputmask.js',
defaultExtension: 'js'
}
}
});
})(this);

View file

@ -0,0 +1,42 @@
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"declaration": false,
"sourceMap": false,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"removeComments": false,
"skipLibCheck": true,
"noImplicitAny": false,
"typeRoots": [
"node_modules/@types"
],
"lib": [
"es2016",
"dom",
"es2017.object",
"es2018.promise"
]
},
"compileOnSave": false,
"buildOnSave": false,
"include": [
"src/ts/**/*"
],
"exclude": [
"node_modules",
"./node_modules/@types",
"src/ts/main.ts",
"**/*.spec.ts",
"src/test.ts"
],
"angularCompilerOptions": {
"skipMetadataEmit": true,
"alwaysCompileGeneratedCode":true,
"preserveWhitespaces": false,
"annotationsAs": "decorators",
"mainPath": "./src/ts"
}
}

40
frontend/tsconfig.json Normal file
View file

@ -0,0 +1,40 @@
{
"compilerOptions": {
"baseUrl": "./",
"target": "es5",
"module": "commonjs",
"moduleResolution": "node",
"declaration": false,
"inlineSourceMap": true,
"emitDecoratorMetadata": true,
"experimentalDecorators": true,
"removeComments": false,
"skipLibCheck": true,
"noImplicitAny": false,
"outDir": "build_dev/js",
"typeRoots": [
"node_modules/@types"
],
"lib": [
"es2016",
"dom",
"es2017.object",
"es2018.promise"
]
},
"compileOnSave": false,
"buildOnSave": false,
"include": [
"src/ts/**/*"
],
"exclude": [
"node_modules",
"./node_modules/@types",
"**/*.ngfactory.ts",
"**/*.shim.ts",
"src/ts/main.aot.ts",
"src/ts/generated-sources/**/*",
"src/ts/page.routing.ts",
"src/ts/aot"
]
}

View file

@ -0,0 +1,136 @@
'use strict';
const path = require('path');
const webpack = require('webpack');
const CopyWebpackPlugin = require('copy-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin;
const MiniCssExtractPlugin = require("mini-css-extract-plugin");
const TerserPlugin = require('terser-webpack-plugin');
function _path(p) {
return path.join(__dirname, p);
}
module.exports = {
mode: 'production',
entry: {
'polyfills': './build/scripts/polyfills.js',
'vendor': './build/scripts/vendor.js',
'main': './build/scripts/main.aot.js'
},
context: process.cwd(),
output: {
path: path.join(process.cwd(), './dist'),
filename: '[name].[chunkhash].bundle.js',
chunkFilename: '[id].[chunkhash].chunk.js'
},
module: {
rules: [
{
test: /\.js$/,
loader: 'babel-loader',
include: [path.resolve(__dirname, "node_modules")],
options: {
presets: ['@babel/preset-env']
}
},
{
test: /\.js$/,
loader: 'angular-router-loader?aot=true'
},
{
test: /\.html$/,
loader: 'raw-loader'
},
{
test: /\.css$/,
use: [
{
loader: MiniCssExtractPlugin.loader,
options: {
// you can specify a publicPath here
// by default it use publicPath in webpackOptions.output
// publicPath: '../'
}
},
"css-loader"
]
},
{
test: /\.(png|jpe?g|gif|svg|woff|woff2|ttf|eot|ico|otf)$/,
loader: 'file-loader?name=src/resources/[name].[hash].[ext]'
}
]
},
optimization: {
minimizer: [
new TerserPlugin({
cache: true,
parallel: true,
terserOptions: {
// https://github.com/webpack-contrib/terser-webpack-plugin#terseroptions
}
})
],
splitChunks: {
// include all types of chunks
chunks: 'all'
}
},
plugins: [
// new BundleAnalyzerPlugin(),
new HtmlWebpackPlugin({
template: 'index.webpack.html',
filename: 'index.html',
chunksSortMode : 'none'
}),
new CopyWebpackPlugin([
{from: 'index.webpack.html', to: 'index.html'},
{from: 'src/resources/img/progress.gif', to: 'src/resources/img/progress.gif'},
{from: 'src/resources/img/logo.png', to: 'src/resources/img/logo.png'},
{from: 'src/resources/app-config.json', to: 'src/resources/app-config.json'},
{from: 'src/resources/app.version', to: 'src/resources/app.version'}
]),
new MiniCssExtractPlugin({
filename: '[name].[hash].css',
chunkFilename: '[id].[hash].css'
}),
new webpack.ProvidePlugin({
$: "jquery",
jQuery: "jquery",
"window.jQuery": "jquery",
Popper: ['popper.js', 'default']
})
],
resolve: {
alias: {
'jquery': _path('node_modules/jquery/dist/jquery.min'),
'inputmask': _path('node_modules/inputmask/dist/inputmask'),
'downloadjs': _path('node_modules/downloadjs/download.min.js'),
'esmarttokenjs': _path('node_modules/esmarttokenjs/esmarttoken.js'),
'cadesplugin_api': _path('node_modules/cadesplugin_api/index.js'),
'eonasdan-bootstrap-datetimepicker': _path('node_modules/eonasdan-bootstrap-datetimepicker/src/js/bootstrap-datetimepicker'),
'autonumeric': _path('node_modules/autonumeric/dist/autoNumeric.js'),
'jsgantt-improved': _path('node_modules/jsgantt-improved/dist/jsgantt.js'),
'js-year-calendar': _path('node_modules/js-year-calendar/dist/js-year-calendar.js'),
'chart.js': _path('node_modules/chart.js/dist/chart.js')
},
modules: [
'node_modules',
path.resolve(process.cwd(), './build'),
path.resolve(process.cwd(), './build/scripts'),
],
extensions: ['.js']
},
stats: {
children: false
},
devtool: false
};