Merge branch 'release/1.1.1'

# Conflicts:
#	frontend/src/resources/css/style.css
This commit is contained in:
kochetkov 2025-11-13 16:25:49 +03:00
commit a929b1a5fc
701 changed files with 32246 additions and 58543 deletions

12
frontend/.gitignore vendored Normal file
View file

@ -0,0 +1,12 @@
# frameworks dirs
.angular
.nx
# compiled output
dist
tmp
out-tsc
# generated by webbpm
tsconfig.base.json
src/ts/page.routing.ts

3
frontend/.nxignore Normal file
View file

@ -0,0 +1,3 @@
!modules/generated/
!tsconfig.base.json
!src/ts/page.routing.ts

6
frontend/.prettierignore Normal file
View file

@ -0,0 +1,6 @@
# Add files here to ignore them from prettier formatting
/dist
/coverage
/.nx/cache
/.nx/workspace-data
.angular

3
frontend/.prettierrc Normal file
View file

@ -0,0 +1,3 @@
{
"singleQuote": true
}

View file

@ -1,71 +0,0 @@
{
"$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"
}

View file

@ -0,0 +1,42 @@
import nx from '@nx/eslint-plugin';
export default [
...nx.configs['flat/base'],
...nx.configs['flat/typescript'],
...nx.configs['flat/javascript'],
{
ignores: ['**/dist'],
},
{
files: ['**/*.ts', '**/*.tsx', '**/*.js', '**/*.jsx'],
rules: {
'@nx/enforce-module-boundaries': [
'error',
{
enforceBuildableLibDependency: true,
allow: ['^.*/eslint(\\.base)?\\.config\\.[cm]?js$'],
depConstraints: [
{
sourceTag: '*',
onlyDependOnLibsWithTags: ['*'],
},
],
},
],
},
},
{
files: [
'**/*.ts',
'**/*.tsx',
'**/*.cts',
'**/*.mts',
'**/*.js',
'**/*.jsx',
'**/*.cjs',
'**/*.mjs',
],
// Override or add rules here
rules: {},
},
];

View file

@ -1,23 +1,14 @@
<!DOCTYPE html>
<html>
<head>
<title>ervu-eks</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<link rel="icon" type="image/png" href="src/resources/img/logo.png"/>
<link rel="stylesheet" href="src/resources/css/style.css"/>
<script src="node_modules/core-js/client/shim.min.js"></script>
<script src="node_modules/zone.js/dist/zone.js"></script>
<script src="node_modules/reflect-metadata/Reflect.js"></script>
<script src="node_modules/systemjs/dist/system.src.js"></script>
<script src="systemjs.config.js"></script>
<script>
System.import('webbpm').catch(function (err) {
console.error(err);
});
</script>
</head>
<body webbpm class="webbpm ervu-eks">
<div class="progress"></div>
</body>
</html>
<!DOCTYPE html>
<html>
<head>
<title>ervu-eks</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1"/>
<link rel="icon" type="image/png" href="resources/img/logo.png"/>
</head>
<body class="webbpm ervu-eks">
<app-root>
<div class="progress"></div>
</app-root>
</body>
</html>

View file

@ -1,11 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>ervu-eks</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<link rel="icon" type="image/png" href="src/resources/img/logo.png"/>
</head>
<body webbpm class="webbpm ervu-eks">
<div class="progress"></div>
</body>
</html>

View file

@ -0,0 +1,7 @@
{
"$schema": "../../node_modules/ng-packagr/ng-package.schema.json",
"dest": "../../dist/modules/shared",
"lib": {
"entryFile": "src/index.ts"
}
}

View file

@ -0,0 +1,12 @@
{
"name": "shared",
"version": "0.0.1",
"scripts": {
"generate-barrels": "npx barrelsby -D -d src -n index.ts -i src -e \"\\.spec\\.ts$\" \"\\.d\\.ts$\""
},
"peerDependencies": {
"@angular/common": "19.2.7",
"@angular/core": "19.2.7"
},
"sideEffects": false
}

View file

@ -0,0 +1,29 @@
{
"name": "shared",
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"sourceRoot": "modules/shared/src",
"prefix": "lib",
"projectType": "library",
"tags": [],
"targets": {
"build": {
"executor": "@nx/angular:ng-packagr-lite",
"outputs": ["{workspaceRoot}/dist/{projectRoot}"],
"options": {
"project": "modules/shared/ng-package.json"
},
"configurations": {
"production": {
"tsConfig": "modules/shared/tsconfig.lib.prod.json"
},
"development": {
"tsConfig": "modules/shared/tsconfig.lib.json"
}
},
"defaultConfiguration": "production"
},
"lint": {
"executor": "@nx/eslint:lint"
}
}
}

View file

@ -0,0 +1,13 @@
/**
* @file Automatically generated by barrelsby.
*/
export * from "./lib/shared.module";
export * from "./lib/component/app-progress-indication.component";
export * from "./lib/component/ConfigExecuteBtn";
export * from "./lib/component/ExportDataBtn";
export * from "./lib/generated/dto/ConfigExecuteRequest";
export * from "./lib/generated/dto/ExportDataRequest";
export * from "./lib/generated/rpc/ConfigExecutorRpcService";
export * from "./lib/generated/rpc/ExportDataRpcService";
export * from "./lib/service/app-progress-indication.service";

View file

@ -0,0 +1,83 @@
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef} from "@angular/core";
import {
AbstractButton,
DateTimePicker,
MessagesService,
NotNull,
ObjectRef,
TextArea
} from "@webbpm/base-package";
import {ConfigExecutorRpcService} from "../generated/rpc/ConfigExecutorRpcService";
import {ConfigExecuteRequest} from "../generated/dto/ConfigExecuteRequest";
/**
* @author: a.petrov
*/
@Component({
selector: 'config-execute-button-component',
templateUrl: './ConfigExecuteBtn.html',
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false
})
export class ConfigExecuteBtn extends AbstractButton {
@ObjectRef()
@NotNull()
public ervuIdField: TextArea;
@ObjectRef()
public startDate: DateTimePicker;
@ObjectRef()
public endDate: DateTimePicker;
@NotNull()
public methodPath: string;
private rpcService: ConfigExecutorRpcService;
private messagesService: MessagesService;
constructor(el: ElementRef, cd: ChangeDetectorRef) {
super(el, cd);
}
initialize() {
super.initialize();
this.rpcService = this.getScript(ConfigExecutorRpcService);
this.messagesService = this.injector.get(MessagesService);
}
doClickActions(): Promise<any> {
if (this.methodPath.trim().length == 0) {
return;
}
const ids: string[] = this.ervuIdField.getValue()
? ConfigExecuteBtn.parseIds(this.ervuIdField.getValue())
: new Array<string>();
let configExecuteRequest: ConfigExecuteRequest = new ConfigExecuteRequest();
let withDate = false;
configExecuteRequest.ids = ids;
if (this.startDate || this.endDate) {
withDate = true;
configExecuteRequest.startDate = this.startDate ? this.startDate.getDateValue() : null;
configExecuteRequest.endDate = this.endDate ? this.endDate.getDateValue() : null;
}
return this.rpcService.callConfigExecutor(this.methodPath, configExecuteRequest, withDate, true)
.then(successMsg => this.messagesService.success(successMsg))
.catch(error => Promise.reject(error));
}
getFocusElement(): HTMLInputElement {
return this.el.nativeElement.querySelector('button');
}
private static parseIds(value: string): string[] {
return value.replace(/[{}]/g, '')
.split(',')
.map(id => id.trim().replace(/"/g, ''));
}
}

View file

@ -17,10 +17,10 @@ import {ExportDataRequest} from "../generated/dto/ExportDataRequest";
* @author: kochetkov
*/
@Component({
moduleId: module.id,
selector: 'export-data-button-component',
templateUrl: './../../../src/resources/template/app/component/ConfigExecuteBtn.html',
changeDetection: ChangeDetectionStrategy.OnPush
templateUrl: './ConfigExecuteBtn.html',
changeDetection: ChangeDetectionStrategy.OnPush,
standalone: false
})
export class ExportDataBtn extends AbstractButton {

View file

@ -0,0 +1,11 @@
import {ChangeDetectionStrategy, Component} from "@angular/core";
@Component({
changeDetection: ChangeDetectionStrategy.OnPush,
selector: 'progress-indication-dialog-content',
templateUrl: './progress-indication.html',
standalone: false
})
export class AppProgressIndicationComponent {
}

View file

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

View file

@ -2,7 +2,7 @@ import {Injectable} from "@angular/core";
import {AppProgressIndicationComponent} from "../component/app-progress-indication.component";
import {NgbModal, NgbModalOptions, NgbModalRef} from "@ng-bootstrap/ng-bootstrap";
@Injectable()
@Injectable({providedIn: 'root'})
export class AppProgressIndicationService {
private static readonly EVENT_INTERCEPTOR = (event) => {
@ -68,7 +68,7 @@ export class AppProgressIndicationService {
}
private saveFocus() {
this.focused = $(':focus');
this.focused = document.querySelector(':focus');
}
private restoreFocus() {
@ -79,17 +79,14 @@ export class AppProgressIndicationService {
}
private disableEvents() {
let body = $('body');
body.keydown(AppProgressIndicationService.EVENT_INTERCEPTOR);
body.keyup(AppProgressIndicationService.EVENT_INTERCEPTOR);
body.contextmenu(AppProgressIndicationService.EVENT_INTERCEPTOR)
document.body.addEventListener('keydown', AppProgressIndicationService.EVENT_INTERCEPTOR);
document.body.addEventListener('keyup', AppProgressIndicationService.EVENT_INTERCEPTOR);
document.body.addEventListener('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);
document.body.removeEventListener('keydown', AppProgressIndicationService.EVENT_INTERCEPTOR);
document.body.removeEventListener('keyup', AppProgressIndicationService.EVENT_INTERCEPTOR);
document.body.removeEventListener('contextmenu', AppProgressIndicationService.EVENT_INTERCEPTOR);
}
}
}

View file

@ -0,0 +1,55 @@
import {NgModule} from "@angular/core";
import {NgbModule} from "@ng-bootstrap/ng-bootstrap";
import {
BpmnModule,
ComponentsModule,
ProgressIndicationService,
SecurityModule
} from "@webbpm/base-package";
import {CommonModule} from "@angular/common";
import {FormsModule} from "@angular/forms";
import {AgGridModule} from "ag-grid-angular";
import {RouterModule} from "@angular/router";
import {NgxIntlTelInputModule} from "ngx-intl-tel-input";
import {AppProgressIndicationComponent} from "./component/app-progress-indication.component";
import {AppProgressIndicationService} from "./service/app-progress-indication.service";
import {ConfigExecuteBtn} from "./component/ConfigExecuteBtn";
import {ExportDataBtn} from "./component/ExportDataBtn";
@NgModule({
imports: [
CommonModule,
FormsModule,
BpmnModule,
NgbModule,
SecurityModule,
ComponentsModule,
RouterModule,
NgxIntlTelInputModule,
AgGridModule
],
declarations: [
AppProgressIndicationComponent,
ConfigExecuteBtn,
ExportDataBtn
],
exports: [
AppProgressIndicationComponent,
ConfigExecuteBtn,
ExportDataBtn,
CommonModule,
FormsModule,
BpmnModule,
NgbModule,
SecurityModule,
ComponentsModule,
RouterModule,
NgxIntlTelInputModule,
AgGridModule
],
providers: [
{provide: ProgressIndicationService, useClass: AppProgressIndicationService}
],
})
export class SharedModule {
}

View file

@ -0,0 +1,29 @@
{
"compilerOptions": {
"target": "es2022",
"useDefineForClassFields": false,
"forceConsistentCasingInFileNames": true,
"strict": false,
"noImplicitOverride": false,
"noImplicitAny": false,
"noPropertyAccessFromIndexSignature": true,
"noImplicitReturns": false,
"noFallthroughCasesInSwitch": true,
"strictPropertyInitialization": false
},
"files": [],
"include": [],
"references": [
{
"path": "./tsconfig.lib.json"
}
],
"extends": "../../tsconfig.base.json",
"angularCompilerOptions": {
"enableI18nLegacyMessageIdFormat": false,
"strictPropertyInitialization": false,
"strictInjectionParameters": true,
"strictInputAccessModifiers": true,
"strictTemplates": false
}
}

View file

@ -0,0 +1,12 @@
{
"extends": "./tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc",
"declaration": true,
"declarationMap": true,
"inlineSources": true,
"types": []
},
"exclude": ["src/**/*.spec.ts", "jest.config.ts", "src/**/*.test.ts"],
"include": ["src/**/*.ts"]
}

View file

@ -0,0 +1,7 @@
{
"extends": "./tsconfig.lib.json",
"compilerOptions": {
"declarationMap": false
},
"angularCompilerOptions": {}
}

46
frontend/nx.json Normal file
View file

@ -0,0 +1,46 @@
{
"$schema": "./node_modules/nx/schemas/nx-schema.json",
"targetDefaults": {
"build": {
"cache": true,
"dependsOn": ["^build"],
"inputs": ["production", "^production"]
},
"@nx/angular:ng-packagr-lite": {
"cache": true,
"dependsOn": ["^build"],
"inputs": ["production", "^production"]
},
"@nx/eslint:lint": {
"cache": true,
"inputs": [
"default",
"{workspaceRoot}/.eslintrc.json",
"{workspaceRoot}/.eslintignore",
"{workspaceRoot}/eslint.config.cjs"
]
}
},
"defaultBase": "develop",
"parallel": 10,
"namedInputs": {
"sharedGlobals": [],
"default": ["{projectRoot}/**/*", "sharedGlobals"],
"production": [
"default",
"!{projectRoot}/.eslintrc.json",
"!{projectRoot}/eslint.config.cjs",
"!{projectRoot}/eslint.config.mjs"
]
},
"generators": {
"@nx/angular:library": {
"linter": "eslint",
"unitTestRunner": "none",
"strict": false
},
"@nx/angular:component": {
"style": "css"
}
}
}

27818
frontend/package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -2,99 +2,97 @@
"name": "ervu-eks",
"version": "1.0.0",
"scripts": {
"cleanup": "npm run cleanup-ngc && node ./node_modules/rimraf/bin ./build ./dist",
"cleanup-ngc": "node ./node_modules/rimraf/bin ./src/ts/**/*.js ./src/ts/**/*.json ./src/ts/page.routing.ts",
"cleanup-and-ngc": "npm run cleanup && npm run ngc",
"ngc": "node --max-old-space-size=14336 ./node_modules/@angular/compiler-cli/src/main -p tsconfig.aot.json",
"build-webpack": "node --max-old-space-size=14336 ./node_modules/webpack/bin/webpack --config webpack.aot.config.js --progress --profile",
"cleanup": "rimraf ./build ./build_dev ./dist ./tmp ./.angular ./.nx",
"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"
"serve": "nx serve --configuration development",
"build": "nx build --verbose --skip-nx-cache",
"build-dev": "nx build --verbose --configuration development",
"postinstall": "npm run generate-barrels --prefix modules/shared"
},
"dependencies": {
"@angular/animations": "7.2.15",
"@angular/common": "7.2.15",
"@angular/compiler": "7.2.15",
"@angular/core": "7.2.15",
"@angular/forms": "7.2.15",
"@angular/http": "7.2.15",
"@angular/platform-browser": "7.2.15",
"@angular/platform-browser-dynamic": "7.2.15",
"@angular/router": "7.2.15",
"@ng-bootstrap/ng-bootstrap": "4.2.2-micord.1",
"@webbpm/base-package": "3.185.0",
"ag-grid-angular": "29.0.0-micord.4",
"ag-grid-community": "29.0.0-micord.4",
"angular-calendar": "0.28.28",
"autonumeric": "4.5.10-cg",
"bootstrap": "4.3.1",
"bootstrap-icons": "1.10.3",
"cadesplugin_api": "2.0.4-micord.1",
"chart.js": "3.8.0-cg.1",
"chartjs-adapter-moment": "1.0.0",
"core-js": "2.4.1",
"date-fns": "2.29.3",
"downloadjs": "1.4.8",
"eonasdan-bootstrap-datetimepicker": "4.17.47-micord.5",
"esmarttokenjs": "2.2.1-cg",
"@angular/animations": "19.2.7",
"@angular/common": "19.2.7",
"@angular/compiler": "19.2.7",
"@angular/core": "19.2.7",
"@angular/forms": "19.2.7",
"@angular/platform-browser": "19.2.7",
"@angular/platform-browser-dynamic": "19.2.7",
"@angular/router": "19.2.7",
"@ng-bootstrap/ng-bootstrap": "18.0.0",
"@popperjs/core": "2.11.8",
"@webbpm/base-package": "5.8.2",
"ag-grid-angular": "29.0.0-micord.3334",
"ag-grid-community": "29.0.0-micord.3334",
"angular-calendar": "0.31.1",
"autonumeric": "4.10.8",
"barrelsby": "2.8.1",
"bootstrap": "5.3.3",
"bootstrap-icons": "1.10.5",
"cadesplugin_api": "2.1.1-micord.2222",
"chart.js": "4.5.0",
"chartjs-adapter-moment": "1.0.1",
"core-js": "3.31.0",
"date-fns": "2.30.0",
"eonasdan-bootstrap-datetimepicker": "4.17.47-micord.6",
"esmarttokenjs": "2.2.1-micord.3",
"font-awesome": "4.7.0",
"google-libphonenumber": "3.0.9",
"inputmask": "5.0.5-cg.2",
"google-libphonenumber": "3.2.40",
"inputmask": "5.0.10-beta.37",
"intl-tel-input": "17.0.21",
"jquery": "3.7.1",
"js-year-calendar": "1.0.0-cg.2",
"jsgantt-improved": "2.0.10-cg",
"jsgantt-improved": "2.0.9-cg.1",
"moment": "2.30.1",
"moment-timezone": "0.5.46",
"ngx-cookie": "3.0.1",
"ngx-international-phone-number": "1.0.6",
"ngx-toastr": "10.2.0-cg",
"popper.js": "1.14.7",
"reflect-metadata": "0.1.13",
"rxjs": "6.4.0",
"rxjs-compat": "6.4.0",
"selectize": "0.12.4-cg.11",
"systemjs": "0.21.4",
"systemjs-plugin-babel": "0.0.25",
"tslib": "1.9.3",
"zone.js": "0.11.8"
"ngx-cookie": "6.0.1",
"ngx-intl-tel-input": "3.3.0-micord.2",
"ngx-toastr": "19.0.0",
"rxjs": "7.8.1",
"selectize": "0.12.4-cg.15",
"tslib": "2.6.2",
"zone.js": "0.15.0"
},
"devDependencies": {
"@angular-devkit/build-optimizer": "0.13.9",
"@angular-devkit/core": "7.3.9",
"@angular/cli": "7.3.9",
"@angular/compiler-cli": "7.2.15",
"@angular/platform-server": "7.2.15",
"@babel/core": "7.18.10",
"@babel/preset-env": "7.18.10",
"@types/bootstrap": "3.3.39",
"@types/eslint": "7.2.5",
"@types/jquery": "3.5.5",
"@types/node": "7.0.5",
"@types/selectize": "0.12.33",
"ajv": "8.8.2",
"angular-router-loader": "0.8.5",
"angular2-template-loader": "0.6.2",
"babel-loader": "9.1.2",
"codelyzer": "5.2.1",
"copy-webpack-plugin": "5.0.3",
"cross-env": "5.2.1",
"css-loader": "6.11.0",
"@angular-devkit/build-angular": "19.2.7",
"@angular-devkit/core": "19.2.7",
"@angular-devkit/schematics": "19.2.7",
"@angular-eslint/eslint-plugin": "19.3.0",
"@angular-eslint/eslint-plugin-template": "19.3.0",
"@angular-eslint/template-parser": "19.3.0",
"@angular/compiler-cli": "19.2.7",
"@angular/localize": "19.2.7",
"@angular/platform-server": "19.2.7",
"@eslint/js": "^9.8.0",
"@nx/angular": "21.0.3",
"@nx/eslint": "21.0.3",
"@nx/eslint-plugin": "21.0.3",
"@nx/js": "21.0.3",
"@nx/module-federation": "21.0.3",
"@nx/workspace": "21.0.3",
"@schematics/angular": "19.2.7",
"@swc-node/register": "1.9.2",
"@swc/core": "1.5.29",
"@swc/helpers": "0.5.15",
"@types/bootstrap": "5.2.10",
"@types/jquery": "3.5.16",
"@types/node": "22.17.0",
"@types/selectize": "0.12.35",
"@typescript-eslint/utils": "^8.19.0",
"angular-eslint": "19.3.0",
"autoprefixer": "^10.4.0",
"del": "2.2.2",
"file-loader": "6.2.0",
"html-webpack-plugin": "5.6.0",
"mini-css-extract-plugin": "2.9.1",
"mkdirp": "3.0.1",
"raw-loader": "4.0.2",
"style-loader": "3.3.4",
"terser-webpack-plugin": "5.3.10",
"tslint": "5.13.1",
"typescript": "3.2.4",
"typescript-parser": "2.6.1-cg.2",
"webpack": "5.90.1",
"webpack-cli": "5.0.2"
"eslint": "^9.8.0",
"eslint-config-prettier": "10.0.0",
"jsonc-eslint-parser": "^2.1.0",
"ng-extract-i18n-merge": "2.15.1",
"ng-packagr": "19.2.0",
"nx": "21.0.3",
"postcss": "^8.4.5",
"postcss-url": "~10.1.3",
"prettier": "^2.6.2",
"rimraf": "6.0.1",
"typescript": "5.8.2",
"typescript-eslint": "^8.19.0",
"typescript-parser": "2.6.1-micord-angular.19"
}
}

View file

@ -4,15 +4,32 @@
<parent>
<groupId>ru.micord.ervu</groupId>
<artifactId>eks</artifactId>
<version>1.0.0-SNAPSHOT</version>
<version>1.1.1</version>
</parent>
<groupId>ru.micord.ervu.eks</groupId>
<artifactId>frontend</artifactId>
<packaging>war</packaging>
<properties>
<enable.version.in.url>false</enable.version.in.url>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-clean-plugin</artifactId>
<configuration>
<filesets>
<fileset>
<directory>${basedir}/.nx</directory>
<directory>${basedir}/.angular</directory>
<directory>${basedir}/tmp</directory>
<directory>${basedir}/dist</directory>
</fileset>
</filesets>
</configuration>
</plugin>
<plugin>
<groupId>com.google.code.maven-replacer-plugin</groupId>
<artifactId>replacer</artifactId>
@ -28,10 +45,7 @@
</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>
@ -63,27 +77,34 @@
<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>
<directory>${basedir}/build_dev</directory>
</resource>
</webResources>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<executions>
<execution>
<id>frontend build</id>
<goals>
<goal>exec</goal>
</goals>
<phase>prepare-package</phase>
<configuration>
<executable>npm</executable>
<arguments>
<argument>run</argument>
<argument>build-dev</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
<profile>
<id>compile-ts</id>
<activation>
<activeByDefault>false</activeByDefault>
</activation>
</profile>
<profile>
<id>prod</id>
<build>
@ -100,6 +121,26 @@
</webResources>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<executions>
<execution>
<id>frontend build</id>
<goals>
<goal>exec</goal>
</goals>
<phase>prepare-package</phase>
<configuration>
<executable>npm</executable>
<arguments>
<argument>run</argument>
<argument>build</argument>
</arguments>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>

View file

@ -1,23 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<title>Web BPM</title>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<link rel="icon" type="image/png" href="src/resources/img/logo.png"/>
<link rel="stylesheet" href="src/resources/css/style.css"/>
<script src="node_modules/core-js/client/shim.min.js"></script>
<script src="node_modules/zone.js/dist/zone.js"></script>
<script src="node_modules/reflect-metadata/Reflect.js"></script>
<script src="node_modules/systemjs/dist/system.src.js"></script>
<script src="systemjs.preview.config.js"></script>
<script>
System.import('preview').catch(function (err) {
console.error(err);
});
</script>
</head>
<body preview-container id="webbpm-angular-application-container ervu-eks" class="webbpm">
<div class="progress"></div>
</body>
</html>

123
frontend/project.json Normal file
View file

@ -0,0 +1,123 @@
{
"$schema": "node_modules/nx/schemas/project-schema.json",
"projectType": "application",
"name": "ervu-eks",
"i18n": {
"sourceLocale": "ru",
"locales": {
"en": "src/resources/locale/messages.en.xlf"
}
},
"sourceRoot": "./src",
"tags": [],
"targets": {
"build": {
"executor": "@nx/angular:webpack-browser",
"outputs": ["{options.outputPath}"],
"options": {
"preserveSymlinks": true,
"main": "src/ts/main.ts",
"index": "index.html",
"polyfills": ["zone.js", "@angular/localize/init"],
"tsConfig": "tsconfig.json",
"assets": [
"src/resources"
],
"resourcesOutputPath": "./resources",
"styles": [
"src/resources/css/style.css"
],
"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/selectize/dist/js/selectize.min.js",
"node_modules/eonasdan-bootstrap-datetimepicker/build/js/bootstrap-datetimepicker.min.js",
"node_modules/esmarttokenjs/esmarttoken.js",
"node_modules/js-year-calendar/dist/js-year-calendar.js"
],
"i18nMissingTranslation": "warning"
},
"configurations": {
"production": {
"outputPath": "dist",
"fileReplacements": [
{
"replace": "src/ts/modules/preview/preview.routes.ts",
"with": "src/ts/modules/preview/preview.routes.prod.ts"
},
{
"replace": "src/ts/modules/webbpm/handler/global-error.handler.ts",
"with": "src/ts/modules/webbpm/handler/global-error.handler.prod.ts"
},
{
"replace": "src/ts/modules/webbpm/interceptor/default-interceptors.ts",
"with": "src/ts/modules/webbpm/interceptor/default-interceptors.prod.ts"
},
{
"replace": "src/ts/environments/environment.ts",
"with": "src/ts/environments/environment.prod.ts"
}
],
"budgets": [
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "10mb"
},
{
"type": "anyComponentStyle",
"maximumWarning": "2kb",
"maximumError": "4kb"
}
],
"outputHashing": "all",
},
"development": {
"outputPath": "build_dev",
"buildOptimizer": false,
"optimization": false,
"vendorChunk": true,
"extractLicenses": false,
"sourceMap": true,
"outputHashing": "none"
},
"ru": {
"localize": ["ru"]
},
"en": {
"localize": ["en"]
}
},
"defaultConfiguration": "production"
},
"serve": {
"executor": "@nx/angular:dev-server",
"configurations": {
"production": {
"buildTarget": "ervu-eks:build:production"
},
"development": {
"buildTarget": "ervu-eks:build:development"
}
},
"defaultConfiguration": "development"
},
"extract-i18n": {
"executor": "ng-extract-i18n-merge:ng-extract-i18n-merge",
"options": {
"buildTarget": "ervu-eks:build",
"outputPath": "src/resources/locale",
"format": "xliff2",
"targetFiles": ["messages.en.xlf"]
}
},
"serve-static": {
"executor": "@nx/web:file-server",
"options": {
"buildTarget": "ervu-eks:build:development",
"spa": true
}
}
}
}

View file

@ -6,7 +6,7 @@ var ts = require("typescript");
var parser = new tsp.TypescriptParser();
var excludedDirs = [
'generated-sources'
path.resolve(__dirname, "modules", "generated")
];
var walkFileTree = function (dir, action) {
@ -15,14 +15,14 @@ var walkFileTree = function (dir, action) {
}
fs.readdirSync(dir).forEach(function (file) {
var path = dir + "/" + file;
var stat = fs.statSync(path);
var filePath = dir + path.sep + file;
var stat = fs.statSync(filePath);
var extension = ".ts";
if (stat && stat.isDirectory() && excludedDirs.indexOf(file) === -1) {
walkFileTree(path, action);
if (stat && stat.isDirectory() && excludedDirs.indexOf(filePath) === -1) {
walkFileTree(filePath, action);
}
else if (path.indexOf(extension, path.length - extension.length) !== -1) {
action(null, path);
else if (filePath.indexOf(extension, filePath.length - extension.length) !== -1) {
action(null, filePath);
}
});
};
@ -30,28 +30,57 @@ var walkFileTree = function (dir, action) {
var dateInLong = Date.now();
var arr = [];
var basePath = path.resolve(__dirname, "src/ts/");
walkFileTree(basePath, function (err, file) {
function parseTsMeta(file,childModule) {
var content = fs.readFileSync(file).toString();
var jsonStructure = parser.parseTypescript(ts.createSourceFile(
file,
content,
ts.ScriptTarget.Latest,
true,
ts.ScriptKind.TS
file,
content,
ts.ScriptTarget.Latest,
true,
ts.ScriptKind.TS
),
'/');
jsonStructure['packageName'] = path.relative(path.resolve(__dirname, "src/ts/"),jsonStructure['filePath']);
if (childModule) {
let moduleRelativePath = path.relative(path.resolve(__dirname, "modules"), file);
let pathElements = moduleRelativePath.split(path.sep);
jsonStructure['moduleName'] = pathElements.shift();
pathElements.shift(); //remove src folder
pathElements.shift(); //remove lib folder
jsonStructure['packageName'] = pathElements.join(path.sep);
}else {
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']);
if (childModule) {
// let subPath = "modules" + path.sep + jsonStructure['moduleName'] + path.sep + "src" + path.sep + "lib";
val['libraryName'] = path.relative(path.resolve(__dirname, "modules", jsonStructure['moduleName'], "src", "lib"), val['libraryName']);
}
else {
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 basePath = path.resolve(__dirname, "src", "ts");
walkFileTree(basePath, function (err, file) {
parseTsMeta(file, false);
});
walkFileTree(path.resolve(__dirname, "modules"), function (err, file) {
parseTsMeta(file,true);
});
var cache = [];
@ -69,4 +98,4 @@ fs.writeFileSync("./../.studio/typescript.metadata.json",
}));
cache = null;
console.log("typescript parse time = " + (Date.now() - dateInLong));
console.log("typescript parse time = " + (Date.now() - dateInLong));

View file

@ -1,10 +1,12 @@
{
"electronic_sign.esmart_extension_url": "",
"electronic_sign.tsp_address": "",
"filter_cleanup_interval_hours": 720,
"filter_cleanup_check_period_minutes": 30,
"version": "%project.version%",
"electronic_sign.esmart_plugin.tsp_address": "",
"electronic_sign.cades_plugin.tsp_address": "",
"filter_cleanup_interval_hours": 720.0,
"filter_cleanup_check_period_minutes": 30.0,
"auth_method": "form",
"enable.version.in.url": "false",
"enable.version.in.url": "%enable.version.in.url%",
"backend.context": "/ervu-eks",
"guard.confirm_exit": false,
"message_service_error_timeout": "",
"message_service_warning_timeout": "",
@ -13,7 +15,6 @@
"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

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

View file

@ -0,0 +1,92 @@
/*----------------- Buttons ---------------- */
.webbpm .btn {
font-size: var(--size-text-primary);
font-family: 'SegoeSB';
padding: 6px 14px;
margin-bottom: 0;
border-radius: 20px;
}
.webbpm .btn:focus,
.webbpm .btn:active:focus {
box-shadow: none !important;
}
.webbpm .btn-main {
border-color: transparent !important;
background: transparent !important;
}
.webbpm .btn-main > * > .btn,
.webbpm .btn-primary {
color: var(--white) !important;
border-color: var(--color-link);
background: var(--color-link);
box-shadow: 0px 0px 6px 2px rgb(77 72 91 / 6%);
}
.webbpm .btn-main > * > .btn:not(:disabled):not(.disabled):hover,
.webbpm .btn-main > * > .btn:not(:disabled):not(.disabled):active,
.webbpm .btn-primary:hover,
.webbpm .btn-primary:active {
border-color: #1b84d2;
background: #1b84d2;
}
.webbpm .btn-main > * > .btn:not(:disabled):not(.disabled):focus,
.webbpm .btn-primary:focus {
border-color: var(--color-link-hover);
background: var(--color-link-hover);
box-shadow: none;
}
.webbpm .btn-secondary,
.webbpm .btn-default {
color: var(--color-text-primary);
border: 1px solid var(--border-light);
background: var(--white);
box-shadow: 0px 0px 6px 2px rgb(77 72 91 / 6%);
}
.webbpm .btn-secondary:not(:disabled):not(.disabled):hover,
.webbpm .btn-secondary:not(:disabled):not(.disabled):active,
.webbpm .btn-default:not(:disabled):not(.disabled):hover,
.webbpm .btn-default:not(:disabled):not(.disabled):active {
color: var(--color-link);
border-color: var(--border-light);
background-color: var(--white);
}
.webbpm .btn-secondary:not(:disabled):not(.disabled):focus,
.webbpm .btn-default:not(:disabled):not(.disabled):focus {
border-color: var(--border-light);
background-color: var(--white);
box-shadow: none;
}
.webbpm .btn.disabled,
.webbpm .btn:disabled {
color: #666;
border-color: #f3f3f3;
background-color: #f3f3f3;
box-shadow: none;
}
.webbpm .btn.btn-primary.disabled,
.webbpm .btn.btn-primary:disabled,
.webbpm .btn-main .btn.disabled,
.webbpm .btn-main .btn:disabled {
color: rgba(255, 255, 255, 0.8) !important;
border-color: var(--color-link);
background-color: var(--color-link);
box-shadow: none;
}
.webbpm ag-grid-angular .state-btn-edit,
.webbpm ag-grid-angular .state-btn-delete {
color: var(--color-link) !important;
border: 0;
background: transparent;
box-shadow: none;
}
.webbpm button.close {
outline: none;
}
/*---------------- end - Buttons -------------- */

View file

@ -1,48 +1,39 @@
@charset "utf-8";
.webbpm div[id="page"],
.webbpm div[id="page"] > div,
.webbpm div[id="page"] > div > div {
.webbpm .container-inside > div,
.webbpm .container-inside > div > div {
display: flex;
flex-direction: column;
height: 100%;
}
.webbpm [id="page"] > div > div > [page-object]:first-child {
padding-top: 15px;
}
.webbpm [id="page"] > div > div > [page-object] {
.webbpm .container-inside > div > div {
display: inherit;
margin-bottom: 10px;
padding: 0 40px;
}
.webbpm #dashboard {
padding: 15px 40px 0 40px;
margin-bottom: var(--indent-small);
padding: var(--indent-base) var(--w-screen) 0 var(--w-screen);
}
.webbpm .title {
font-size: 24px;
font-size: var(--size-text-title);
font-family: 'SegoeSB';
padding: 10px 0 0 0;
padding: var(--indent-small) 0 0 0;
}
.webbpm .sub-header {
color: var(--white);
font-weight: normal;
padding: 0 20px;
margin-top: 10px;
padding: 0 var(--indent-xbase);
margin-top: var(--indent-small);
border-radius: 20px;
background: var(--bg-secondary);
& > .form-group {
padding: 10px 0;
padding: var(--indent-small) 0;
margin-bottom: 0;
}
& > div > div:last-of-type {
font-size: 18px;
font-size: var(--size-text-header);
padding: 0;
}
}
@ -50,7 +41,8 @@
.webbpm .form-signup input.ng-invalid.ng-touched,
.webbpm .form-signup input.ng-invalid.ng-touched ~ .input-group-append > .input-group-text,
.webbpm .form-signup input.ng-invalid.ng-dirty,
.webbpm .form-signup input.ng-invalid.ng-dirty ~ .input-group-append > .input-group-text {
.webbpm .form-signup input.ng-invalid.ng-dirty ~ .input-group-append > .input-group-text,
.webbpm .form-signup ngx-intl-tel-input.ng-invalid.ng-dirty.ng-touched .iti {
border-color: var(--color-link) !important;
}
@ -70,7 +62,7 @@
.webbpm .multi.plugin-remove_button .selectize-input {
color: var(--color-text-primary);
font-size: var(--size-text-primary);
min-height: 38px;
min-height: var(--h-input);
padding: 6px 8px;
border: 1px solid var(--border-light);
border-radius: 8px;
@ -95,53 +87,42 @@
color: var(--color-link);
}
.webbpm .input-group input[name="password"],
.webbpm .input-group input[name="confirmPassword"],
.webbpm .date input[type="text"] {
.webbpm .input-group :is(input[name="password"], input[name="confirmPassword"], input[type="text"]) {
border-right: 0 ;
border-top-right-radius: 0;
border-bottom-right-radius: 0 ;
}
border-bottom-right-radius: 0;
.webbpm .input-group input[name="password"] ~ .input-group-append > .input-group-text,
.webbpm .input-group input[name="confirmPassword"] ~ .input-group-append > .input-group-text,
.webbpm .date input ~ .input-group-addon {
color: var(--color-text-primary);
border-color: var(--border-light);
border-radius: 8px;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
background-color: transparent;
}
.webbpm .input-group input[name="password"] ~ .input-group-append > .input-group-text,
.webbpm .input-group input[name="confirmPassword"] ~ .input-group-append > .input-group-text,
.webbpm .date input ~ .input-group-addon {
color: var(--color-text-primary);
border-color: var(--border-light);
border-radius: 8px;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
background-color: transparent;
}
.webbpm international-phone-number .input-group-addon.flagInput {
border: 0;
background-color: transparent;
.btn {
border: 1px solid var(--border-light);
border-right: 0;
border-radius: 10px;
border-top-right-radius: 0;
border-bottom-right-radius: 0;
& ~ :is(.input-group-append, .input-group-addon) {
display: flex;
align-items: center;
}
}
& ~ input {
border-right: 1px solid var(--border-light);
border-radius: 10px;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
.webbpm .input-group input[name="password"] ~ .input-group-append > .input-group-text,
.webbpm .input-group input[name="confirmPassword"] ~ .input-group-append > .input-group-text,
.webbpm .date input ~ .input-group-addon {
color: var(--color-text-primary);
min-height: var(--h-input);
border-color: var(--border-light);
border-radius: 8px;
border-top-left-radius: 0;
border-bottom-left-radius: 0;
background-color: transparent;
}
.webbpm ngx-intl-tel-input .iti {
width: 100%;
border: 1px solid var(--border-light);
border-radius: 8px;
.iti__flag-container {
button, & ~ input {
border: 0;
background-color: transparent;
}
&:hover button.iti__selected-flag {
background-color: transparent;
}
}
}
@ -161,13 +142,15 @@
background-color: transparent;
}
.webbpm .form-group > label,
.webbpm .form-group > div {
display: inline-block;
vertical-align: top;
.webbpm .form-group {
margin-bottom: var(--indent-base);
& > :is(label, div) {
display: inline-block;
vertical-align: top;
}
}
.webbpm .width-full .form-group > label,
.webbpm .width-full .form-group > div {
.webbpm .width-full .form-group > :is(label, div) {
display: table-cell;
}
@ -181,11 +164,11 @@
.webbpm .date .form-control[readonly] ~ .input-group-addon,
.webbpm fieldset[disabled] .form-control,
.webbpm .selectize-control .selectize-input.disabled {
background-color: var(--bg-light);
background-color: var(--bg-light);
}
.webbpm .selectize-control.form-control {
min-height: 38px;
min-height: var(--h-input);
}
.webbpm .selectize-control.single .selectize-input > .item {
@ -205,8 +188,8 @@
}
.webbpm drop-down-button .dropdown-menu.show .btn {
padding-top: 4px;
padding-bottom: 4px;
padding-top: var(--indent-xmini);
padding-bottom: var(--indent-xmini);
border: 0;
box-shadow: none;
}
@ -220,105 +203,12 @@
background-color: #f0f7fd;
}
/*----------------- Button ---------------- */
.webbpm .btn {
font-size: var(--size-text-primary);
font-family: 'SegoeSB';
padding: 6px 14px;
margin-bottom: 0;
border-radius: 20px;
}
.webbpm .btn:focus,
.webbpm .btn:active:focus {
box-shadow: none !important;
}
.webbpm .btn-main {
border-color: transparent !important;
background: transparent !important;
}
.webbpm .btn-main > * > .btn,
.webbpm .btn-primary {
color: var(--white) !important;
border-color: var(--color-link);
background: var(--color-link);
box-shadow: 0px 0px 6px 2px rgb(77 72 91 / 6%);
}
.webbpm .btn-main > * > .btn:not(:disabled):not(.disabled):hover,
.webbpm .btn-main > * > .btn:not(:disabled):not(.disabled):active,
.webbpm .btn-primary:hover,
.webbpm .btn-primary:active {
border-color: #1b84d2;
background: #1b84d2;
}
.webbpm .btn-main > * > .btn:not(:disabled):not(.disabled):focus,
.webbpm .btn-primary:focus {
border-color: #1b84d2;
background: #1b84d2;
box-shadow: none;
}
.webbpm .btn-secondary,
.webbpm .btn-default {
color: var(--color-text-primary);
border: 1px solid var(--border-light);
background: var(--white);
box-shadow: 0px 0px 6px 2px rgb(77 72 91 / 6%);
}
.webbpm .btn-secondary:not(:disabled):not(.disabled):hover,
.webbpm .btn-secondary:not(:disabled):not(.disabled):active,
.webbpm .btn-default:not(:disabled):not(.disabled):hover,
.webbpm .btn-default:not(:disabled):not(.disabled):active {
color: var(--color-link);
border-color: var(--border-light);
background-color: var(--white);
}
.webbpm .btn-secondary:not(:disabled):not(.disabled):focus,
.webbpm .btn-default:not(:disabled):not(.disabled):focus {
border-color: var(--border-light);
background-color: var(--white);
box-shadow: none;
}
.webbpm .btn.disabled,
.webbpm .btn:disabled {
color: #666;
border-color: #f3f3f3;
background-color: #f3f3f3;
box-shadow: none;
}
.webbpm .btn.btn-primary.disabled,
.webbpm .btn.btn-primary:disabled,
.webbpm .btn-main .btn.disabled,
.webbpm .btn-main .btn:disabled {
color: rgba(255, 255, 255, 0.8) !important;
border-color: var(--color-link);
background-color: var(--color-link);
box-shadow: none;
}
.webbpm ag-grid-angular .state-btn-edit,
.webbpm ag-grid-angular .state-btn-delete {
color: var(--color-link) !important;
border: 0;
background: transparent;
box-shadow: none;
}
.webbpm button.close {
outline: none;
}
/*---------------- end - Button -------------- */
/*----------------- Bread-crumb -------------- */
.webbpm bread-crumb {
display: flex;
flex-direction: column;
max-width: 800px;
margin-top: 20px;
margin-top: var(var(--indent-xbase));
.status_bar {
position: relative;
@ -329,7 +219,7 @@
.crumb-element {
color: var(--white);
font-size: var(--size-text-secondary);
padding: 4px 15px;
padding: var(--indent-xmini) var(--indent-base);
background-color: var(--bg-secondary);
border-radius: 20px;
z-index: 1;
@ -376,12 +266,16 @@
}
.webbpm .container-inside > div > div > grid,
.webbpm .container-inside > div > div > grid-v2-with-project-defined-sidebar{
.webbpm .container-inside > div > div > grid-v2-with-project-defined-sidebar {
height: auto !important;
flex: 1 1 auto;
overflow: hidden;
}
.webbpm .grid-full-height {
flex: 1 1 auto;
}
.webbpm ag-grid-angular .ag-body-viewport,
.webbpm ag-grid-angular .ag-row-odd,
.webbpm ag-grid-angular .ag-row-even {
@ -1161,6 +1055,16 @@
margin-right: 6px;
}
.webbpm ag-grid-angular .ag-combobox-filter > select,
.webbpm ag-grid-angular .ag-combobox-floating-filter > select {
font-size: var(--size-text-secondary);
min-height: auto;
padding: 0;
border: 0;
background-color: transparent;
outline: transparent;
}
.webbpm .modal-content {
border: 0;
border-radius: 15px;
@ -1237,22 +1141,22 @@
.webbpm collapsible-panel:not(.grid-setting-panel):not(.column-states-panel):not(.filter-states-panel) .card-header,
.webbpm .fieldset > legend {
font-family: 'SegoeSB';
font-size: 18px;
padding: 15px 20px;
font-size: var(--size-text-header);
padding: var(--indent-base) var(--indent-xbase);
border: 0;
background: transparent;
}
.webbpm collapsible-panel:not(.grid-setting-panel):not(.column-states-panel):not(.filter-states-panel) .card-header .font-bold {
margin-top: 10px;
margin-top: var(--indent-small);
}
.webbpm collapsible-panel:not(.grid-setting-panel):not(.column-states-panel):not(.filter-states-panel) .card-block {
padding: 0 20px 15px 20px;
padding: 0 var(--indent-xbase) var(--indent-base) var(--indent-xbase);
}
.webbpm .fieldset > .legend + div {
padding: 55px 20px 15px 20px;
padding: 55px var(--indent-xbase) var(--indent-base) var(--indent-xbase);
}
.webbpm collapsible-panel:not(.grid-setting-panel):not(.column-states-panel):not(.filter-states-panel) .card i {
@ -1279,12 +1183,12 @@
.webbpm [id^="user-management-"],
.webbpm #process-instance-list,
.webbpm #process-instance {
padding: 0 40px;
padding: 0 var(--w-screen);
}
.webbpm [id^="user-management-"] > vbox > div > *:not(.title),
.webbpm #process-instance-list > vbox > div > *:not(.title) {
margin-bottom: 20px;
margin-bottom: var(--indent-xbase);
}
.webbpm [id^="user-management-"] .card label,
@ -1303,7 +1207,7 @@
}
.webbpm #process-instance-list static-column-grid {
margin-top: 20px;
margin-top: var(--indent-xbase);
}
.webbpm #process-instance-list .radio-block .radio-content {
@ -1317,7 +1221,7 @@
}
.webbpm #process-instance bpmn-back-button {
margin: 20px 0;
margin: var(--indent-xbase) 0;
}
/*----------- end Admin-pages ------------- */
@ -1331,7 +1235,7 @@
.webbpm .calendar .month-container {
float: none;
height: max-content;
margin-bottom: 20px;
margin-bottom: var(--indent-xbase);
}
.webbpm .calendar .month .day {
@ -1355,10 +1259,9 @@
.webbpm .date .bootstrap-datetimepicker-widget {
color: var(--color-text-primary);
width: min-content;
padding: 10px;
padding: var(--indent-small);
margin: 0;
border: 1px solid var(--border-light);
border-radius: 10px;
background-color: var(--white);
box-shadow: 0 8px 12px rgb(77 72 91 / 5%), 0 6px 10px rgb(77 72 91 / 0%);
@ -1397,7 +1300,7 @@
.webbpm .btn-group-viewdate.btn-group .btn {
width: 38px;
height: 38px;
border-radius: 19px;
border-radius: 20px;
cursor: pointer;
}
@ -1414,18 +1317,18 @@
}
.webbpm .btn-group-viewperiod.btn-group .btn.active:hover,
.webbpm .btn-group-viewperiod.btn-group .btn.active:active {
border-color: #1b84d2 !important;
background: #1b84d2 !important;
border-color: var(--color-link-hover) !important;
background: var(--color-link-hover) !important;
}
.webbpm .btn-group-viewperiod.btn-group .btn.active:focus {
border-color: #1b84d2 !important;
background: #1b84d2 !important;
border-color: var(--color-link-hover) !important;
background: var(--color-link-hover) !important;
box-shadow: none;
}
.webbpm .btn-group-viewdate.btn-group .btn + .btn,
.webbpm .btn-group-viewperiod.btn-group .btn + .btn {
margin-left: 10px;
margin-left: var(--indent-small);
}
.webbpm .cal-week-view .cal-header.cal-weekend span {

View file

@ -20,12 +20,13 @@
}
.webbpm a {
color: var(--color-link);
color: var(--color-link);
text-decoration: none;
&:hover,
&:focus,
&:active {
color: var(--color-link-hover);
text-decoration: none;
}
}
@ -38,7 +39,7 @@ body.webbpm {
}
.webbpm .container {
padding: 70px 0 0;
padding: var(--h-header) 0 0;
}
body.webbpm [id="page"],
@ -56,7 +57,7 @@ body.webbpm [id="page"],
display: flex;
flex-direction: row;
align-items: center;
margin-left: 40px;
margin-left: var(--w-screen);
.logo a {
background: url('../../../src/resources/img/logo-full.png') no-repeat 0 50%;
@ -67,9 +68,9 @@ body.webbpm [id="page"],
display: flex;
flex-direction: row;
margin-left: auto;
margin-right: 40px;
margin-right: var(--w-screen);
& > * {
margin-right: 20px;
margin-right: var(--indent-xbase);
&:last-child {
margin-right: 0;
}
@ -102,14 +103,14 @@ body.webbpm [id="page"],
display: flex;
flex-direction: column;
color: var(--black);
padding: 4px 20px;
padding: var(--indent-xmini) var(--indent-xbase);
background: transparent;
cursor: default;
& > * {
display: flex;
padding-bottom: 10px;
margin: 0 0 10px 0;
padding-bottom: var(--indent-small);
margin: 0 0 var(--indent-small) 0;
border-bottom: 1px solid #f1f5f9;
&:last-child {
margin-bottom: 0;
@ -134,7 +135,7 @@ body.webbpm [id="page"],
font-family: 'Segoe';
width: 100%;
height: auto;
min-height: 70px;
min-height: var(--h-header);
border-bottom: 1px solid var(--bg-light);
background: var(--white);
box-shadow: 0px 15px 20px 0px rgb(0 0 0 / 4%);
@ -146,7 +147,7 @@ body.webbpm [id="page"],
}
.dropdown-menu.show {
top: 69px !important;
top: calc(var(--h-header) - 1px) !important;
right: 0px !important;
left: auto !important;
transform: none !important;
@ -157,13 +158,13 @@ body.webbpm [id="page"],
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);
max-height: calc(100vh - var(--h-header)*2);
overflow-y: auto;
}
}
:is(process, admin-menu) .dropdown-menu.show {
top: 49px !important;
:is(process, admin-menu) .dropdown.show {
position: static;
}
.logout .dropdown-menu.show {
@ -176,7 +177,7 @@ body.webbpm [id="page"],
}
.webbpm .dropdown-item {
padding: 4px 20px;
padding: var(--indent-xmini) var(--indent-xbase);
&:hover,
&:focus,
&:active {
@ -191,7 +192,7 @@ body.webbpm [id="page"],
font-size: var(--size-text-secondary);
left: 0;
right: 0;
padding: 15px 40px;
padding: var(--indent-base) var(--w-screen);
border-top: 1px solid var(--border-light);
a {
color: var(--color-text-primary);
@ -203,7 +204,7 @@ body.webbpm [id="page"],
/*-------------- Menu tasks -------------- */
.webbpm .task-list-tree-panel {
padding: 0 40px;
padding: 0 var(--w-screen);
.task-list-filter {
font-family: 'Segoe';
box-shadow: none;
@ -225,7 +226,7 @@ body.webbpm [id="page"],
}
.webbpm .task-list-workplace {
padding: 20px 40px 0 40px;
padding: 20px var(--w-screen) 0 var(--w-screen);
}
.webbpm .task-tbl :is(.tr.task-ontime, .tr.task-overdue) > .td.task::before {

View file

@ -8,8 +8,24 @@
--bg-secondary: #4c5969;
--border-light: #e3e6ed;
--size-text-title: 24px;
--size-text-subtitle: 20px;
--size-text-header: 18px;
--size-text-primary: 16px;
--size-text-secondary: 14px;
--size-text-small: 12px;
--indent-medium: 24px;
--indent-xbase: 20px;
--indent-base: 16px;
--indent-small: 12px;
--indent-mini: 8px;
--indent-xmini: 4px;
--h-input: 38px;
--h-header: 70px;
--h-footer: 50px;
--w-screen: 40px;
}
* {
@ -23,7 +39,7 @@
box-sizing: border-box;
}
body.webbpm .form-signin label {
.webbpm .form-signin label {
width: 160px;
margin-right: 0;
}
@ -36,7 +52,7 @@ body.webbpm .form-signin label {
background: url("../img/progress.gif") no-repeat 0 0;
}
.webbpm > .progress {
.webbpm .progress {
top: 50%;
left: 50%;
margin-top: -30px;
@ -72,18 +88,15 @@ body.webbpm .form-signin label {
list-style: none;
}
.webbpm :is(h1, h2, h3) {
.webbpm :is(h1, h2) {
margin: 0;
font-weight: normal;
}
.webbpm h1 {
font-size: 2.33em;
font-size: var(--size-text-title);
}
.webbpm h2 {
font-size: 2em;
}
.webbpm h3 {
font-size: 1.5em;
font-size: var(--size-text-subtitle);
}
.webbpm .table {
@ -99,13 +112,13 @@ body.webbpm .form-signin label {
}
/*-- layout --*/
html, body.webbpm {
html, .webbpm, .webbpm app-root {
width: 100%;
height: 100%;
display: block;
}
body.webbpm {
.webbpm {
background-color: #f9f9fa;
font-family: Arial;
font-size: var(--size-text-secondary);
@ -124,12 +137,12 @@ body.webbpm {
max-width: 100%;
height: auto;
margin: 0;
padding: 67px 0 0;
padding: var(--h-header) 0 0;
position: absolute;
top: 0px;
left: 0;
right: 0;
bottom: 50px;
bottom: var(--h-footer);
border: 0;
overflow: hidden;
@ -151,17 +164,17 @@ body.webbpm {
.webbpm footer {
position: absolute;
color: var(--black);
font-size: 12px;
font-size: var(--size-text-small);
bottom: 0;
left: 15px;
right: 15px;
height: 50px;
padding: 15px 0;
left: var(--indent-base);
right: var(--indent-base);
height: var(--h-footer);
padding: var(--indent-base) 0;
border-top: 1px solid #c1c1c1;
background: transparent;
span + span {
padding-left: 20px;
padding-left: var(--indent-medium);
}
}
/*--------- TOP MENU ----------*/
@ -172,7 +185,7 @@ body.webbpm {
a {
width: 200px;
height: 67px;
height: var(--h-header);
position: absolute;
background: url("../img/logo.png") no-repeat 0 0;
}
@ -181,40 +194,37 @@ body.webbpm {
.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;
height: var(--h-header);
min-height: var(--h-header);
line-height: normal;
border: 0;
padding: 0;
background: #b9c0ca;
z-index: 997;
.nav .nav-link {
.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 {
.nav-link:hover {
text-decoration: none;
}
}
.webbpm .dropdown-menu {
background-color: #eee;
background-color: var(--bg-light);
}
.webbpm .nav .nav-item .dropdown-menu:after {
border-bottom: 6px solid #eee;
border-bottom: 6px solid var(--bg-light);
}
.webbpm .inner {
@ -234,30 +244,6 @@ body.webbpm {
}
/*--------- 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 {
@ -282,7 +268,7 @@ body.webbpm {
.task-list-tree-panel .task-list-filter {
position: relative;
margin-top: 15px;
margin-top: var(--indent-base);
z-index: 10;
}
@ -416,7 +402,7 @@ body.webbpm {
/*--------------Окно сообщения об ошибке--------------*/
.webbpm #toast-container {
font-size: 12px;
font-size: var(--size-text-small);
bottom: auto;
overflow-y: auto;
overflow-x: hidden;
@ -581,7 +567,6 @@ body.webbpm {
}
.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);
@ -667,7 +652,7 @@ body.webbpm {
}
/*-- login --*/
.webbpm :is(.form-signin, .form-signup, .confirm) {
.webbpm :is(.form-signin, .form-signup, .confirm) {
color: #333;
width: 580px;
padding: 80px 100px;
@ -700,7 +685,7 @@ body.webbpm {
}
.webbpm .form-signin input {
width: 240px;
width: auto;
font-size: var(--size-text-secondary);
display: inline-block;
}
@ -711,11 +696,17 @@ body.webbpm {
}
.webbpm :is(.form-signin, .form-signup) .row {
display: flex;
display: flex;
margin: 0 0 20px;
& > :is(div, label, ngx-intl-tel-input), &.registration > * {
width: auto;
padding-left: 0;
padding-right: 0;
}
}
.webbpm .registration-link,
.webbpm .registration-link,
.webbpm .login-link {
margin-right: 20px;
font-size: var(--size-text-secondary);
@ -747,13 +738,6 @@ body.webbpm {
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);
@ -843,4 +827,3 @@ body.webbpm {
background-image: url('./../img/country-flags.jpg') !important;
}
/*-------------- end Поле телефона ------------ */

View file

@ -1,11 +1,15 @@
@import "../../../node_modules/angular-calendar/css/angular-calendar.css";
@import "../../../node_modules/ngx-toastr/toastr.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 "../../../node_modules/intl-tel-input/build/css/intlTelInput.css";
@import "structure.css";
@import "inbox-app.css";
@import "components-app.css";
@import "components-eks.css";
@import "buttons.css";
@import "components-eks.css";

View file

@ -1,4 +0,0 @@
<div class="error_message">
<div class="error_title">403</div>
<div class="error_body">Доступ запрещен</div>
</div>

View file

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

View file

@ -1,51 +0,0 @@
<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

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

View file

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

View file

@ -0,0 +1,3 @@
export const environment = {
mode: "production",
};

View file

@ -0,0 +1,3 @@
export const environment = {
mode: "development",
};

View file

@ -1,47 +0,0 @@
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, ElementRef} from "@angular/core";
import {AbstractButton, NotNull, ObjectRef, TextArea} from "@webbpm/base-package";
import {ConfigExecutorRpcService} from "../generated/rpc/ConfigExecutorRpcService";
/**
* @author: a.petrov
*/
@Component({
moduleId: module.id,
selector: 'config-execute-button-component',
templateUrl: './../../../src/resources/template/app/component/ConfigExecuteBtn.html',
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ConfigExecuteBtn extends AbstractButton {
@ObjectRef()
@NotNull()
public ervuIdField: TextArea;
@NotNull()
public methodPath: string;
private script: ConfigExecutorRpcService;
constructor(el: ElementRef, cd: ChangeDetectorRef) {
super(el, cd);
}
initialize() {
super.initialize();
this.script = this.getScript(ConfigExecutorRpcService)
}
doClickActions(): Promise<any> {
const value = this.ervuIdField.getValue();
if (value && this.methodPath.trim().length !== 0) {
const ids = value.replace(/[{}]/g, '')
.split(',')
.map(id => id.trim().replace(/"/g, ''));
return this.script.callConfigExecutor(this.methodPath, ids, true)
.catch(error => Promise.reject(error));
}
}
getFocusElement(): HTMLInputElement {
return this.el.nativeElement.querySelector('button');
}
}

View file

@ -1,8 +0,0 @@
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);

View file

@ -1,7 +1,20 @@
import {platformBrowserDynamic} from "@angular/platform-browser-dynamic";
/// <reference types="@angular/localize" />
import {enableProdMode} from "@angular/core";
import {WebbpmModule} from "./modules/webbpm/webbpm.module";
import {environment} from "./environments/environment";
import {bootstrapApplication} from "@angular/platform-browser";
import {WebbpmComponent} from "./modules/webbpm/component/webbpm.component";
import {appConfig} from "./modules/webbpm/app.config";
import "@angular/localize/init";
window['dev_mode'] = true;
enableProdMode();
platformBrowserDynamic().bootstrapModule(WebbpmModule);
// import "cadesplugin_api";
let isProduction: boolean = environment.mode === "production";
if (isProduction) {
enableProdMode();
}
window['dev_mode'] = !isProduction;
bootstrapApplication(WebbpmComponent, appConfig)
.catch((err) => console.error(err));

View file

@ -1,47 +0,0 @@
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} 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";
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
},
];
@NgModule({
imports: [RouterModule.forChild(appRoutes)],
exports: [RouterModule]
})
export class AppRoutingModule {
}

View file

@ -1,76 +0,0 @@
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 {ConfigExecuteBtn} from "../../ervu/ConfigExecuteBtn";
import {ExportDataBtn} from "../../ervu/ExportDataBtn";
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(() => ConfigExecuteBtn),
forwardRef(() => ExportDataBtn)
];
@NgModule({
imports: [
CommonModule,
FormsModule,
CoreModule,
NgbModule,
BpmnModule,
SecurityModule,
ComponentsModule,
AgGridModule,
RouterModule,
InternationalPhoneNumberModule
],
declarations: [
DIRECTIVES
],
exports: [
DIRECTIVES
],
providers: [
{ provide: ProgressIndicationService, useClass: AppProgressIndicationService }
],
bootstrap: [],
entryComponents: [AppProgressIndicationComponent]
})
export class AppModule {
}

View file

@ -1,11 +0,0 @@
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

@ -1,9 +0,0 @@
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

@ -1,20 +0,0 @@
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();
}
}

View file

@ -1,11 +0,0 @@
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

@ -1,51 +0,0 @@
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

@ -1,79 +0,0 @@
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

@ -1,71 +0,0 @@
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

@ -1,9 +0,0 @@
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

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

View file

@ -1,20 +0,0 @@
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

@ -1,7 +0,0 @@
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

@ -1,51 +0,0 @@
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 {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: [
HTTP_INTERCEPTOR_PROVIDERS
],
bootstrap: [
PreviewContainerComponent
]
})
export class PreviewModule {
constructor(zone: NgZone) {
window['zoneImpl'] = zone;
}
}

View file

@ -0,0 +1,4 @@
import {Routes} from '@angular/router';
export const previewRoutes: Routes = [
];

View file

@ -0,0 +1,9 @@
import {Routes} from '@angular/router';
import {DYNAMIC_ROUTING} from "../../page.routing";
export const previewRoutes: Routes = [
{
path: '',
children: DYNAMIC_ROUTING
}
];

View file

@ -1,16 +0,0 @@
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,70 @@
import {
ApplicationConfig,
ErrorHandler,
importProvidersFrom,
inject,
provideAppInitializer,
provideZoneChangeDetection
} from '@angular/core';
import {provideRouter, withHashLocation, withRouterConfig} from '@angular/router';
import {
AppConfigService,
ProcessInstanceParamsProvider,
TaskParamsProvider,
} from "@webbpm/base-package";
import {appRoutes} from './app.routes';
import {SharedModule} from "shared";
import {provideAnimations} from "@angular/platform-browser/animations";
import {GlobalErrorHandler} from "./handler/global-error.handler";
import {DEFAULT_HTTP_INTERCEPTOR_PROVIDERS} from "./interceptor/default-interceptors";
import {
HttpClient,
provideHttpClient,
withInterceptorsFromDi,
withXsrfConfiguration
} from "@angular/common/http";
import {provideToastr} from "ngx-toastr";
import {TokenConstants} from "./security/TokenConstants";
import {lastValueFrom} from "rxjs";
import {environment} from "../../environments/environment";
export const appConfig: ApplicationConfig = {
providers: [
provideAnimations(),
provideToastr({
preventDuplicates: true,
positionClass: 'toast-top-right'
}),
provideHttpClient(
withInterceptorsFromDi(),
withXsrfConfiguration(
{
cookieName: TokenConstants.CSRF_TOKEN_NAME,
headerName: TokenConstants.CSRF_HEADER_NAME
}),
),
importProvidersFrom(SharedModule),
provideAppInitializer(() => {
let httpClient = inject(HttpClient);
let appConfigService = inject(AppConfigService);
return appConfigService.load()
.then(() => lastValueFrom(httpClient.get<any>("version"))
.catch(reason => {
if (environment.mode == "development") {
console.error("Can't get backend version:" + reason);
}
else {
return Promise.reject(reason);
}
}));
}),
TaskParamsProvider,
ProcessInstanceParamsProvider,
{provide: ErrorHandler, useClass: GlobalErrorHandler},
DEFAULT_HTTP_INTERCEPTOR_PROVIDERS,
provideZoneChangeDetection({eventCoalescing: true}),
provideRouter(appRoutes,
withHashLocation(),
withRouterConfig({onSameUrlNavigation: "reload"})),
],
};

View file

@ -0,0 +1,142 @@
import {Route} from '@angular/router';
import {
AuthenticationGuard,
ConfirmExitGuard,
SignedInGuard,
} from "@webbpm/base-package";
import {LoginComponent} from "./component/login.component";
import {AccessDeniedComponent} from "./component/access-denied.component";
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 {previewRoutes} from "../preview/preview.routes";
export const appRoutes: Route[] = [
{
path: '',
loadComponent: () => import('page-main').then(
m => m.PagemainComponent),
canActivate: [AuthenticationGuard],
pathMatch: 'full',
},
{
path: 'webbpm-preview',
children: previewRoutes,
},
{
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: 'user-management',
canActivate: [AuthenticationGuard],
children: [
{
path: 'users',
loadComponent: () => import('page-user-management-users').then(
m => m.PageusermanagementusersComponent),
canActivate: [ConfirmExitGuard]
},
{
path: 'users/new',
loadComponent: () => import('page-user-management-user-create').then(
m => m.PageusermanagementusercreateComponent),
canActivate: [ConfirmExitGuard]
},
{
path: 'users/:id',
loadComponent: () => import('page-user-management-user-edit').then(
m => m.PageusermanagementusereditComponent),
canActivate: [ConfirmExitGuard]
},
{
path: 'org-units',
loadComponent: () => import('page-user-management-org-units').then(
m => m.PageusermanagementorgunitsComponent),
canActivate: [ConfirmExitGuard]
},
{
path: 'org-units/new',
loadComponent: () => import('page-user-management-org-unit').then(
m => m.PageusermanagementorgunitComponent),
canActivate: [ConfirmExitGuard]
},
{
path: 'org-units/:id',
loadComponent: () => import('page-user-management-org-unit').then(
m => m.PageusermanagementorgunitComponent),
canActivate: [ConfirmExitGuard]
},
{
path: 'roles',
loadComponent: () => import('page-user-management-roles').then(
m => m.PageusermanagementrolesComponent),
canActivate: [ConfirmExitGuard]
},
{
path: 'roles/new',
loadComponent: () => import('page-user-management-role').then(
m => m.PageusermanagementroleComponent),
canActivate: [ConfirmExitGuard]
},
{
path: 'roles/:id',
loadComponent: () => import('page-user-management-role').then(
m => m.PageusermanagementroleComponent),
canActivate: [ConfirmExitGuard]
},
{
path: 'groups',
loadComponent: () => import('page-user-management-groups').then(
m => m.PageusermanagementgroupsComponent),
canActivate: [ConfirmExitGuard]
},
{
path: 'groups/new',
loadComponent: () => import('page-user-management-group-create').then(
m => m.PageusermanagementgroupcreateComponent),
canActivate: [ConfirmExitGuard]
},
{
path: 'groups/:id',
loadComponent: () => import('page-user-management-group-edit').then(
m => m.PageusermanagementgroupeditComponent),
canActivate: [ConfirmExitGuard]
},
{
path: 'authorities',
loadComponent: () => import('page-user-management-authorities').then(
m => m.PageusermanagementauthoritiesComponent),
canActivate: [ConfirmExitGuard]
}
]
},
{
path: '**',
redirectTo: '',
}
];

View file

@ -0,0 +1,10 @@
import {ChangeDetectionStrategy, Component} from "@angular/core";
@Component({
selector: "access-denied",
templateUrl: "./access_denied.html",
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AccessDeniedComponent {
}

View file

@ -0,0 +1,4 @@
<div class="error_message">
<div class="error_title">403</div>
<div class="error_body" i18n="|Template=access_denied.html">Доступ запрещен</div>
</div>

View file

@ -2,13 +2,15 @@ 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";
import {SharedModule} from "shared";
import {AsyncPipe} from "@angular/common";
@Component({
moduleId: module.id,
selector: 'admin-menu',
templateUrl: '../../../../../src/resources/template/app/component/admin_menu.html',
providers: [NgbDropdownConfig],
changeDetection: ChangeDetectionStrategy.OnPush
selector: 'admin-menu',
templateUrl: './admin_menu.html',
providers: [NgbDropdownConfig],
imports: [AsyncPipe, SharedModule],
changeDetection: ChangeDetectionStrategy.OnPush
})
export class AdminMenuComponent {

View file

@ -1,29 +1,30 @@
<div *ngIf="(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('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>
<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 i18n-title="|Template=admin_menu.html" title="Администрирование"></button>
<div ngbDropdownMenu aria-labelledby="adminDropdownMenu">
<button *ngIf="(currentSession | async)?.authorities.includes('USER_MANAGEMENT.USER.LIST')"
routerLink="/user-management/users" ngbDropdownItem i18n="|Template=admin_menu.html">
Пользователи
</button>
<button *ngIf="(currentSession | async)?.authorities.includes('USER_MANAGEMENT.GROUP.LIST')"
routerLink="/user-management/groups" ngbDropdownItem i18n="|Template=admin_menu.html">
Группы
</button>
<button *ngIf="(currentSession | async)?.authorities.includes('USER_MANAGEMENT.ROLE.LIST')"
routerLink="/user-management/roles" ngbDropdownItem i18n="|Template=admin_menu.html">
Роли
</button>
<button *ngIf="(currentSession | async)?.authorities.includes('USER_MANAGEMENT.ORG_UNIT.LIST')"
routerLink="/user-management/org-units" ngbDropdownItem i18n="|Template=admin_menu.html">
Организации
</button>
<button *ngIf="(currentSession | async)?.authorities.includes('USER_MANAGEMENT.AUTHORITY.LIST')"
routerLink="/user-management/authorities" ngbDropdownItem i18n="|Template=admin_menu.html">
Безопасность действий
</button>
</div>
</div>

View file

@ -0,0 +1,10 @@
import {Component} from "@angular/core";
import {ApplicationVersionComponent} from "./application-version.component";
@Component({
selector: "app-footer",
templateUrl: "./app_footer.html",
imports: [ApplicationVersionComponent]
})
export class AppFooterComponent {
}

View file

@ -0,0 +1,35 @@
import {ChangeDetectionStrategy, Component} from "@angular/core";
import {Router} from "@angular/router";
import {UserService, Session} from "@webbpm/base-package";
import {Observable} from "rxjs";
import {AdminMenuComponent} from "./admin-menu.component";
import {SharedModule} from "shared";
import {LogOutComponent} from "./logout.component";
import {AsyncPipe} from "@angular/common";
import {ProcessListComponent} from "./process-list.component";
@Component({
selector: "app-header",
templateUrl: "./app_header.html",
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [
AsyncPipe,
SharedModule,
ProcessListComponent,
AdminMenuComponent,
LogOutComponent
]
})
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

@ -1,10 +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>
<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" i18n="|Template=app_footer.html">Поддержка</a></span>
<span><a href="http://www.micord.ru" target="_blank" i18n="|Template=app_footer.html">Компания "Микорд"</a></span>
<span><application-version></application-version></span>
</div>
</div>
</footer>

View file

@ -1,9 +1,9 @@
<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">
<admin-menu [placement]="'bottom'"></admin-menu>
<div ngbDropdown class="logout" log-out></div>
</div>
</nav>
<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">
<admin-menu [placement]="'bottom'"></admin-menu>
<div ngbDropdown class="logout" log-out></div>
</div>
</nav>

View file

@ -1,13 +1,14 @@
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input} from "@angular/core";
import {HttpClient} from "@angular/common/http";
import {lastValueFrom} from "rxjs";
@Component({
moduleId: module.id,
selector: "application-version",
templateUrl: "../../../../../src/resources/template/app/component/application_version.html",
changeDetection: ChangeDetectionStrategy.OnPush
selector: "application-version",
templateUrl: "./application_version.html",
changeDetection: ChangeDetectionStrategy.OnPush
})
export class ApplicationVersionComponent {
private versionPrefix: string = $localize`:|ApplicationVersionComponent.versionPrefix:Версия:`;
@Input()
public applicationVersion: string;
@ -16,8 +17,8 @@ export class ApplicationVersionComponent {
}
private loadAppVersion() {
this.httpClient.get("version").toPromise().then((version: any) => {
this.applicationVersion = version.number;
lastValueFrom(this.httpClient.get("version")).then((version: any) => {
this.applicationVersion = this.versionPrefix + ' ' + version.number;
this.cd.markForCheck();
})
}

View file

@ -0,0 +1 @@
<span id="version-footer">{{applicationVersion}}</span>

View file

@ -0,0 +1,54 @@
import {ActivatedRoute, Router, RouterModule} from "@angular/router";
import {ChangeDetectionStrategy, ChangeDetectorRef, Component} from "@angular/core";
import {Session, UserService} from "@webbpm/base-package";
import {Observable} from "rxjs";
import {AsyncPipe} from "@angular/common";
import {SharedModule} from "shared";
enum VerificationStatus {
VERIFYING = "VERIFYING",
VERIFIED = "VERIFIED",
FAILED = "FAILED"
}
@Component({
selector: "confirm",
changeDetection: ChangeDetectionStrategy.OnPush,
templateUrl: "./confirm-user-email.html",
imports: [AsyncPipe, SharedModule, RouterModule]
})
export class ConfirmUserEmailComponent {
public verificationStatus = VerificationStatus.VERIFYING;
public currentSession: Observable<Session>;
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 =
$localize`:|ConfirmUserEmailComponent.wrongConfirmEmailLink:Ссылка недействительна. Требуется повторная регистрация.`;
}
else {
this.errorMessage =
$localize`:|ConfirmUserEmailComponent.emailConfirmError:Произошла ошибка, обратитесь в службу технической поддержки!`;
}
this.cd.markForCheck();
})
.then(() => {
this.verificationStatus = VerificationStatus.VERIFIED;
this.cd.markForCheck();
});
}
}

View file

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

View file

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

View file

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

View file

@ -1,9 +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>
<button class="nav-link bi bi-person-fill" ngbDropdownToggle i18n-title="|Template=log_out.html" 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()" i18n="|Template=log_out.html">Выход</button>
</div>

View file

@ -1,11 +1,13 @@
import {Component, Input} from "@angular/core";
import {ActivatedRoute, Router} from "@angular/router";
import {UserService, Credentials} from "@webbpm/base-package";
import {UserService, Credentials, MessagesService} from "@webbpm/base-package";
import {CommonModule} from "@angular/common";
import {FormsModule} from "@angular/forms";
@Component({
moduleId: module.id,
selector: "login",
templateUrl: "../../../../../src/resources/template/app/component/login.html"
selector: "login",
templateUrl: "./login.html",
imports: [CommonModule, FormsModule]
})
export class LoginComponent {
@ -23,7 +25,7 @@ export class LoginComponent {
public confirmationSent: boolean;
constructor(private router: Router, private userService: UserService,
private route: ActivatedRoute) {
private route: ActivatedRoute, private messagesService: MessagesService) {
}
ngOnInit() {
@ -41,16 +43,15 @@ export class LoginComponent {
(reason: any) => {
switch (reason.status) {
case 401: {
this.errorMessage = "Неправильный логин или пароль";
this.errorMessage = $localize`:|LoginComponent.wrongCredentialsMsg:Неправильный логин или пароль`;
break;
}
case 404: {
this.errorMessage = "Приложение стартует. Пожалуйста, подождите...";
this.errorMessage = $localize`:|LoginComponent.backendStartingPleaseWaitMsg:Приложение стартует. Пожалуйста, подождите...`;
break;
}
default: {
this.errorMessage =
"Произошла неизвестная ошибка, обратитесь в службу технической поддержки!";
this.errorMessage = this.messagesService.getUnknownErrorMessage();
break;
}
}

View file

@ -0,0 +1,49 @@
<div class="form-signin">
<form #formComponent="ngForm">
<div class="alert alert-success" [hidden]="!confirmationSent" i18n="|Template=login.html">На ваш почтовый адрес было
отправлено письмо. Подтвердите почту, чтобы войти в систему
</div>
<div class="alert alert-danger" [hidden]="!errorMessage">{{errorMessage}}</div>
<h2 i18n="|Template=login.html">Вход</h2>
<div class="row registration">
<span i18n="|Template=login.html">Еще нет аккаунта?</span><a href="#/registration" i18n="|Template=login.html">Зарегистрироваться</a>
</div>
<div class="row">
<label i18n="|Template=login.html">Пользователь</label>
<input type="text" name="username" class="form-control" i18n-placeholder="|Template=login.html" placeholder="Пользователь" required autofocus
[(ngModel)]="username" maxlength="100">
</div>
<div class="row">
<label i18n="|Template=login.html">Пароль</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()" i18n="|Template=login.html">Войти</button>
<div class="password"><a href="#/reset-password" i18n="|Template=login.html">Забыли пароль?</a></div>
</div>
</form>
</div>

View file

@ -1,17 +1,20 @@
import {Component} from "@angular/core";
import {UserService, Session, AuthenticationMethodService} from "@webbpm/base-package";
import {AuthenticationMethodService, Session, UserService} from "@webbpm/base-package";
import {Observable} from "rxjs";
import {AsyncPipe} from "@angular/common";
import {SharedModule} from "shared";
@Component({
moduleId: module.id,
selector: "[log-out]",
templateUrl: "../../../../../src/resources/template/app/component/log_out.html"
selector: "[log-out]",
templateUrl: "./log_out.html",
imports: [SharedModule, AsyncPipe]
})
export class LogOutComponent {
public currentSession: Observable<Session>;
constructor(private userService: UserService, private authenticationMethodService: AuthenticationMethodService) {
constructor(private userService: UserService,
private authenticationMethodService: AuthenticationMethodService) {
this.currentSession = userService.getCurrentSession();
}
@ -34,4 +37,4 @@ export class LogOutComponent {
public getOrgUnitName(): string {
return this.userService.getOrgUnitName();
}
}
}

View file

@ -0,0 +1,84 @@
import {ActivatedRoute, Router} from "@angular/router";
import {Component} from "@angular/core";
import {
MessagesService,
Session,
UserPasswordResetRequestDto,
UserService
} from "@webbpm/base-package";
import {BehaviorSubject, Observable} from "rxjs";
import {AsyncPipe, CommonModule} from "@angular/common";
import {FormsModule} from "@angular/forms";
@Component({
selector: "newPassword",
templateUrl: "./new_password.html",
imports: [AsyncPipe, CommonModule, FormsModule]
})
export class NewPasswordComponent {
private errorMsgBehavior: BehaviorSubject<string> = new BehaviorSubject<string>(null);
public currentSession: Observable<Session>;
public errorMsg: Observable<string>;
private token: string;
public password: string;
public passwordType: boolean;
public confirmPassword: string;
public confirmPasswordType: boolean;
constructor(private router: Router, private userService: UserService,
private route: ActivatedRoute, private messagesService: MessagesService) {
this.currentSession = this.userService.getCurrentSession();
this.errorMsg = this.errorMsgBehavior.asObservable();
}
ngOnInit() {
this.token = this.route.snapshot.queryParamMap.get("token");
this.router.navigate([], {relativeTo: this.route, replaceUrl: true});
if (this.token == undefined || this.token === '') {
this.errorMsgBehavior.next($localize`:|NewPasswordComponent.wrongLinkPleaseRetryMsg:Ссылка недействительна. Требуется повторить восстановление пароля.`);
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.errorMsgBehavior.next(this.messagesService.getUnknownErrorMessage());
});
}
togglePasswordType(): void {
this.passwordType = !this.passwordType;
}
toggleConfirmPasswordType(): void {
this.confirmPasswordType = !this.confirmPasswordType;
}
isFormValid(): boolean {
if (this.password === undefined || this.confirmPassword === undefined) {
return false;
}
return this.password === this.confirmPassword;
}
onPasswordChange(): void {
this.updateErrMsg(this.isFormValid())
}
private updateErrMsg(valid: boolean) {
this.errorMsgBehavior.next(
valid ? null : $localize`:|NewPasswordComponent.passwordMismatchMsg:Введенные пароли не совпадают. Убедитесь, что данные, введенные в поле "Подтверждение пароля", совпадают с теми, которые указаны в поле "Пароль".`);
}
}

View file

@ -1,103 +1,101 @@
<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>
<div class="form-signup">
<div class="form-logo">
<div></div>
</div>
<div class="form-new-password">
<form #formComponent="ngForm">
<div *ngIf="errorMsg | async as msg" class="alert alert-danger">{{ msg }}</div>
<p class="has-account" i18n="|Template=new_password.html">Вспомнили пароль?
<a href="#/login"><span class="fa fa-lock"></span>Войти</a></p>
<p class="has-account" i18n="|Template=new_password.html">Задайте новый пароль</p>
<div class="row">
<label i18n="|Template=new_password.html">Пароль</label>
<div class="input-group">
<input
#passwordInput="ngModel"
[(ngModel)]="password"
[type]="passwordType ? 'text' : 'password'"
(ngModelChange)="onPasswordChange()"
class="form-control"
maxlength="32"
minlength="6"
name="password"
pattern="^(?=.*[a-zA-Z])(?=.*[0-9])[a-zA-Z0-9]+$"
required
>
<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" i18n="|Template=new_password.html">Поле обязательно
</div>
<div *ngIf="passwordInput.errors.minlength" class="msg-alert" i18n="|Template=new_password.html">Пароль должен
содержать как минимум 6 символов
</div>
<div *ngIf="passwordInput.errors.pattern" class="msg-alert" i18n="|Template=new_password.html">Пароль должен
содержать заглавные и прописные буквы и как минимум 1 цифру
</div>
</div>
</div>
<div class="row">
<label i18n="|Template=new_password.html">Подтверждение пароля</label>
<div class="input-group">
<input
#confirmPasswordInput="ngModel"
[(ngModel)]="confirmPassword"
[type]="confirmPasswordType ? 'text' : 'password'"
(ngModelChange)="onPasswordChange()"
class="form-control"
maxlength="32"
minlength="6"
name="confirmPassword"
pattern="^(?=.*[a-zA-Z])(?=.*[0-9])[a-zA-Z0-9]+$"
required
>
<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" i18n="|Template=new_password.html">Поле обязательно
</div>
<div *ngIf="confirmPasswordInput.errors.minlength" class="msg-alert" i18n="|Template=new_password.html">Пароль должен
содержать как минимум 6 символов
</div>
<div *ngIf="confirmPasswordInput.errors.pattern" class="msg-alert" i18n="|Template=new_password.html">Пароль должен
содержать заглавные и прописные буквы и как минимум 1 цифру
</div>
</div>
</div>
<div class="reset-password-btn-box">
<button (click)="formComponent.form.valid && isFormValid() && changePassword()"
[disabled]="!formComponent.form.valid && !isFormValid()"
class="btn btn-primary"
type="submit" i18n="|Template=new_password.html">
Изменить пароль
</button>
</div>
</form>
</div>
</div>

View file

@ -0,0 +1,39 @@
import {ChangeDetectionStrategy, ChangeDetectorRef, Component, Input} from "@angular/core";
import {Process, TaskService, ProcessDefinitionResource, ProcessService} from "@webbpm/base-package";
import {SharedModule} from "shared";
@Component({
selector: 'process',
templateUrl: './process_list.html',
changeDetection: ChangeDetectionStrategy.OnPush,
imports: [SharedModule]
})
export class ProcessListComponent {
@Input()
public processList: Process[];
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();
});
}
startProcess(processDefinitionId: string) {
this.processService.start(processDefinitionId, null).then(
(processInstanceId: number) => {
this.taskService.startAndOpenNextTask(processInstanceId);
}
);
}
}

View file

@ -0,0 +1,12 @@
<div ngbDropdown class="nav-item">
<button class="nav-link bi bi-clipboard-plus-fill" id="startProcessDropdownMenu" ngbDropdownToggle i18n-title="|Template=process_list.html" 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,85 @@
import {Component, Input, ViewChild} from "@angular/core";
import {AppConfigService, UserDto, UserService, MessagesService} from "@webbpm/base-package";
import {Router} from "@angular/router";
import {
NgxIntlTelInputComponent,
NgxIntlTelInputModule,
SearchCountryField
} from "ngx-intl-tel-input";
import {FormsModule, NgModel} from "@angular/forms";
import {CommonModule} from "@angular/common";
@Component({
selector: "register",
templateUrl: "./register.html",
imports: [CommonModule, FormsModule, NgxIntlTelInputModule]
})
export class RegisterComponent {
SearchCountryField = SearchCountryField;
public passwordPattern: string;
public passwordPatternErrorMessage: string;
public errorMessage: string;
@Input()
public username: string;
@Input()
public email: string;
@ViewChild(NgxIntlTelInputComponent)
public phone: NgxIntlTelInputComponent;
public phoneNumber: string;
public phoneIsTouched: boolean = false;
@Input()
public password: string;
public fieldType: boolean;
@Input()
public consent: string;
@ViewChild('phoneInput', {static: true})
protected model: NgModel;
constructor(private router: Router, private userService: UserService,private appConfigService: AppConfigService,
private messagesService: MessagesService) {
this.passwordPattern = appConfigService.getParamValue("password_pattern");
this.passwordPatternErrorMessage =
$localize`:|RegisterComponent.passwordPatternErrorMessage:Пароль должен содержать заглавные или прописные буквы и как минимум 1 цифру`;
}
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, true)
.then(() => this.router.navigateByUrl("/login?confirmationSent=true"))
.catch((reason: any) => {
if (reason.status === 409) {
this.errorMessage = $localize`:|NewPasswordComponent.userWithSameEmailAlreadyExists:Пользователь с данным почтовым адресом уже существует`;
}
else {
this.errorMessage = this.messagesService.getUnknownErrorMessage();
}
});
}
toggleFieldType(): void {
this.fieldType = !this.fieldType;
}
phoneHasOnlyDialCode(): boolean {
return this.phone && this.phone.phoneNumber && this.phone.selectedCountry && this.phone.phoneNumber.trim() ===
this.phone.selectedCountry.dialCode.trim()
}
phoneInputFocusOut(): void {
this.phoneIsTouched = true;
}
}

View file

@ -1,121 +1,123 @@
<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>
<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 i18n="|Template=register.html">Регистрация</h2>
<p class="has-account" i18n="|Template=register.html">Уже зарегистрированы?
<a href="#/login"><span class="fa fa-lock"></span>Войти</a></p>
<div class="row">
<label i18n="|Template=register.html">Имя</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" i18n="|Template=register.html">Поле обязательно</div>
</div>
</div>
<div class="row">
<label i18n="|Template=register.html">Адрес эл. почты</label>
<input
#emailInput="ngModel"
[(ngModel)]="email"
class="form-control"
email
maxlength="100"
name="email"
required
type="email"
autocomplete="username"
>
<div *ngIf="emailInput.invalid && (emailInput.dirty || emailInput.touched)">
<div *ngIf="emailInput.errors.required" class="msg-alert" i18n="|Template=register.html">Поле обязательно</div>
<div *ngIf="emailInput.errors.email" class="msg-alert" i18n="|Template=register.html">Неверный формат адреса
эл. почты
</div>
</div>
</div>
<div class="row">
<label i18n="|Template=register.html">Номер телефона</label>
<ngx-intl-tel-input
#phoneInput="ngModel"
[(ngModel)]="phoneNumber"
[searchCountryField]="[SearchCountryField.All]"
[preferredCountries]="['ru']"
[searchCountryFlag]="false"
[separateDialCode]="true"
maxlength="20"
minlength="8"
name="phoneNumber"
required
></ngx-intl-tel-input>
<div *ngIf="phoneInput.touched && phoneInput.invalid">
<div *ngIf="phone.selectedCountry">
<div *ngIf="phoneHasOnlyDialCode()" class="msg-alert" i18n="|Template=register.html">Поле обязательно</div>
<div *ngIf="phoneInput.errors" class="msg-alert" i18n="|Template=register.html">Введите корректный номер</div>
</div>
<div *ngIf="!phone.selectedCountry">
<div *ngIf="phoneInput.errors.required" class="msg-alert" i18n="|Template=register.html">Поле обязательно</div>
<div *ngIf="!phoneInput.errors.required" class="msg-alert" i18n="|Template=register.html">Введите код страны</div>
</div>
</div>
</div>
<div class="row">
<label i18n="|Template=register.html">Пароль</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
autocomplete="new-password"
>
<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" i18n="|Template=register.html">Поле обязательно
</div>
<div *ngIf="passwordInput.errors.minlength" class="msg-alert" i18n="|Template=register.html">Пароль должен
содержать как минимум 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"
i18n="|Template=register.html">
Зарегистрироваться
</button>
</div>
<div *ngIf="consent" [innerHTML]="consent" class="consent"></div>
</form>
</div>
</div>

View file

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

View file

@ -1,45 +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>
<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,123 @@
import {Component, Input} from "@angular/core";
import {Task, TaskService, TaskStatus, TaskReference, TaskResource, ProcessVariable, UserService, RolesService, ProcessService} from "@webbpm/base-package";
import {CommonModule} from "@angular/common";
import {FormsModule} from "@angular/forms";
@Component({
selector: "task-list",
templateUrl: "./task_list.html",
imports: [CommonModule, FormsModule]
})
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,17 @@
import {ChangeDetectionStrategy, Component} from "@angular/core";
import {Router} from "@angular/router";
@Component({
selector: 'task-not-found',
templateUrl: './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,11 @@
import {Component} from "@angular/core";
import {RouterModule} from "@angular/router";
import {SharedModule} from "shared";
@Component({
selector: "[task]",
templateUrl: "./task.html",
imports: [SharedModule, RouterModule]
})
export class TaskComponent {
}

View file

@ -1,3 +1,3 @@
<div id="page">
<router-outlet></router-outlet>
<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 i18n="|Template=task_list.html">Фильтры</li>
<li>
<label>
<input type="radio" name="task-filter" value="All" [(ngModel)]="showMode" (ngModelChange)="filterVisibleTasks()">
<span i18n="|Template=task_list.html">Все [{{tasks.length}}]</span>
</label></li>
<li class="ontime">
<label>
<input type="radio" name="task-filter" value="OnTime" [(ngModel)]="showMode" (ngModelChange)="filterVisibleTasks()">
<span i18n="|Template=task_list.html"><div></div>Без превышения срока [{{onTimeTasks.length}}]</span>
</label></li>
<li class="overdue">
<label>
<input type="radio" name="task-filter" value="Overdue" [(ngModel)]="showMode" (ngModelChange)="filterVisibleTasks()">
<span i18n="|Template=task_list.html"><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" i18n="|Template=task_list.html">Процесс</div>
<div class="th" i18n="|Template=task_list.html">Версия</div>
<div class="th" i18n="|Template=task_list.html">Задача</div>
<div class="th" i18n="|Template=task_list.html">Дата создания</div>
<div class="th" i18n="|Template=task_list.html">Срок</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 i18n="|Template=task_list.html"><h2>Ошибка</h2>
Данная задача не существует.<br/>
Перейти к <a (click)="goToTaskList()" tabindex>списку задач</a></div>
</div>
</div>

View file

@ -8,15 +8,22 @@ import {
Router
} from "@angular/router";
import {ProgressIndicationService} from "@webbpm/base-package";
import {SharedModule} from "shared";
import {AppHeaderComponent} from "./app-header.component";
import {AppFooterComponent} from "./app-footer.component";
@Component({
moduleId: module.id,
selector: '[webbpm]',
templateUrl: './../../../../../src/resources/template/webbpm/webbpm.html'
})
selector: 'app-root',
templateUrl: './webbpm.html',
imports: [
SharedModule,
AppHeaderComponent,
AppFooterComponent
]
})
export class WebbpmComponent {
public headerVisible: boolean = true;
public footerVisible: boolean = true;
public footerVisible: boolean = false;
constructor(private router: Router,
private progressIndicationService: ProgressIndicationService) {
@ -25,8 +32,8 @@ export class WebbpmComponent {
progressIndicationService.showProgressBar();
}
else if (event instanceof NavigationEnd
|| event instanceof NavigationError
|| event instanceof NavigationCancel) {
|| event instanceof NavigationError
|| event instanceof NavigationCancel) {
progressIndicationService.hideProgressBar();
}
})

View file

@ -1,12 +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>
<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

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

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