diff --git a/package-lock.json b/package-lock.json index 1501d221f..28102bc94 100644 --- a/package-lock.json +++ b/package-lock.json @@ -73,13 +73,12 @@ "leaflet": "^1.9.4", "lodash": "^4.17.21", "luxon": "^3.7.1", - "ng-apexcharts": "^2.0.0", "ngx-color-picker": "^20.1.1", "ngx-cookie-service": "^20.0.1", "ngx-echarts": "^20.0.1", "ngx-resize-observer": "^3.1.0", "ngx-scrollbar": "^18.0.0", - "ngx-toastr": "^19.0.0", + "ngx-toastr": "^19.1.0", "prettier": "^3.6.2", "primeng": "^20.0.1", "rxjs": "~7.8.2", @@ -1746,6 +1745,278 @@ "node": ">=0.8.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.5.tgz", + "integrity": "sha512-9o3TMmpmftaCMepOdA5k/yDw8SfInyzWWTjYTFCX3kPSDJMROQTb8jg+h9Cnwnmm1vOzvxN7gIfB5V2ewpjtGA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.5.tgz", + "integrity": "sha512-AdJKSPeEHgi7/ZhuIPtcQKr5RQdo6OO2IL87JkianiMYMPbCtot9fxPbrMiBADOWWm3T2si9stAiVsGbTQFkbA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.5.tgz", + "integrity": "sha512-VGzGhj4lJO+TVGV1v8ntCZWJktV7SGCs3Pn1GRWI1SBFtRALoomm8k5E9Pmwg3HOAal2VDc2F9+PM/rEY6oIDg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.5.tgz", + "integrity": "sha512-D2GyJT1kjvO//drbRT3Hib9XPwQeWd9vZoBJn+bu/lVsOZ13cqNdDeqIF/xQ5/VmWvMduP6AmXvylO/PIc2isw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.5.tgz", + "integrity": "sha512-GtaBgammVvdF7aPIgH2jxMDdivezgFu6iKpmT+48+F8Hhg5J/sfnDieg0aeG/jfSvkYQU2/pceFPDKlqZzwnfQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.5.tgz", + "integrity": "sha512-1iT4FVL0dJ76/q1wd7XDsXrSW+oLoquptvh4CLR4kITDtqi2e/xwXwdCVH8hVHU43wgJdsq7Gxuzcs6Iq/7bxQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.5.tgz", + "integrity": "sha512-nk4tGP3JThz4La38Uy/gzyXtpkPW8zSAmoUhK9xKKXdBCzKODMc2adkB2+8om9BDYugz+uGV7sLmpTYzvmz6Sw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.5.tgz", + "integrity": "sha512-PrikaNjiXdR2laW6OIjlbeuCPrPaAl0IwPIaRv+SMV8CiM8i2LqVUHFC1+8eORgWyY7yhQY+2U2fA55mBzReaw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.5.tgz", + "integrity": "sha512-cPzojwW2okgh7ZlRpcBEtsX7WBuqbLrNXqLU89GxWbNt6uIg78ET82qifUy3W6OVww6ZWobWub5oqZOVtwolfw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.5.tgz", + "integrity": "sha512-Z9kfb1v6ZlGbWj8EJk9T6czVEjjq2ntSYLY2cw6pAZl4oKtfgQuS4HOq41M/BcoLPzrUbNd+R4BXFyH//nHxVg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.5.tgz", + "integrity": "sha512-sQ7l00M8bSv36GLV95BVAdhJ2QsIbCuCjh/uYrWiMQSUuV+LpXwIqhgJDcvMTj+VsQmqAHL2yYaasENvJ7CDKA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.5.tgz", + "integrity": "sha512-0ur7ae16hDUC4OL5iEnDb0tZHDxYmuQyhKhsPBV8f99f6Z9KQM02g33f93rNH5A30agMS46u2HP6qTdEt6Q1kg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.5.tgz", + "integrity": "sha512-kB/66P1OsHO5zLz0i6X0RxlQ+3cu0mkxS3TKFvkb5lin6uwZ/ttOkP3Z8lfR9mJOBk14ZwZ9182SIIWFGNmqmg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.5.tgz", + "integrity": "sha512-UZCmJ7r9X2fe2D6jBmkLBMQetXPXIsZjQJCjgwpVDz+YMcS6oFR27alkgGv3Oqkv07bxdvw7fyB71/olceJhkQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.5.tgz", + "integrity": "sha512-kTxwu4mLyeOlsVIFPfQo+fQJAV9mh24xL+y+Bm6ej067sYANjyEw1dNHmvoqxJUCMnkBdKpvOn0Ahql6+4VyeA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.5.tgz", + "integrity": "sha512-K2dSKTKfmdh78uJ3NcWFiqyRrimfdinS5ErLSn3vluHNeHVnBAFWC8a4X5N+7FgVE1EjXS1QDZbpqZBjfrqMTQ==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/linux-x64": { "version": "0.25.5", "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.5.tgz", @@ -1763,6 +2034,142 @@ "node": ">=18" } }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.5.tgz", + "integrity": "sha512-pwHtMP9viAy1oHPvgxtOv+OkduK5ugofNTVDilIzBLpoWAM16r7b/mxBvfpuQDpRQFMfuVr5aLcn4yveGvBZvw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.5.tgz", + "integrity": "sha512-WOb5fKrvVTRMfWFNCroYWWklbnXH0Q5rZppjq0vQIdlsQKuw6mdSihwSo4RV/YdQ5UCKKvBy7/0ZZYLBZKIbwQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.5.tgz", + "integrity": "sha512-7A208+uQKgTxHd0G0uqZO8UjK2R0DDb4fDmERtARjSHWxqMTye4Erz4zZafx7Di9Cv+lNHYuncAkiGFySoD+Mw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.5.tgz", + "integrity": "sha512-G4hE405ErTWraiZ8UiSoesH8DaCsMm0Cay4fsFWOOUcz8b8rC6uCvnagr+gnioEjWn0wC+o1/TAHt+It+MpIMg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.5.tgz", + "integrity": "sha512-l+azKShMy7FxzY0Rj4RCt5VD/q8mG/e+mDivgspo+yL8zW7qEwctQ6YqKX34DTEleFAvCIUviCFX1SDZRSyMQA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.5.tgz", + "integrity": "sha512-O2S7SNZzdcFG7eFKgvwUEZ2VG9D/sn/eIiz8XRZ1Q/DO5a3s76Xv0mdBzVM5j5R639lXQmPmSo0iRpHqUUrsxw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.5.tgz", + "integrity": "sha512-onOJ02pqs9h1iMJ1PQphR+VZv8qBMQ77Klcsqv9CNW2w6yLqoURLcgERAIurY6QE63bbLuqgP9ATqajFLK5AMQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.25.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.5.tgz", + "integrity": "sha512-TXv6YnJ8ZMVdX+SXWVBo/0p8LTcrUYngpWjvm91TMjjBQii7Oz11Lw5lbDV5Y0TzuhSJHwiH4hEtC1I42mMS0g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@foblex/2d": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/@foblex/2d/-/2d-1.2.1.tgz", @@ -2890,10 +3297,66 @@ "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", "dev": true, - "license": "ISC", - "engines": { - "node": "^14.17.0 || ^16.13.0 || >=18.0.0" - } + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/@lmdb/lmdb-darwin-arm64": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-arm64/-/lmdb-darwin-arm64-3.4.1.tgz", + "integrity": "sha512-kKeP5PaY3bFrrF6GY5aDd96iuh1eoS+5CHJ+7hIP629KIEwzGNwbIzBmEX9TAhRJOivSRDTHCIsbu//+NsYKkg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@lmdb/lmdb-darwin-x64": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-darwin-x64/-/lmdb-darwin-x64-3.4.1.tgz", + "integrity": "sha512-9CMB3seTyHs3EOVWdKiB8IIEDBJ3Gq00Tqyi0V7DS3HL90BjM/AkbZGuhzXwPrfeFazR24SKaRrUQF74f+CmWw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@lmdb/lmdb-linux-arm": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm/-/lmdb-linux-arm-3.4.1.tgz", + "integrity": "sha512-1Mi69vU0akHgCI7tF6YbimPaNEKJiBm/p5A+aM8egr0joj27cQmCCOm2mZQ+Ht2BqmCfZaIgQnMg4gFYNMlpCA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@lmdb/lmdb-linux-arm64": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-linux-arm64/-/lmdb-linux-arm64-3.4.1.tgz", + "integrity": "sha512-d0vuXOdoKjHHJYZ/CRWopnkOiUpev+bgBBW+1tXtWsYWUj8uxl9ZmTBEmsL5mjUlpQDrlYiJSrhOU1hg5QWBSw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] }, "node_modules/@lmdb/lmdb-linux-x64": { "version": "3.4.1", @@ -2909,6 +3372,34 @@ "linux" ] }, + "node_modules/@lmdb/lmdb-win32-arm64": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-arm64/-/lmdb-win32-arm64-3.4.1.tgz", + "integrity": "sha512-4h8tm3i1ODf+28UyqQZLP7c2jmRM26AyEEyYp994B4GiBdGvGAsYUu3oiHANYK9xFpvLuFzyGeqFm1kdNC0D1A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@lmdb/lmdb-win32-x64": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/@lmdb/lmdb-win32-x64/-/lmdb-win32-x64-3.4.1.tgz", + "integrity": "sha512-HqqKIhTbq6piJhkJpTTf3w1m/CgrmwXRAL9R9j7Ru5xdZSeO7Mg4AWiBC9B00uXR+LvVZKtUyRMVZfhmIZztmQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, "node_modules/@marijn/find-cluster-break": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", @@ -2956,74 +3447,331 @@ "url": "https://github.com/sponsors/epoberezkin" } }, - "node_modules/@modelcontextprotocol/sdk/node_modules/json-schema-traverse": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", - "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "node_modules/@modelcontextprotocol/sdk/node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "license": "MIT" + }, + "node_modules/@modelcontextprotocol/sdk/node_modules/raw-body": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", + "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "dev": true, + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.6.3", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-arm64/-/msgpackr-extract-darwin-arm64-3.0.3.tgz", + "integrity": "sha512-QZHtlVgbAdy2zAqNA9Gu1UpIuI8Xvsd1v8ic6B2pZmeFnFcMWiPLfWXh7TVw4eGEZ/C9TH281KwhVoeQUKbyjw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-darwin-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-darwin-x64/-/msgpackr-extract-darwin-x64-3.0.3.tgz", + "integrity": "sha512-mdzd3AVzYKuUmiWOQ8GNhl64/IoFGol569zNRdkLReh6LRLHOXxU4U8eq0JwaD8iFHdVGqSy4IjFL4reoWCDFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm/-/msgpackr-extract-linux-arm-3.0.3.tgz", + "integrity": "sha512-fg0uy/dG/nZEXfYilKoRe7yALaNmHoYeIoJuJ7KJ+YyU2bvY8vPv27f7UKhGRpY6euFYqEVhxCFZgAUNQBM3nw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-arm64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-arm64/-/msgpackr-extract-linux-arm64-3.0.3.tgz", + "integrity": "sha512-YxQL+ax0XqBJDZiKimS2XQaf+2wDGVa1enVRGzEvLLVFeqa5kx2bWbtcSXgsxjQB7nRqqIGFIcLteF/sHeVtQg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz", + "integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@msgpackr-extract/msgpackr-extract-win32-x64": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-win32-x64/-/msgpackr-extract-win32-x64-3.0.3.tgz", + "integrity": "sha512-x0fWaQtYp4E6sktbsdAqnehxDgEc/VwM7uLsRCYWaiGu0ykYdZPiS8zCWdnjHwyiumousxfBm4SO31eXqwEZhQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@napi-rs/nice": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@napi-rs/nice/-/nice-1.0.4.tgz", + "integrity": "sha512-Sqih1YARrmMoHlXGgI9JrrgkzxcaaEso0AH+Y7j8NHonUs+xe4iDsgC3IBIDNdzEewbNpccNN6hip+b5vmyRLw==", + "dev": true, + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 10" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/Brooooooklyn" + }, + "optionalDependencies": { + "@napi-rs/nice-android-arm-eabi": "1.0.4", + "@napi-rs/nice-android-arm64": "1.0.4", + "@napi-rs/nice-darwin-arm64": "1.0.4", + "@napi-rs/nice-darwin-x64": "1.0.4", + "@napi-rs/nice-freebsd-x64": "1.0.4", + "@napi-rs/nice-linux-arm-gnueabihf": "1.0.4", + "@napi-rs/nice-linux-arm64-gnu": "1.0.4", + "@napi-rs/nice-linux-arm64-musl": "1.0.4", + "@napi-rs/nice-linux-ppc64-gnu": "1.0.4", + "@napi-rs/nice-linux-riscv64-gnu": "1.0.4", + "@napi-rs/nice-linux-s390x-gnu": "1.0.4", + "@napi-rs/nice-linux-x64-gnu": "1.0.4", + "@napi-rs/nice-linux-x64-musl": "1.0.4", + "@napi-rs/nice-win32-arm64-msvc": "1.0.4", + "@napi-rs/nice-win32-ia32-msvc": "1.0.4", + "@napi-rs/nice-win32-x64-msvc": "1.0.4" + } + }, + "node_modules/@napi-rs/nice-android-arm-eabi": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm-eabi/-/nice-android-arm-eabi-1.0.4.tgz", + "integrity": "sha512-OZFMYUkih4g6HCKTjqJHhMUlgvPiDuSLZPbPBWHLjKmFTv74COzRlq/gwHtmEVaR39mJQ6ZyttDl2HNMUbLVoA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-android-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-android-arm64/-/nice-android-arm64-1.0.4.tgz", + "integrity": "sha512-k8u7cjeA64vQWXZcRrPbmwjH8K09CBnNaPnI9L1D5N6iMPL3XYQzLcN6WwQonfcqCDv5OCY3IqX89goPTV4KMw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-darwin-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-arm64/-/nice-darwin-arm64-1.0.4.tgz", + "integrity": "sha512-GsLdQvUcuVzoyzmtjsThnpaVEizAqH5yPHgnsBmq3JdVoVZHELFo7PuJEdfOH1DOHi2mPwB9sCJEstAYf3XCJA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-darwin-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-darwin-x64/-/nice-darwin-x64-1.0.4.tgz", + "integrity": "sha512-1y3gyT3e5zUY5SxRl3QDtJiWVsbkmhtUHIYwdWWIQ3Ia+byd/IHIEpqAxOGW1nhhnIKfTCuxBadHQb+yZASVoA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-freebsd-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-freebsd-x64/-/nice-freebsd-x64-1.0.4.tgz", + "integrity": "sha512-06oXzESPRdXUuzS8n2hGwhM2HACnDfl3bfUaSqLGImM8TA33pzDXgGL0e3If8CcFWT98aHows5Lk7xnqYNGFeA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm-gnueabihf": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm-gnueabihf/-/nice-linux-arm-gnueabihf-1.0.4.tgz", + "integrity": "sha512-CgklZ6g8WL4+EgVVkxkEvvsi2DSLf9QIloxWO0fvQyQBp6VguUSX3eHLeRpqwW8cRm2Hv/Q1+PduNk7VK37VZw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm64-gnu": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-gnu/-/nice-linux-arm64-gnu-1.0.4.tgz", + "integrity": "sha512-wdAJ7lgjhAlsANUCv0zi6msRwq+D4KDgU+GCCHssSxWmAERZa2KZXO0H2xdmoJ/0i03i6YfK/sWaZgUAyuW2oQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-linux-arm64-musl": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-arm64-musl/-/nice-linux-arm64-musl-1.0.4.tgz", + "integrity": "sha512-4b1KYG+sriufhFrpUS9uNOEYYJqSfcbnwGx6uGX7JjrH8tELG90cOpCawz5THNIwlS3DhLgnCOcn0+4p6z26QA==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "MIT" + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">= 10" + } }, - "node_modules/@modelcontextprotocol/sdk/node_modules/raw-body": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.0.tgz", - "integrity": "sha512-RmkhL8CAyCRPXCE28MMH0z2PNWQBNk2Q09ZdxM9IOOXwxwZbN+qbWaatPkdkWIKL2ZVDImrN/pK5HTRz2PcS4g==", + "node_modules/@napi-rs/nice-linux-ppc64-gnu": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-ppc64-gnu/-/nice-linux-ppc64-gnu-1.0.4.tgz", + "integrity": "sha512-iaf3vMRgr23oe1PUaKpxaH3DS0IMN0+N9iEiWVwYPm/U15vZFYdqVegGfN2PzrZLUl5lc8ZxbmEKDfuqslhAMA==", + "cpu": [ + "ppc64" + ], "dev": true, "license": "MIT", - "dependencies": { - "bytes": "3.1.2", - "http-errors": "2.0.0", - "iconv-lite": "0.6.3", - "unpipe": "1.0.0" - }, + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">= 0.8" + "node": ">= 10" } }, - "node_modules/@msgpackr-extract/msgpackr-extract-linux-x64": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/@msgpackr-extract/msgpackr-extract-linux-x64/-/msgpackr-extract-linux-x64-3.0.3.tgz", - "integrity": "sha512-cvwNfbP07pKUfq1uH+S6KJ7dT9K8WOE4ZiAcsrSes+UY55E/0jLYc+vq+DO7jlmqRb5zAggExKm0H7O/CBaesg==", + "node_modules/@napi-rs/nice-linux-riscv64-gnu": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-riscv64-gnu/-/nice-linux-riscv64-gnu-1.0.4.tgz", + "integrity": "sha512-UXoREY6Yw6rHrGuTwQgBxpfjK34t6mTjibE9/cXbefL9AuUCJ9gEgwNKZiONuR5QGswChqo9cnthjdKkYyAdDg==", "cpu": [ - "x64" + "riscv64" ], "dev": true, "license": "MIT", "optional": true, "os": [ "linux" - ] + ], + "engines": { + "node": ">= 10" + } }, - "node_modules/@napi-rs/nice": { + "node_modules/@napi-rs/nice-linux-s390x-gnu": { "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@napi-rs/nice/-/nice-1.0.4.tgz", - "integrity": "sha512-Sqih1YARrmMoHlXGgI9JrrgkzxcaaEso0AH+Y7j8NHonUs+xe4iDsgC3IBIDNdzEewbNpccNN6hip+b5vmyRLw==", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-linux-s390x-gnu/-/nice-linux-s390x-gnu-1.0.4.tgz", + "integrity": "sha512-eFbgYCRPmsqbYPAlLYU5hYTNbogmIDUvknilehHsFhCH1+0/kN87lP+XaLT0Yeq4V/rpwChSd9vlz4muzFArtw==", + "cpu": [ + "s390x" + ], "dev": true, "license": "MIT", "optional": true, + "os": [ + "linux" + ], "engines": { "node": ">= 10" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Brooooooklyn" - }, - "optionalDependencies": { - "@napi-rs/nice-android-arm-eabi": "1.0.4", - "@napi-rs/nice-android-arm64": "1.0.4", - "@napi-rs/nice-darwin-arm64": "1.0.4", - "@napi-rs/nice-darwin-x64": "1.0.4", - "@napi-rs/nice-freebsd-x64": "1.0.4", - "@napi-rs/nice-linux-arm-gnueabihf": "1.0.4", - "@napi-rs/nice-linux-arm64-gnu": "1.0.4", - "@napi-rs/nice-linux-arm64-musl": "1.0.4", - "@napi-rs/nice-linux-ppc64-gnu": "1.0.4", - "@napi-rs/nice-linux-riscv64-gnu": "1.0.4", - "@napi-rs/nice-linux-s390x-gnu": "1.0.4", - "@napi-rs/nice-linux-x64-gnu": "1.0.4", - "@napi-rs/nice-linux-x64-musl": "1.0.4", - "@napi-rs/nice-win32-arm64-msvc": "1.0.4", - "@napi-rs/nice-win32-ia32-msvc": "1.0.4", - "@napi-rs/nice-win32-x64-msvc": "1.0.4" } }, "node_modules/@napi-rs/nice-linux-x64-gnu": { @@ -3060,6 +3808,57 @@ "node": ">= 10" } }, + "node_modules/@napi-rs/nice-win32-arm64-msvc": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-arm64-msvc/-/nice-win32-arm64-msvc-1.0.4.tgz", + "integrity": "sha512-vubOe3i+YtSJGEk/++73y+TIxbuVHi+W8ZzrRm2eETCjCRwNlgbfToQZ85dSA+4iBB/NJRGNp+O4hfdbbttZWA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-ia32-msvc": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-ia32-msvc/-/nice-win32-ia32-msvc-1.0.4.tgz", + "integrity": "sha512-BMOVrUDZeg1RNRKVlh4eyLv5djAAVLiSddfpuuQ47EFjBcklg0NUeKMFKNrKQR4UnSn4HAiACLD7YK7koskwmg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, + "node_modules/@napi-rs/nice-win32-x64-msvc": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@napi-rs/nice-win32-x64-msvc/-/nice-win32-x64-msvc-1.0.4.tgz", + "integrity": "sha512-kCNk6HcRZquhw/whwh4rHsdPyOSCQCgnVDVik+Y9cuSVTDy3frpiCJTScJqPPS872h4JgZKkr/+CwcwttNEo9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10" + } + }, "node_modules/@ng-select/ng-option-highlight": { "version": "20.0.1", "resolved": "https://registry.npmjs.org/@ng-select/ng-option-highlight/-/ng-option-highlight-20.0.1.tgz", @@ -3262,98 +4061,266 @@ "node-which": "bin/which.js" }, "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/redact": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-3.2.2.tgz", + "integrity": "sha512-7VmYAmk4csGv08QzrDKScdzn11jHPFGyqJW39FyPgPuAp3zIaUmuCo1yxw9aGs+NEJuTGQ9Gwqpt93vtJubucg==", + "dev": true, + "license": "ISC", + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/run-script": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-9.1.0.tgz", + "integrity": "sha512-aoNSbxtkePXUlbZB+anS1LqsJdctG5n3UVhfU47+CDdwMi6uNTBMF9gPcQRnqghQd2FGzcwwIFBruFMxjhBewg==", + "dev": true, + "license": "ISC", + "dependencies": { + "@npmcli/node-gyp": "^4.0.0", + "@npmcli/package-json": "^6.0.0", + "@npmcli/promise-spawn": "^8.0.0", + "node-gyp": "^11.0.0", + "proc-log": "^5.0.0", + "which": "^5.0.0" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@npmcli/run-script/node_modules/isexe": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", + "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=16" + } + }, + "node_modules/@npmcli/run-script/node_modules/which": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", + "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^3.1.1" + }, + "bin": { + "node-which": "bin/which.js" + }, + "engines": { + "node": "^18.17.0 || >=20.5.0" + } + }, + "node_modules/@parcel/watcher": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", + "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "detect-libc": "^1.0.3", + "is-glob": "^4.0.3", + "micromatch": "^4.0.5", + "node-addon-api": "^7.0.0" + }, + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + }, + "optionalDependencies": { + "@parcel/watcher-android-arm64": "2.5.1", + "@parcel/watcher-darwin-arm64": "2.5.1", + "@parcel/watcher-darwin-x64": "2.5.1", + "@parcel/watcher-freebsd-x64": "2.5.1", + "@parcel/watcher-linux-arm-glibc": "2.5.1", + "@parcel/watcher-linux-arm-musl": "2.5.1", + "@parcel/watcher-linux-arm64-glibc": "2.5.1", + "@parcel/watcher-linux-arm64-musl": "2.5.1", + "@parcel/watcher-linux-x64-glibc": "2.5.1", + "@parcel/watcher-linux-x64-musl": "2.5.1", + "@parcel/watcher-win32-arm64": "2.5.1", + "@parcel/watcher-win32-ia32": "2.5.1", + "@parcel/watcher-win32-x64": "2.5.1" + } + }, + "node_modules/@parcel/watcher-android-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz", + "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz", + "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-darwin-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz", + "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@npmcli/redact": { - "version": "3.2.2", - "resolved": "https://registry.npmjs.org/@npmcli/redact/-/redact-3.2.2.tgz", - "integrity": "sha512-7VmYAmk4csGv08QzrDKScdzn11jHPFGyqJW39FyPgPuAp3zIaUmuCo1yxw9aGs+NEJuTGQ9Gwqpt93vtJubucg==", + "node_modules/@parcel/watcher-freebsd-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz", + "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==", + "cpu": [ + "x64" + ], "dev": true, - "license": "ISC", + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@npmcli/run-script": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@npmcli/run-script/-/run-script-9.1.0.tgz", - "integrity": "sha512-aoNSbxtkePXUlbZB+anS1LqsJdctG5n3UVhfU47+CDdwMi6uNTBMF9gPcQRnqghQd2FGzcwwIFBruFMxjhBewg==", + "node_modules/@parcel/watcher-linux-arm-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz", + "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==", + "cpu": [ + "arm" + ], "dev": true, - "license": "ISC", - "dependencies": { - "@npmcli/node-gyp": "^4.0.0", - "@npmcli/package-json": "^6.0.0", - "@npmcli/promise-spawn": "^8.0.0", - "node-gyp": "^11.0.0", - "proc-log": "^5.0.0", - "which": "^5.0.0" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@npmcli/run-script/node_modules/isexe": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/isexe/-/isexe-3.1.1.tgz", - "integrity": "sha512-LpB/54B+/2J5hqQ7imZHfdU31OlgQqx7ZicVlkm9kzg9/w8GKLEcFfJl/t7DCEDueOyBAD6zCCwTO6Fzs0NoEQ==", + "node_modules/@parcel/watcher-linux-arm-musl": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz", + "integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==", + "cpu": [ + "arm" + ], "dev": true, - "license": "ISC", + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": ">=16" + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@npmcli/run-script/node_modules/which": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/which/-/which-5.0.0.tgz", - "integrity": "sha512-JEdGzHwwkrbWoGOlIHqQ5gtprKGOenpDHpxE9zVR1bWbOtYRyPPHMe9FaP6x61CmNaTThSkb0DAJte5jD+DmzQ==", + "node_modules/@parcel/watcher-linux-arm64-glibc": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz", + "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==", + "cpu": [ + "arm64" + ], "dev": true, - "license": "ISC", - "dependencies": { - "isexe": "^3.1.1" - }, - "bin": { - "node-which": "bin/which.js" - }, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], "engines": { - "node": "^18.17.0 || >=20.5.0" + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" } }, - "node_modules/@parcel/watcher": { + "node_modules/@parcel/watcher-linux-arm64-musl": { "version": "2.5.1", - "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz", - "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==", + "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz", + "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==", + "cpu": [ + "arm64" + ], "dev": true, - "hasInstallScript": true, "license": "MIT", "optional": true, - "dependencies": { - "detect-libc": "^1.0.3", - "is-glob": "^4.0.3", - "micromatch": "^4.0.5", - "node-addon-api": "^7.0.0" - }, + "os": [ + "linux" + ], "engines": { "node": ">= 10.0.0" }, "funding": { "type": "opencollective", "url": "https://opencollective.com/parcel" - }, - "optionalDependencies": { - "@parcel/watcher-android-arm64": "2.5.1", - "@parcel/watcher-darwin-arm64": "2.5.1", - "@parcel/watcher-darwin-x64": "2.5.1", - "@parcel/watcher-freebsd-x64": "2.5.1", - "@parcel/watcher-linux-arm-glibc": "2.5.1", - "@parcel/watcher-linux-arm-musl": "2.5.1", - "@parcel/watcher-linux-arm64-glibc": "2.5.1", - "@parcel/watcher-linux-arm64-musl": "2.5.1", - "@parcel/watcher-linux-x64-glibc": "2.5.1", - "@parcel/watcher-linux-x64-musl": "2.5.1", - "@parcel/watcher-win32-arm64": "2.5.1", - "@parcel/watcher-win32-ia32": "2.5.1", - "@parcel/watcher-win32-x64": "2.5.1" } }, "node_modules/@parcel/watcher-linux-x64-glibc": { @@ -3398,6 +4365,69 @@ "url": "https://opencollective.com/parcel" } }, + "node_modules/@parcel/watcher-win32-arm64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz", + "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-ia32": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz", + "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, + "node_modules/@parcel/watcher-win32-x64": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz", + "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">= 10.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/parcel" + } + }, "node_modules/@parcel/watcher/node_modules/detect-libc": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", @@ -3952,67 +4982,6 @@ "acorn": "^8.9.0" } }, - "node_modules/@svgdotjs/svg.draggable.js": { - "version": "3.0.6", - "resolved": "https://registry.npmjs.org/@svgdotjs/svg.draggable.js/-/svg.draggable.js-3.0.6.tgz", - "integrity": "sha512-7iJFm9lL3C40HQcqzEfezK2l+dW2CpoVY3b77KQGqc8GXWa6LhhmX5Ckv7alQfUXBuZbjpICZ+Dvq1czlGx7gA==", - "license": "MIT", - "peer": true, - "peerDependencies": { - "@svgdotjs/svg.js": "^3.2.4" - } - }, - "node_modules/@svgdotjs/svg.filter.js": { - "version": "3.0.9", - "resolved": "https://registry.npmjs.org/@svgdotjs/svg.filter.js/-/svg.filter.js-3.0.9.tgz", - "integrity": "sha512-/69XMRCDoam2HgC4ldHIaDgeQf1ViHIsa0Ld4uWgiXtZ+E24DWHe/9Ib6kbNiZ7WRIdlVokUDR1Fg0kjIpkfbw==", - "license": "MIT", - "peer": true, - "dependencies": { - "@svgdotjs/svg.js": "^3.2.4" - }, - "engines": { - "node": ">= 0.8.0" - } - }, - "node_modules/@svgdotjs/svg.js": { - "version": "3.2.4", - "resolved": "https://registry.npmjs.org/@svgdotjs/svg.js/-/svg.js-3.2.4.tgz", - "integrity": "sha512-BjJ/7vWNowlX3Z8O4ywT58DqbNRyYlkk6Yz/D13aB7hGmfQTvGX4Tkgtm/ApYlu9M7lCQi15xUEidqMUmdMYwg==", - "license": "MIT", - "peer": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/Fuzzyma" - } - }, - "node_modules/@svgdotjs/svg.resize.js": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/@svgdotjs/svg.resize.js/-/svg.resize.js-2.0.5.tgz", - "integrity": "sha512-4heRW4B1QrJeENfi7326lUPYBCevj78FJs8kfeDxn5st0IYPIRXoTtOSYvTzFWgaWWXd3YCDE6ao4fmv91RthA==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 14.18" - }, - "peerDependencies": { - "@svgdotjs/svg.js": "^3.2.4", - "@svgdotjs/svg.select.js": "^4.0.1" - } - }, - "node_modules/@svgdotjs/svg.select.js": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/@svgdotjs/svg.select.js/-/svg.select.js-4.0.3.tgz", - "integrity": "sha512-qkMgso1sd2hXKd1FZ1weO7ANq12sNmQJeGDjs46QwDVsxSRcHmvWKL2NDF7Yimpwf3sl5esOLkPqtV2bQ3v/Jg==", - "license": "MIT", - "peer": true, - "engines": { - "node": ">= 14.18" - }, - "peerDependencies": { - "@svgdotjs/svg.js": "^3.2.4" - } - }, "node_modules/@swc/helpers": { "version": "0.2.14", "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.2.14.tgz", @@ -5136,13 +6105,6 @@ "dev": true, "license": "BSD-2-Clause" }, - "node_modules/@yr/monotone-cubic-spline": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/@yr/monotone-cubic-spline/-/monotone-cubic-spline-1.0.3.tgz", - "integrity": "sha512-FQXkOta0XBSUPHndIKON2Y9JeQz5ZeMqLYZVVK93FliNBFm7LNMIZmY6FrMEB9XPcDbE2bekMbZD6kzDkxwYjA==", - "license": "MIT", - "peer": true - }, "node_modules/abbrev": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", @@ -5353,21 +6315,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "node_modules/apexcharts": { - "version": "5.3.2", - "resolved": "https://registry.npmjs.org/apexcharts/-/apexcharts-5.3.2.tgz", - "integrity": "sha512-qeKIS5CS/n+CoNNwbd69G4rRc3we5/8g5Mu46OumqH7pCMSN4MhI2lr0xDY/ktBlFh94YuM9psc9WX6EWtC90g==", - "license": "SEE LICENSE IN LICENSE", - "peer": true, - "dependencies": { - "@svgdotjs/svg.draggable.js": "^3.0.4", - "@svgdotjs/svg.filter.js": "^3.0.8", - "@svgdotjs/svg.js": "^3.2.4", - "@svgdotjs/svg.resize.js": "^2.0.2", - "@svgdotjs/svg.select.js": "^4.0.1", - "@yr/monotone-cubic-spline": "^1.0.3" - } - }, "node_modules/argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -8757,18 +9704,6 @@ "dev": true, "license": "MIT" }, - "node_modules/jiti": { - "version": "1.21.7", - "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.21.7.tgz", - "integrity": "sha512-/imKNG4EbWNrVjoNC/1H5/9GFy+tqjGBHCaSsN+P2RnPqjsLmv6UD3Ej+Kj8nBWaRAwyk7kK5ZUc+OEatnTR3A==", - "dev": true, - "license": "MIT", - "optional": true, - "peer": true, - "bin": { - "jiti": "bin/jiti.js" - } - }, "node_modules/jmespath": { "version": "0.16.0", "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.16.0.tgz", @@ -10238,20 +11173,6 @@ "node": ">= 0.6" } }, - "node_modules/ng-apexcharts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ng-apexcharts/-/ng-apexcharts-2.0.0.tgz", - "integrity": "sha512-PO6DSrgRXxHtlQtjbF+7iJhbhNocAqUCF4+K5kvMkFhZ7TTehUzxpyjKVNCqcMI15cjU49pRl7k+lhNPQCmE6w==", - "dependencies": { - "tslib": "^2.8.1" - }, - "peerDependencies": { - "@angular/common": "^20.0.0", - "@angular/core": "^20.0.0", - "apexcharts": "^5.3.2", - "rxjs": "^7.8.2" - } - }, "node_modules/ngx-color-picker": { "version": "20.1.1", "resolved": "https://registry.npmjs.org/ngx-color-picker/-/ngx-color-picker-20.1.1.tgz", @@ -10321,9 +11242,9 @@ } }, "node_modules/ngx-toastr": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/ngx-toastr/-/ngx-toastr-19.0.0.tgz", - "integrity": "sha512-6pTnktwwWD+kx342wuMOWB4+bkyX9221pAgGz3SHOJH0/MI9erLucS8PeeJDFwbUYyh75nQ6AzVtolgHxi52dQ==", + "version": "19.1.0", + "resolved": "https://registry.npmjs.org/ngx-toastr/-/ngx-toastr-19.1.0.tgz", + "integrity": "sha512-Qa7Kg7QzGKNtp1v04hu3poPKKx8BGBD/Onkhm6CdH5F0vSMdq+BdR/f8DTpZnGFksW891tAFufpiWb9UZX+3vg==", "license": "MIT", "dependencies": { "tslib": "^2.3.0" diff --git a/package.json b/package.json index 7be74565c..7415ad369 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "watch": "ng build --watch --configuration development", "test": "ng test", "i18n:extract": "transloco-keys-manager extract", - "i18n:find": "transloco-keys-manager find" + "i18n:find": "transloco-keys-manager find", + "check:licenses": "node parse-3rdpartylicenses.js dist/frontend-angular/3rdpartylicenses.txt" }, "private": true, "dependencies": { @@ -77,13 +78,12 @@ "leaflet": "^1.9.4", "lodash": "^4.17.21", "luxon": "^3.7.1", - "ng-apexcharts": "^2.0.0", "ngx-color-picker": "^20.1.1", "ngx-cookie-service": "^20.0.1", "ngx-echarts": "^20.0.1", "ngx-resize-observer": "^3.1.0", "ngx-scrollbar": "^18.0.0", - "ngx-toastr": "^19.0.0", + "ngx-toastr": "^19.1.0", "prettier": "^3.6.2", "primeng": "^20.0.1", "rxjs": "~7.8.2", diff --git a/parse-3rdpartylicenses.js b/parse-3rdpartylicenses.js new file mode 100644 index 000000000..9b6a9e5fa --- /dev/null +++ b/parse-3rdpartylicenses.js @@ -0,0 +1,146 @@ +/** + * MIT License + * + * Copyright (c) 2025 Daniel Ziegler + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +const fs = require('fs'); +const path = require('path'); +const {forEach} = require('lodash'); + +/** + * Parses the 3rdpartylicenses.txt file. + * @param {string} filePath - Path to the license file. + * @returns {Array<{package: string, license: string, licenseText: string}>} Array of license entries. + */ +function parse3rdPartyLicenses(filePath) { + const content = fs.readFileSync(filePath, 'utf-8'); + const blocks = content.split(/^-{80,}$/m).map(b => b.trim()).filter(Boolean); + + const entries = []; + + for(const block of blocks) { + // Try to extract package name (first line), license (second line), rest is license text + const lines = block.split(/\r?\n/).map(l => l.trim()); + if(lines.length < 2) { + continue; + } + + const pkg = lines[0].replace('Package: ', '').trim(); + const license = lines[1].replace('License: ', '').replaceAll('"', '').replaceAll('\'', '').trim(); + const licenseText = lines.slice(2).join('\n'); + entries.push({package: pkg, license, licenseText}); + } + return entries; +} + +/** + * @param {string} license + * @param allowedLicenses + * @returns {boolean} + */ +function isAllowedLicense(license, allowedLicenses) { + return allowedLicenses.includes(license); +} + +/** + * + * @param {string} packageName + * @param {string} licenseText + * @returns {boolean} + */ +function deepLicenseCheck(packageName, licenseText) { + switch(packageName) { + case "primeng": + case "@primeng/themes": + return licenseText.includes("The MIT License (MIT)"); + + // FontAwesome special cases + case "Font Awesome Free License": + return licenseText.includes("Font Awesome Free is free, open source, and GPL friendly."); + + case "# Icons: CC BY 4.0 License (https://creativecommons.org/licenses/by/4.0/)": + return licenseText.includes("Attribution 4.0 International License"); + + case "# Fonts: SIL OFL 1.1 License": + return licenseText.includes("In the Font Awesome Free download, the SIL OFL license applies to all icons"); + + case "# Code: MIT License (https://opensource.org/licenses/MIT)": + return licenseText.includes("In the Font Awesome Free download, the MIT license applies to all non-font and"); + + case "# Attribution": + return licenseText.includes("Attribution is required by MIT, SIL OFL, and CC BY licenses. Downloaded Font"); + + case "# Brand Icons": + return licenseText.includes("All brand icons are trademarks of their respective owners. The use of these"); + + } + + return false; +} + +if(require.main === module) { + const filePath = process.argv[2] || path.join(__dirname, '3rdpartylicenses.txt'); + if(!fs.existsSync(filePath)) { + console.error('File not found:', filePath); + process.exit(1); + } + + const allowedLicenses = [ + 'MIT', + 'ISC', + 'Apache-2.0', + '(Apache-2.0 OR MIT)', // vis-timeline + '0BSD', + 'BSD-2-Clause', + 'BSD-3-Clause', + '(CC-BY-4.0 AND MIT)' // FontAwesome + ]; + + const packages = parse3rdPartyLicenses(filePath); + + let errors = {}; + forEach(packages, (pkg) => { + if(!isAllowedLicense(pkg.license, allowedLicenses)) { + errors[pkg.package] = pkg; + // Strange license found - further investigation needed + if(!deepLicenseCheck(pkg.package, pkg.licenseText)) { + console.error(`Package "${pkg.package}" has a non-allowed license: "${pkg.license}"`); + process.exit(1); + } else { + // Clear error + delete errors[pkg.package]; + } + } + }); + + if(Object.keys(errors).length > 0) { + console.error('License check failed for the following packages:'); + forEach(errors, (err) => { + console.error(`- ${err.package}: ${err.license}`); + }); + process.exit(1); + } + + console.log('All package licenses are allowed.'); + +} + diff --git a/src/app/app.component.html b/src/app/app.component.html index 91f4ab6d1..16c903e87 100644 --- a/src/app/app.component.html +++ b/src/app/app.component.html @@ -1,22 +1,37 @@
- - + @if (( this.LayoutService.layout$ | async ) == LayoutOptions.Default) { + + } - + @if (( this.LayoutService.layout$ | async ) != LayoutOptions.Kiosk) { + + }
diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 2483433aa..000a40eb4 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -10,7 +10,7 @@ import { Renderer2, signal } from '@angular/core'; -import { NavigationEnd, Router, RouterOutlet } from '@angular/router'; +import { ActivatedRoute, NavigationEnd, Router, RouterOutlet } from '@angular/router'; import { FaIconLibrary, FontAwesomeModule } from '@fortawesome/angular-fontawesome'; import { fas } from '@fortawesome/free-solid-svg-icons'; import { far } from '@fortawesome/free-regular-svg-icons'; @@ -22,7 +22,7 @@ import { HistoryService } from './history.service'; import { CoreuiHeaderComponent } from './layouts/coreui/coreui-header/coreui-header.component'; import { CoreuiNavbarComponent } from './layouts/coreui/coreui-navbar/coreui-navbar.component'; import { GlobalLoaderComponent } from './layouts/coreui/global-loader/global-loader.component'; -import { AsyncPipe, NgClass, NgIf } from '@angular/common'; +import { AsyncPipe, NgClass } from '@angular/common'; import { Subscription } from 'rxjs'; import { toObservable } from '@angular/core/rxjs-interop'; import { LayoutOptions, LayoutService } from './layouts/coreui/layout.service'; @@ -36,7 +36,8 @@ import { AuthService } from './auth/auth.service'; import { TitleService } from './services/title.service'; import { SystemnameService } from './services/systemname.service'; import { PermissionsService } from './permissions/permissions.service'; -import { TimezoneConfiguration, TimezoneService } from './services/timezone.service'; +import { TimezoneService } from './services/timezone.service'; +import { TranslocoDirective } from '@jsverse/transloco'; @Component({ selector: 'oitc-root', @@ -48,17 +49,17 @@ import { TimezoneConfiguration, TimezoneService } from './services/timezone.serv CoreuiNavbarComponent, GlobalLoaderComponent, ShadowOnScrollDirective, - NgIf, AsyncPipe, NgClass, - MessageOfTheDayModalComponent + MessageOfTheDayModalComponent, + TranslocoDirective ], templateUrl: './app.component.html', styleUrl: './app.component.css', changeDetection: ChangeDetectionStrategy.OnPush }) export class AppComponent implements OnInit, OnDestroy, AfterViewInit { - + // Inject HistoryService to keep track of the previous URLs private historyService: HistoryService = inject(HistoryService); @@ -80,6 +81,7 @@ export class AppComponent implements OnInit, OnDestroy, AfterViewInit { constructor(library: FaIconLibrary, private router: Router, + private route: ActivatedRoute, private IconSetService: IconSetService, // private selectConfig: NgSelectConfig, // private TranslocoService: TranslocoService, @@ -119,6 +121,12 @@ export class AppComponent implements OnInit, OnDestroy, AfterViewInit { } })); + this.subscription.add(this.route.queryParams.subscribe(params => { + if (params && params.hasOwnProperty('kiosk')) { + this.LayoutService.setLayout(LayoutOptions.Kiosk); + } + })); + // Fetch the message of the day this.watchMessageOfTheDay(); diff --git a/src/app/app.routes.ts b/src/app/app.routes.ts index 1a052dc6a..f3b55a376 100644 --- a/src/app/app.routes.ts +++ b/src/app/app.routes.ts @@ -40,6 +40,9 @@ import { servicenowModuleRoutes } from './modules/servicenow_module/servicenow_m import { dellModuleRoutes } from './modules/dell_module/dell_module.routes'; import { proxmoxModuleRoutes } from './modules/proxmox_module/proxmox_module.routes'; import { ms365ModuleRoutes } from './modules/ms365_module/ms365_module.routes'; +import { mshypervModuleRoutes } from './modules/mshyperv_module/mshyperv_module.routes'; +import { ciscoModuleRoutes } from './modules/cisco_module/cisco_module.routes'; +import { broadcomProxyModuleRoutes } from './modules/broadcomproxy_module/broadcomproxy_module.routes'; @Component({ selector: 'legacy-redirect', @@ -101,7 +104,10 @@ const moduleRoutes: Routes = [ ...servicenowModuleRoutes, ...dellModuleRoutes, ...proxmoxModuleRoutes, - ...ms365ModuleRoutes + ...ms365ModuleRoutes, + ...mshypervModuleRoutes, + ...ciscoModuleRoutes, + ...broadcomProxyModuleRoutes ]; /*** Core routes ***/ const coreRoutes: Routes = [{ diff --git a/src/app/components/charts/host-pie-chart/host-pie-chart.component.html b/src/app/components/charts/host-pie-chart/host-pie-chart.component.html index dc510ce75..4f9020252 100644 --- a/src/app/components/charts/host-pie-chart/host-pie-chart.component.html +++ b/src/app/components/charts/host-pie-chart/host-pie-chart.component.html @@ -1,11 +1,7 @@ - +
+
+
diff --git a/src/app/components/charts/host-pie-chart/host-pie-chart.component.ts b/src/app/components/charts/host-pie-chart/host-pie-chart.component.ts index cc0ae421f..8b4f6d2e0 100644 --- a/src/app/components/charts/host-pie-chart/host-pie-chart.component.ts +++ b/src/app/components/charts/host-pie-chart/host-pie-chart.component.ts @@ -3,6 +3,7 @@ import { ChangeDetectorRef, Component, effect, + ElementRef, inject, input, Input, @@ -12,37 +13,28 @@ import { SimpleChanges, ViewChild } from '@angular/core'; -import { - ApexChart, - ApexGrid, - ApexLegend, - ApexNonAxisChartSeries, - ApexPlotOptions, - ApexResponsive, - ChartComponent, - NgApexchartsModule -} from 'ng-apexcharts'; import { TranslocoService } from '@jsverse/transloco'; import { Subscription } from 'rxjs'; import { LayoutService } from '../../../layouts/coreui/layout.service'; +import { BarChart } from 'echarts/charts'; +import { LegendComponent, PolarComponent, TitleComponent, TooltipComponent } from 'echarts/components'; +import { NgxEchartsDirective, provideEchartsCore } from 'ngx-echarts'; +import * as echarts from 'echarts/core'; +import 'echarts/theme/dark.js'; +import { EChartsOption } from 'echarts'; +echarts.use([BarChart, LegendComponent, TitleComponent, TooltipComponent, PolarComponent]); -export type ChartOptions = { - series: ApexNonAxisChartSeries; - chart: ApexChart; - labels: string[]; - colors: string[]; - legend: ApexLegend; - plotOptions: ApexPlotOptions; - responsive: ApexResponsive | ApexResponsive[]; -}; @Component({ selector: 'oitc-host-pie-chart', imports: [ - NgApexchartsModule + NgxEchartsDirective + ], + providers: [ + provideEchartsCore({echarts}), ], templateUrl: './host-pie-chart.component.html', styleUrl: './host-pie-chart.component.css', @@ -50,10 +42,8 @@ export type ChartOptions = { }) export class HostPieChartComponent implements OnInit, OnChanges, OnDestroy { - @ViewChild("chart") chart!: ChartComponent; - public chartOptions!: Partial; - @Input() public statusDataPercentage: number[] = [0, 0, 0]; + @ViewChild('boxContainer') boxContainer?: ElementRef; public triggerUpdate = input(0); @@ -61,38 +51,23 @@ export class HostPieChartComponent implements OnInit, OnChanges, OnDestroy { private readonly TranslocoService = inject(TranslocoService); private readonly LayoutService = inject(LayoutService); private cdr = inject(ChangeDetectorRef); - - public apexGridOptions: ApexGrid = {}; + public chartOptions: EChartsOption = {}; + public theme: string = ''; + public echartsInstance: any; + public widgetHeight: number = 0; constructor() { // Subscribe to the color mode changes (drop down menu in header) this.subscription.add(this.LayoutService.theme$.subscribe((theme) => { //console.log('Change in theme detected', theme); - if (this.chart && this.chartOptions) { - - // Read the background color value from the CSS variable and update the chart - let cuiSecondaryBg = getComputedStyle(document.documentElement).getPropertyValue('--cui-secondary-bg').trim(); - - //this.chartOptions.plotOptions?.radialBar?.track?.background = cuiSecondaryBg; - this.chart.updateOptions({ - plotOptions: { - radialBar: { - track: { - background: cuiSecondaryBg - } - } - } - }); - } this.cdr.markForCheck(); })); effect(() => { if (this.triggerUpdate() > 0) { + this.initializeChartOptions(); // External component has triggered an update - if (this.chart) { - this.chart.updateOptions(this.chartOptions); - } + } }); } @@ -102,11 +77,15 @@ export class HostPieChartComponent implements OnInit, OnChanges, OnDestroy { } public ngOnChanges(changes: SimpleChanges): void { - if (changes['statusDataPercentage'] && this.chart) { - this.chartOptions.series = this.statusDataPercentage; - this.chart.updateOptions({ - series: this.statusDataPercentage - }); + if (changes['statusDataPercentage']) { + if (!changes['statusDataPercentage'].firstChange) { + this.statusDataPercentage = [ + changes['statusDataPercentage'].currentValue[0] || 0, // Up + changes['statusDataPercentage'].currentValue[1] || 0, // Down + changes['statusDataPercentage'].currentValue[2] || 0, // Unreachable + ]; + } + this.initializeChartOptions(); this.cdr.markForCheck(); } } @@ -117,91 +96,148 @@ export class HostPieChartComponent implements OnInit, OnChanges, OnDestroy { private initializeChartOptions() { let labels = [ - this.TranslocoService.translate('Up'), - this.TranslocoService.translate('Down'), this.TranslocoService.translate('Unreachable'), + this.TranslocoService.translate('Down'), + this.TranslocoService.translate('Up') ]; - let cuiSecondaryBg = getComputedStyle(document.documentElement).getPropertyValue('--cui-secondary-bg').trim(); this.chartOptions = { - series: this.statusDataPercentage, - chart: { - height: 300, - offsetY: -20, - type: "radialBar", - - // The sparkline option remove all paddings - // https://github.com/apexcharts/apexcharts.js/issues/1272#issuecomment-591388290 - sparkline: { - enabled: true - }, - }, - plotOptions: { - radialBar: { - offsetY: 0, - startAngle: -90, - endAngle: 90, - hollow: { - margin: 5, - size: "50%", - background: "transparent", - image: undefined - }, - track: { - show: true, - background: cuiSecondaryBg, - opacity: 0.5, - dropShadow: { - enabled: false, - top: 2, - left: 0, - color: '#999999', - opacity: 0.2, - blur: 2 - } - }, - dataLabels: { - name: { - show: true - }, - value: { - show: true - } - } - }, + animation: false, + angleAxis: { + show: false, + max: 100, + startAngle: 180, + endAngle: 0, }, - colors: ["#00bc4c", "#bf0000", "#6b737c"], - labels: labels, - legend: { + radiusAxis: { show: false, - /* floating: true, - fontSize: "16px", - position: "left", - offsetX: 60, - offsetY: 10, - labels: { - useSeriesColors: true - }, - formatter: function (seriesName: string, opts: any) { - return seriesName + ": " + opts.w.globals.series[opts.seriesIndex]; - }, - itemMargin: { - horizontal: 3 - }*/ + type: 'category', + data: labels }, - responsive: [ + polar: { + radius: [70, '65%'], + center: ['50%', '40%'] + }, + tooltip: { + show: true, + formatter: '{b} ({c}%)', + appendToBody: true + }, + series: [ { - breakpoint: 480, - options: { - legend: { - show: false + type: 'bar', + barWidth: '70%', + data: [ + { + value: this.statusDataPercentage[2], + itemStyle: { + color: { + type: 'linear', + x: 0.5, + y: 0.5, + x2: 1.5, + y2: 1.5, + colorStops: [ + { + offset: 0.1, + color: '#6b737c' // color at 0% + }, + { + offset: 1, + color: '#4f555a' // color at 100% + } + ], + global: false // default is false + } + } + }, + { + value: this.statusDataPercentage[1], + itemStyle: { + color: { + type: 'linear', + x: 0.5, + y: 0.5, + x2: 1.5, + y2: 1.5, + colorStops: [ + { + offset: 0.1, + color: '#CC0000' // color at 0% + }, + { + offset: 1, + color: '#c0022e' // color at 100% + } + ], + global: false // default is false + } + } + }, + { + value: this.statusDataPercentage[0], + itemStyle: { + color: { + type: 'linear', + x: 0.5, + y: 0.5, + x2: 1.5, + y2: 1.5, + colorStops: [ + { + offset: 0.1, + color: '#00bc4c' // color at 0% + }, + { + offset: 1, + color: '#039a3f' // color at 100% + } + ], + global: false // default is false + }, + borderWidth: 1 + } } - } + ], + colorBy: 'series', + showBackground: true, + backgroundStyle: { + color: { + type: 'linear', + x: 0.0, + y: 0.0, + x2: 0.0, + y2: 1.0, + colorStops: [ + { + offset: 0.1, + color: '#cccccc19' // color at 0% + }, + { + offset: 0.5, + color: '#cccccc19' // color at 0% + }, + { + offset: 0.5, + color: 'transparent' // color at 100% + } + ] + + }, + borderColor: '#000', + borderRadius: [5, 5, 0, 0] + }, + coordinateSystem: 'polar' } ] }; this.cdr.markForCheck(); } - + onChartInit(ec: any) { + this.echartsInstance = ec; + //https://github.com/apache/echarts/issues/20302 + this.widgetHeight = this.echartsInstance.getHeight() / 2; + this.cdr.markForCheck(); + } } diff --git a/src/app/components/charts/host-radialbar-chart/host-radialbar-chart.component.html b/src/app/components/charts/host-radialbar-chart/host-radialbar-chart.component.html deleted file mode 100644 index fca15bfec..000000000 --- a/src/app/components/charts/host-radialbar-chart/host-radialbar-chart.component.html +++ /dev/null @@ -1,13 +0,0 @@ - -
- -
diff --git a/src/app/components/charts/host-radialbar-chart/host-radialbar-chart.component.ts b/src/app/components/charts/host-radialbar-chart/host-radialbar-chart.component.ts deleted file mode 100644 index 3ae81718f..000000000 --- a/src/app/components/charts/host-radialbar-chart/host-radialbar-chart.component.ts +++ /dev/null @@ -1,215 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - inject, - Input, - OnChanges, - OnDestroy, - OnInit, - SimpleChanges, - ViewChild -} from '@angular/core'; -import { ApexGrid, ChartComponent } from 'ng-apexcharts'; -import { Subscription } from 'rxjs'; -import { ChartOptions } from '../host-pie-chart/host-pie-chart.component'; -import { TranslocoService } from '@jsverse/transloco'; -import { ChartAbsolutValue } from '../charts.interface'; -import { LayoutService } from '../../../layouts/coreui/layout.service'; - -@Component({ - selector: 'oitc-host-radialbar-chart', - imports: [ - ChartComponent - ], - templateUrl: './host-radialbar-chart.component.html', - styleUrl: './host-radialbar-chart.component.css', - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class HostRadialbarChartComponent implements OnInit, OnChanges, OnDestroy { - - @ViewChild("chart") chart!: ChartComponent; - public chartOptions!: Partial; - - @Input() public statusDataPercentage: number[] = []; - @Input() public statusDataAbsolut: ChartAbsolutValue[] = []; - @Input() public maxWidth: number = 450; - @Input() public offsetX: number = 180; - - private chartData: number[] = []; - - private subscription: Subscription = new Subscription(); - private readonly TranslocoService = inject(TranslocoService); - private readonly LayoutService = inject(LayoutService); - private cdr = inject(ChangeDetectorRef); - - public apexGridOptions: ApexGrid = { - padding: { - top: 0, - right: 0, - bottom: 40, - left: 0 - }, - }; - - constructor() { - // Subscribe to the color mode changes (drop down menu in header) - this.subscription.add(this.LayoutService.theme$.subscribe((theme) => { - //console.log('Change in theme detected', theme); - if (this.chart && this.chartOptions) { - - // Read the background color value from the CSS variable and update the chart - let cuiSecondaryBg = getComputedStyle(document.documentElement).getPropertyValue('--cui-secondary-bg').trim(); - - //this.chartOptions.plotOptions?.radialBar?.track?.background = cuiSecondaryBg; - this.chart.updateOptions({ - plotOptions: { - radialBar: { - track: { - background: cuiSecondaryBg - } - } - } - }); - } - this.cdr.markForCheck(); - })); - } - - public ngOnInit(): void { - this.chartData = this.statusDataPercentage; - this.initializeChartOptions(); - } - - public ngOnChanges(changes: SimpleChanges): void { - if (changes['statusDataPercentage'] && this.chart) { - // Chart data is as percentage - nothing to do - - this.chartData = this.statusDataPercentage; - - this.chartOptions.series = this.chartData; - this.chart.updateOptions({ - series: this.chartData - }); - this.cdr.markForCheck(); - } - - if (changes['statusDataAbsolut'] && this.chart) { - // Chart data is as absolute values - convert to percentage - this.chartData = []; - this.statusDataAbsolut.forEach((item) => { - this.chartData.push(item.Value / item.Total * 100); - }); - - this.chartOptions.series = this.chartData; - this.chart.updateOptions({ - series: this.chartData - }); - this.cdr.markForCheck(); - } - } - - public ngOnDestroy(): void { - this.subscription.unsubscribe(); - } - - private initializeChartOptions() { - let labels = [ - this.TranslocoService.translate('Up'), - this.TranslocoService.translate('Down'), - this.TranslocoService.translate('Unreachable'), - ]; - - let cuiSecondaryBg = getComputedStyle(document.documentElement).getPropertyValue('--cui-secondary-bg').trim(); - this.chartOptions = { - series: this.chartData, - chart: { - height: 200, - offsetY: -20, - type: "radialBar", - - // The sparkline option remove all paddings - // https://github.com/apexcharts/apexcharts.js/issues/1272#issuecomment-591388290 - sparkline: { - enabled: true - }, - }, - plotOptions: { - radialBar: { - offsetY: 0, - startAngle: -180, - endAngle: 90, - hollow: { - margin: 5, - size: "30%", - background: "transparent", - image: undefined, - }, - track: { - show: true, - background: cuiSecondaryBg, - opacity: 0.5, - dropShadow: { - enabled: false, - top: 2, - left: 0, - color: '#999999', - opacity: 0.2, - blur: 2 - } - }, - dataLabels: { - name: { - show: false - }, - value: { - show: false - } - } - }, - }, - colors: ["#00bc4c", "#bf0000", "#6b737c"], - labels: labels, - legend: { - show: true, - floating: true, - fontSize: "12px", - position: "left", - offsetY: 80, - offsetX: this.offsetX, - markers: { - strokeWidth: 0, - fillColors: ['transparent', 'transparent', 'transparent'], - }, - labels: { - useSeriesColors: true - }, - formatter: function (seriesName, opts) { - const value = parseFloat(opts.w.globals.series[opts.seriesIndex]); - - return `${seriesName}: ${value.toLocaleString(undefined, {maximumFractionDigits: 3})}%` - }, - itemMargin: { - horizontal: 3, - vertical: 1 - }, - inverseOrder: true, - onItemClick: { - toggleDataSeries: false - }, - - }, - responsive: [ - { - breakpoint: 480, - options: { - legend: { - show: false - } - } - } - ] - }; - this.cdr.markForCheck(); - } -} diff --git a/src/app/components/charts/host-radialbar-chart/radialbar-host-chart.png b/src/app/components/charts/host-radialbar-chart/radialbar-host-chart.png deleted file mode 100644 index 6faf04061..000000000 Binary files a/src/app/components/charts/host-radialbar-chart/radialbar-host-chart.png and /dev/null differ diff --git a/src/app/components/charts/service-pie-chart/service-pie-chart.component.html b/src/app/components/charts/service-pie-chart/service-pie-chart.component.html index 8ad378131..4f9020252 100644 --- a/src/app/components/charts/service-pie-chart/service-pie-chart.component.html +++ b/src/app/components/charts/service-pie-chart/service-pie-chart.component.html @@ -1,12 +1,7 @@ - +
+
+
diff --git a/src/app/components/charts/service-pie-chart/service-pie-chart.component.ts b/src/app/components/charts/service-pie-chart/service-pie-chart.component.ts index 7d4a210b5..df124b0c0 100644 --- a/src/app/components/charts/service-pie-chart/service-pie-chart.component.ts +++ b/src/app/components/charts/service-pie-chart/service-pie-chart.component.ts @@ -9,39 +9,30 @@ import { OnChanges, OnDestroy, OnInit, - SimpleChanges, - ViewChild + SimpleChanges } from '@angular/core'; -import { - ApexChart, - ApexGrid, - ApexLegend, - ApexNonAxisChartSeries, - ApexPlotOptions, - ApexResponsive, - ApexStroke, - ChartComponent, - NgApexchartsModule -} from 'ng-apexcharts'; + import { TranslocoService } from '@jsverse/transloco'; import { Subscription } from 'rxjs'; import { LayoutService } from '../../../layouts/coreui/layout.service'; +import { BarChart } from 'echarts/charts'; +import { LegendComponent, PolarComponent, TitleComponent, TooltipComponent } from 'echarts/components'; +import { NgxEchartsDirective, provideEchartsCore } from 'ngx-echarts'; +import * as echarts from 'echarts/core'; +import 'echarts/theme/dark.js'; +import { EChartsOption } from 'echarts'; + +echarts.use([BarChart, LegendComponent, TitleComponent, TooltipComponent, PolarComponent]); -export type ChartOptions = { - series: ApexNonAxisChartSeries; - chart: ApexChart; - labels: string[]; - colors: string[]; - legend: ApexLegend; - plotOptions: ApexPlotOptions; - responsive: ApexResponsive | ApexResponsive[]; -}; @Component({ selector: 'oitc-service-pie-chart', imports: [ - NgApexchartsModule + NgxEchartsDirective + ], + providers: [ + provideEchartsCore({echarts}), ], templateUrl: './service-pie-chart.component.html', styleUrl: './service-pie-chart.component.css', @@ -49,10 +40,7 @@ export type ChartOptions = { }) export class ServicePieChartComponent implements OnInit, OnChanges, OnDestroy { - @ViewChild("chart") chart!: ChartComponent; - public chartOptions!: Partial; - - @Input() public statusDataPercentage: number[] = [0, 0, 0, 0]; + @Input() public statusDataPercentage: number[] = [0, 0, 0]; public triggerUpdate = input(0); @@ -60,40 +48,24 @@ export class ServicePieChartComponent implements OnInit, OnChanges, OnDestroy { private readonly TranslocoService = inject(TranslocoService); private readonly LayoutService = inject(LayoutService); private cdr = inject(ChangeDetectorRef); + public chartOptions: EChartsOption = {}; + public theme: string = ''; + public echartsInstance: any; + public widgetHeight: number = 0; - public apexGridOptions: ApexGrid = {}; - public apexStroke: ApexStroke = {}; constructor() { // Subscribe to the color mode changes (drop down menu in header) this.subscription.add(this.LayoutService.theme$.subscribe((theme) => { //console.log('Change in theme detected', theme); - if (this.chart && this.chartOptions) { - - // Read the background color value from the CSS variable and update the chart - let cuiSecondaryBg = getComputedStyle(document.documentElement).getPropertyValue('--cui-secondary-bg').trim(); - - //this.chartOptions.plotOptions?.radialBar?.track?.background = cuiSecondaryBg; - this.chart.updateOptions({ - plotOptions: { - radialBar: { - track: { - background: cuiSecondaryBg - } - } - } - }); - this.cdr.markForCheck(); - } - + this.cdr.markForCheck(); })); effect(() => { if (this.triggerUpdate() > 0) { + this.initializeChartOptions(); // External component has triggered an update - if (this.chart) { - this.chart.updateOptions(this.chartOptions); - } + } }); } @@ -103,11 +75,16 @@ export class ServicePieChartComponent implements OnInit, OnChanges, OnDestroy { } public ngOnChanges(changes: SimpleChanges): void { - if (changes['statusDataPercentage'] && this.chart) { - this.chartOptions.series = this.statusDataPercentage; - this.chart.updateOptions({ - series: this.statusDataPercentage - }); + if (changes['statusDataPercentage']) { + if (!changes['statusDataPercentage'].firstChange) { + this.statusDataPercentage = [ + changes['statusDataPercentage'].currentValue[0] || 0, // Ok + changes['statusDataPercentage'].currentValue[1] || 0, // Warning + changes['statusDataPercentage'].currentValue[2] || 0, // Critical + changes['statusDataPercentage'].currentValue[3] || 0, // Unknown + ]; + } + this.initializeChartOptions(); this.cdr.markForCheck(); } } @@ -118,90 +95,172 @@ export class ServicePieChartComponent implements OnInit, OnChanges, OnDestroy { private initializeChartOptions() { let labels = [ - this.TranslocoService.translate('Ok'), - this.TranslocoService.translate('Warning'), - this.TranslocoService.translate('Critical'), this.TranslocoService.translate('Unknown'), + this.TranslocoService.translate('Critical'), + this.TranslocoService.translate('Warning'), + this.TranslocoService.translate('Ok') ]; - let cuiSecondaryBg = getComputedStyle(document.documentElement).getPropertyValue('--cui-secondary-bg').trim(); this.chartOptions = { - series: this.statusDataPercentage, - chart: { - height: 300, - offsetY: -20, - type: "radialBar", - - // The sparkline option remove all paddings - // https://github.com/apexcharts/apexcharts.js/issues/1272#issuecomment-591388290 - sparkline: { - enabled: true - }, - }, - plotOptions: { - radialBar: { - offsetY: 0, - startAngle: -90, - endAngle: 90, - hollow: { - margin: 5, - size: "40%", - background: "transparent", - image: undefined - }, - track: { - show: true, - background: cuiSecondaryBg, - opacity: 0.5, - dropShadow: { - enabled: false, - top: 2, - left: 0, - color: '#999999', - opacity: 0.2, - blur: 2 - } - }, - dataLabels: { - name: { - show: true - }, - value: { - show: true - } - } - }, + animation: false, + angleAxis: { + show: false, + max: 100, + startAngle: 180, + endAngle: 0, }, - colors: ["#00bc4c", '#efaf2f', "#bf0000", "#6b737c"], - labels: labels, - legend: { + radiusAxis: { show: false, - /* floating: true, - fontSize: "16px", - position: "left", - offsetX: 60, - offsetY: 10, - labels: { - useSeriesColors: true - }, - formatter: function (seriesName: string, opts: any) { - return seriesName + ": " + opts.w.globals.series[opts.seriesIndex]; - }, - itemMargin: { - horizontal: 3 - }*/ + type: 'category', + data: labels }, - responsive: [ + polar: { + radius: [60, '65%'], + center: ['50%', '40%'] + }, + tooltip: { + show: true, + formatter: '{b} ({c}%)', + appendToBody: true + }, + series: [ { - breakpoint: 480, - options: { - legend: { - show: false + type: 'bar', + barWidth: '70%', + data: [ + { + value: this.statusDataPercentage[3], + itemStyle: { + color: { + type: 'linear', + x: 0.5, + y: 0.5, + x2: 1.5, + y2: 1.5, + colorStops: [ + { + offset: 0.1, + color: '#6b737c' // color at 0% + }, + { + offset: 1, + color: '#4f555a' // color at 100% + } + ], + global: false // default is false + } + } + }, + { + value: this.statusDataPercentage[2], + itemStyle: { + color: { + type: 'linear', + x: 0.5, + y: 0.5, + x2: 1.5, + y2: 1.5, + colorStops: [ + { + offset: 0.1, + color: '#CC0000' // color at 0% + }, + { + offset: 1, + color: '#c0022e' // color at 100% + } + ], + global: false // default is false + } + } + }, + { + value: this.statusDataPercentage[1], + itemStyle: { + color: { + type: 'linear', + x: 0.5, + y: 0.5, + x2: 1.5, + y2: 1.5, + colorStops: [ + { + offset: 0.1, + color: '#ffbb33' // color at 0% + }, + { + offset: 1, + color: '#ffbb33' // color at 100% + } + ], + global: false // default is false + } + } + }, + { + value: this.statusDataPercentage[0], + itemStyle: { + color: { + type: 'linear', + x: 0.5, + y: 0.5, + x2: 1.5, + y2: 1.5, + colorStops: [ + { + offset: 0.1, + color: '#00bc4c' // color at 0% + }, + { + offset: 1, + color: '#039a3f' // color at 100% + } + ], + global: false // default is false + }, + borderWidth: 1 + } } - } + ], + colorBy: 'series', + showBackground: true, + backgroundStyle: { + color: { + type: 'linear', + x: 0.0, + y: 0.0, + x2: 0.0, + y2: 1.0, + colorStops: [ + { + offset: 0.1, + color: '#cccccc19' // color at 0% + }, + { + offset: 0.5, + color: '#cccccc19' // color at 0% + }, + { + offset: 0.5, + color: 'transparent' // color at 100% + } + ] + + }, + borderColor: '#000', + borderRadius: [5, 5, 0, 0] + }, + coordinateSystem: 'polar' } ] }; this.cdr.markForCheck(); } + + onChartInit(ec: any) { + this.echartsInstance = ec; + //https://github.com/apache/echarts/issues/20302 + this.widgetHeight = this.echartsInstance.getHeight() / 2; + this.cdr.markForCheck(); + } } diff --git a/src/app/components/charts/service-radialbar-chart/radialbar-service-chart.png b/src/app/components/charts/service-radialbar-chart/radialbar-service-chart.png deleted file mode 100644 index d8041b174..000000000 Binary files a/src/app/components/charts/service-radialbar-chart/radialbar-service-chart.png and /dev/null differ diff --git a/src/app/components/charts/service-radialbar-chart/service-radialbar-chart.component.html b/src/app/components/charts/service-radialbar-chart/service-radialbar-chart.component.html deleted file mode 100644 index fca15bfec..000000000 --- a/src/app/components/charts/service-radialbar-chart/service-radialbar-chart.component.html +++ /dev/null @@ -1,13 +0,0 @@ - -
- -
diff --git a/src/app/components/charts/service-radialbar-chart/service-radialbar-chart.component.spec.ts b/src/app/components/charts/service-radialbar-chart/service-radialbar-chart.component.spec.ts deleted file mode 100644 index 8e224250d..000000000 --- a/src/app/components/charts/service-radialbar-chart/service-radialbar-chart.component.spec.ts +++ /dev/null @@ -1,23 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { ServiceRadialbarChartComponent } from './service-radialbar-chart.component'; - -describe('ServiceRadialbarChartComponent', () => { - let component: ServiceRadialbarChartComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [ServiceRadialbarChartComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(ServiceRadialbarChartComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/app/components/charts/service-radialbar-chart/service-radialbar-chart.component.ts b/src/app/components/charts/service-radialbar-chart/service-radialbar-chart.component.ts deleted file mode 100644 index 4ed155786..000000000 --- a/src/app/components/charts/service-radialbar-chart/service-radialbar-chart.component.ts +++ /dev/null @@ -1,217 +0,0 @@ -import { - ChangeDetectionStrategy, - ChangeDetectorRef, - Component, - inject, - Input, - OnChanges, - OnDestroy, - OnInit, - SimpleChanges, - ViewChild -} from '@angular/core'; -import { ApexGrid, ChartComponent } from 'ng-apexcharts'; -import { ChartOptions } from '../host-pie-chart/host-pie-chart.component'; -import { ChartAbsolutValue } from '../charts.interface'; -import { Subscription } from 'rxjs'; -import { TranslocoService } from '@jsverse/transloco'; -import { LayoutService } from '../../../layouts/coreui/layout.service'; - -@Component({ - selector: 'oitc-service-radialbar-chart', - imports: [ - ChartComponent - ], - templateUrl: './service-radialbar-chart.component.html', - styleUrl: './service-radialbar-chart.component.css', - changeDetection: ChangeDetectionStrategy.OnPush -}) -export class ServiceRadialbarChartComponent implements OnInit, OnChanges, OnDestroy { - - @ViewChild("chart") chart!: ChartComponent; - public chartOptions!: Partial; - - @Input() public statusDataPercentage: number[] = []; - @Input() public statusDataAbsolut: ChartAbsolutValue[] = []; - - private chartData: number[] = []; - - public readonly maxWidth: number = 450; - - private subscription: Subscription = new Subscription(); - private readonly TranslocoService = inject(TranslocoService); - private readonly LayoutService = inject(LayoutService); - private cdr = inject(ChangeDetectorRef); - - public apexGridOptions: ApexGrid = { - padding: { - top: 0, - right: 0, - bottom: 40, - left: 0 - }, - }; - - constructor() { - // Subscribe to the color mode changes (drop down menu in header) - this.subscription.add(this.LayoutService.theme$.subscribe((theme) => { - //console.log('Change in theme detected', theme); - if (this.chart && this.chartOptions) { - - // Read the background color value from the CSS variable and update the chart - let cuiSecondaryBg = getComputedStyle(document.documentElement).getPropertyValue('--cui-secondary-bg').trim(); - - //this.chartOptions.plotOptions?.radialBar?.track?.background = cuiSecondaryBg; - this.chart.updateOptions({ - plotOptions: { - radialBar: { - track: { - background: cuiSecondaryBg - } - } - } - }); - this.cdr.markForCheck(); - } - - })); - } - - public ngOnInit(): void { - this.chartData = this.statusDataPercentage; - this.initializeChartOptions(); - } - - public ngOnChanges(changes: SimpleChanges): void { - if (changes['statusDataPercentage'] && this.chart) { - // Chart data is as percentage - nothing to do - - this.chartData = this.statusDataPercentage; - - this.chartOptions.series = this.chartData; - this.chart.updateOptions({ - series: this.chartData - }); - this.cdr.markForCheck(); - } - - if (changes['statusDataAbsolut'] && this.chart) { - // Chart data is as absolute values - convert to percentage - this.chartData = []; - this.statusDataAbsolut.forEach((item) => { - this.chartData.push(item.Value / item.Total * 100); - }); - - this.chartOptions.series = this.chartData; - this.chart.updateOptions({ - series: this.chartData - }); - this.cdr.markForCheck(); - } - } - - public ngOnDestroy(): void { - this.subscription.unsubscribe(); - } - - private initializeChartOptions() { - let labels = [ - this.TranslocoService.translate('Ok'), - this.TranslocoService.translate('Warning'), - this.TranslocoService.translate('Critical'), - this.TranslocoService.translate('Unknown'), - ]; - - let cuiSecondaryBg = getComputedStyle(document.documentElement).getPropertyValue('--cui-secondary-bg').trim(); - this.chartOptions = { - series: this.chartData, - chart: { - height: 200, - offsetY: -20, - type: "radialBar", - - // The sparkline option remove all paddings - // https://github.com/apexcharts/apexcharts.js/issues/1272#issuecomment-591388290 - sparkline: { - enabled: true - }, - }, - plotOptions: { - radialBar: { - offsetY: 0, - startAngle: -180, - endAngle: 90, - hollow: { - margin: 5, - size: "30%", - background: "transparent", - image: undefined, - }, - track: { - show: true, - background: cuiSecondaryBg, - opacity: 0.5, - dropShadow: { - enabled: false, - top: 2, - left: 0, - color: '#999999', - opacity: 0.2, - blur: 2 - } - }, - dataLabels: { - name: { - show: false - }, - value: { - show: false - } - } - }, - }, - colors: ["#00bc4c", '#efaf2f', "#bf0000", "#6b737c"], - labels: labels, - legend: { - show: true, - floating: true, - fontSize: "12px", - position: "left", - offsetY: 70, - offsetX: 180, - markers: { - strokeWidth: 0, - fillColors: ['transparent', 'transparent', 'transparent', 'transparent'], - }, - labels: { - useSeriesColors: true - }, - formatter: function (seriesName, opts) { - const value = parseFloat(opts.w.globals.series[opts.seriesIndex]); - - return `${seriesName}: ${value.toLocaleString(undefined, {maximumFractionDigits: 3})}%` - }, - itemMargin: { - horizontal: 3, - vertical: 1 - }, - inverseOrder: true, - onItemClick: { - toggleDataSeries: false - }, - - }, - responsive: [ - { - breakpoint: 480, - options: { - legend: { - show: false - } - } - } - ] - }; - this.cdr.markForCheck(); - } -} diff --git a/src/app/components/charts/sparkline-bar-echarts/sparkline-bar-echarts.component.spec.ts b/src/app/components/charts/sparkline-bar-echarts/sparkline-bar-echarts.component.spec.ts index c6e26b88c..e69de29bb 100644 --- a/src/app/components/charts/sparkline-bar-echarts/sparkline-bar-echarts.component.spec.ts +++ b/src/app/components/charts/sparkline-bar-echarts/sparkline-bar-echarts.component.spec.ts @@ -1,23 +0,0 @@ -import { ComponentFixture, TestBed } from '@angular/core/testing'; - -import { SparklineBarApexchartComponent } from './sparkline-bar-apexchart.component'; - -describe('SparklineBarApexchartComponent', () => { - let component: SparklineBarApexchartComponent; - let fixture: ComponentFixture; - - beforeEach(async () => { - await TestBed.configureTestingModule({ - imports: [SparklineBarApexchartComponent] - }) - .compileComponents(); - - fixture = TestBed.createComponent(SparklineBarApexchartComponent); - component = fixture.componentInstance; - fixture.detectChanges(); - }); - - it('should create', () => { - expect(component).toBeTruthy(); - }); -}); diff --git a/src/app/components/sparkline-stats/sparkline-stats.component.ts b/src/app/components/sparkline-stats/sparkline-stats.component.ts index 3bfdaa348..e91206d2e 100644 --- a/src/app/components/sparkline-stats/sparkline-stats.component.ts +++ b/src/app/components/sparkline-stats/sparkline-stats.component.ts @@ -8,52 +8,13 @@ import { SimpleChanges } from '@angular/core'; -import { - ApexAxisChartSeries, - ApexChart, - ApexDataLabels, - ApexFill, - ApexLegend, - ApexPlotOptions, - ApexTitleSubtitle, - ApexTooltip, - ApexXAxis, - ApexYAxis, - NgApexchartsModule -} from "ng-apexcharts"; + import { UUID } from '../../classes/UUID'; import uPlot from 'uplot'; - -export type ChartOptions = { - series: ApexAxisChartSeries; - chart: ApexChart; - xaxis: ApexXAxis; - markers: any; //ApexMarkers; - stroke: any; //ApexStroke; - yaxis: ApexYAxis | ApexYAxis[]; - plotOptions: ApexPlotOptions; - dataLabels: ApexDataLabels; - colors: string[]; - labels: string[] | number[]; - title: ApexTitleSubtitle; - subtitle: ApexTitleSubtitle; - legend: ApexLegend; - fill: ApexFill; - tooltip: ApexTooltip; -}; - -declare global { - interface Window { - Apex: any; - } -} - @Component({ selector: 'oitc-sparkline-stats', - imports: [ - NgApexchartsModule - ], + imports: [], templateUrl: './sparkline-stats.component.html', styleUrl: './sparkline-stats.component.css', changeDetection: ChangeDetectionStrategy.OnPush @@ -72,13 +33,7 @@ export class SparklineStatsComponent implements OnChanges { public values: number[] = []; private cdr = inject(ChangeDetectorRef); - - -// ngOnChanges(changes: SimpleChanges) { -// // REMOVE ME WHEN APEXCHARTS IS READY FOR ANGULAR !( -// return; -// } - + ngOnChanges(changes: SimpleChanges) { /* The "value" input is a number that is pushed into the "values" array. The "value" is the last value we want to display diff --git a/src/app/layouts/coreui/layout.service.ts b/src/app/layouts/coreui/layout.service.ts index fd3f44ff4..663cb0d47 100644 --- a/src/app/layouts/coreui/layout.service.ts +++ b/src/app/layouts/coreui/layout.service.ts @@ -1,10 +1,11 @@ -import { inject, Injectable, DOCUMENT } from '@angular/core'; +import { DOCUMENT, inject, Injectable } from '@angular/core'; import { BehaviorSubject, Observable } from 'rxjs'; export enum LayoutOptions { Default = 'default', - Blank = 'blank' + Blank = 'blank', + Kiosk = 'kiosk' } @Injectable({ diff --git a/src/app/modules/autoreport_module/pages/autoreports/autoreport-generate/autoreport-generate.component.html b/src/app/modules/autoreport_module/pages/autoreports/autoreport-generate/autoreport-generate.component.html index 0fcc8b66d..f27421f02 100644 --- a/src/app/modules/autoreport_module/pages/autoreports/autoreport-generate/autoreport-generate.component.html +++ b/src/app/modules/autoreport_module/pages/autoreports/autoreport-generate/autoreport-generate.component.html @@ -364,7 +364,7 @@

- + @if (this.PermissionsService.hasPermissionObservable(['services', 'browser'])|async) { @@ -618,7 +618,7 @@

- + @if (this.PermissionsService.hasPermissionObservable(['services', 'browser'])|async) { @@ -632,7 +632,6 @@

} - import('./pages/wizards/broadcom-proxy/broadcom-proxy.component').then(m => BroadcomProxyComponent) + }, +]; diff --git a/src/app/modules/broadcomproxy_module/pages/wizards/broadcom-proxy/broadcom-proxy-wizard.interface.ts b/src/app/modules/broadcomproxy_module/pages/wizards/broadcom-proxy/broadcom-proxy-wizard.interface.ts new file mode 100644 index 000000000..2554f6894 --- /dev/null +++ b/src/app/modules/broadcomproxy_module/pages/wizards/broadcom-proxy/broadcom-proxy-wizard.interface.ts @@ -0,0 +1,110 @@ +import { WizardGet, WizardPost } from '../../../../../pages/wizards/wizards.interface'; + +// WIZARD GET +export interface BroadcomProxyWizardGet extends WizardGet { + interfaceServicetemplate: InterfaceServicetemplate +} + + +export interface InterfaceServicetemplate { + id: number + uuid: string + template_name: string + name: string + container_id: number + servicetemplatetype_id: number + check_period_id: number + notify_period_id: number + description: string + command_id: number + check_command_args: string + checkcommand_info: string + eventhandler_command_id: number + timeperiod_id: number + check_interval: number + retry_interval: number + max_check_attempts: number + first_notification_delay: number + notification_interval: number + notify_on_warning: number + notify_on_unknown: number + notify_on_critical: number + notify_on_recovery: number + notify_on_flapping: number + notify_on_downtime: number + flap_detection_enabled: number + flap_detection_on_ok: number + flap_detection_on_warning: number + flap_detection_on_unknown: number + flap_detection_on_critical: number + low_flap_threshold: number + high_flap_threshold: number + process_performance_data: number + freshness_checks_enabled: number + freshness_threshold: any + passive_checks_enabled: number + event_handler_enabled: number + active_checks_enabled: number + retain_status_information: number + retain_nonstatus_information: number + notifications_enabled: number + notes: string + priority: number + tags: string + service_url: any + sla_relevant: number + is_volatile: number + check_freshness: number + created: string + modified: string + check_command: { + id: number + name: string + command_line: string + command_type: number + human_args: any + uuid: string + description: string + commandarguments: { + id: number + command_id: number + name: string + human_name: string + created: string + modified: string + }[] + } + servicetemplatecommandargumentvalues: Servicecommandargumentvalue[] +} + +export interface Servicecommandargumentvalue { + commandargument: Commandargument + commandargument_id: number + created: string + id: number + modified: string + servicetemplate_id: number + value: string +} + +export interface Commandargument { + command_id: number + created: string + human_name: string + id: number + modified: string + name: string +} + + +// WIZARD POST +export interface BroadcomProxyWizardPost extends WizardPost { + authPassword: string + authProtocol: string + privacyPassword: string + privacyProtocol: string + securityLevel: string + securityName: string + snmpCommunity: string + snmpVersion: string +} diff --git a/src/app/modules/broadcomproxy_module/pages/wizards/broadcom-proxy/broadcom-proxy-wizard.service.ts b/src/app/modules/broadcomproxy_module/pages/wizards/broadcom-proxy/broadcom-proxy-wizard.service.ts new file mode 100644 index 000000000..70c56ac34 --- /dev/null +++ b/src/app/modules/broadcomproxy_module/pages/wizards/broadcom-proxy/broadcom-proxy-wizard.service.ts @@ -0,0 +1,39 @@ +import { Injectable } from '@angular/core'; +import { catchError, map, Observable, of } from 'rxjs'; +import { WizardsService } from '../../../../../pages/wizards/wizards.service'; +import { GenericResponseWrapper, GenericValidationError } from '../../../../../generic-responses'; +import { BroadcomProxyWizardGet, BroadcomProxyWizardPost } from './broadcom-proxy-wizard.interface'; + +@Injectable({ + providedIn: 'root' +}) +export class BroadcomProxyWizardService extends WizardsService { + + public fetch(hostId: number): Observable { + return this.http.get(`${this.proxyPath}/broadcom_proxy_module/wizards/broadcomProxy/${hostId}.json?angular=true`).pipe( + map((data: BroadcomProxyWizardGet): BroadcomProxyWizardGet => { + return data; + }) + ); + } + + public submit(post: BroadcomProxyWizardPost): Observable { + return this.http.post(`${this.proxyPath}/broadcom_proxy_module/wizards/broadcomProxy.json?angular=true`, post) + .pipe( + map(data => { + return { + success: true, + data: null + }; + }), + catchError((error: any) => { + const err = error.error.error as GenericValidationError; + return of({ + success: false, + data: err + }); + }) + ); + + } +} diff --git a/src/app/components/charts/host-radialbar-chart/host-radialbar-chart.component.css b/src/app/modules/broadcomproxy_module/pages/wizards/broadcom-proxy/broadcom-proxy.component.css similarity index 100% rename from src/app/components/charts/host-radialbar-chart/host-radialbar-chart.component.css rename to src/app/modules/broadcomproxy_module/pages/wizards/broadcom-proxy/broadcom-proxy.component.css diff --git a/src/app/modules/broadcomproxy_module/pages/wizards/broadcom-proxy/broadcom-proxy.component.html b/src/app/modules/broadcomproxy_module/pages/wizards/broadcom-proxy/broadcom-proxy.component.html new file mode 100644 index 000000000..9cd208b5c --- /dev/null +++ b/src/app/modules/broadcomproxy_module/pages/wizards/broadcom-proxy/broadcom-proxy.component.html @@ -0,0 +1,218 @@ + + + + + + +
+ {{ t('Configuration Wizard: Broadcom Proxy') }} +
+
+ + +
+
+ + + {{ t('Host Information') }} + +
+
+ + + {{ t('Configure SNMP for Broadcom Proxy Monitoring') }} + +
+
+
+
+
+ + + +
+
+
+
+ + + +
+ +

+ {{ t('SNMP Server Settings') }} +

+
+ +
+ + + + +
+ +
+ + + + +
+ {{ t('Communication with authentication and privacy. The protocols used for Authentication are MD5 and SHA and for Privacy, DES (Data Encryption Standard) and AES (Advanced Encryption Standard).') }} + {{ t('Communication with authentication and without privacy. The protocols used for Authentication are MD5 and SHA (Secure Hash Algorithm).') }} + {{ t('Communication without authentication and privacy.') }} +
+
+ + +
+ + + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + + +
+ +
+ + + +
+ +
+ + + +
+
+ + + + +
+
+ +
diff --git a/src/app/modules/broadcomproxy_module/pages/wizards/broadcom-proxy/broadcom-proxy.component.spec.ts b/src/app/modules/broadcomproxy_module/pages/wizards/broadcom-proxy/broadcom-proxy.component.spec.ts new file mode 100644 index 000000000..f8169a62b --- /dev/null +++ b/src/app/modules/broadcomproxy_module/pages/wizards/broadcom-proxy/broadcom-proxy.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { BroadcomProxyComponent } from './broadcom-proxy.component'; + +describe('BroadcomProxyComponent', () => { + let component: BroadcomProxyComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [BroadcomProxyComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(BroadcomProxyComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/modules/broadcomproxy_module/pages/wizards/broadcom-proxy/broadcom-proxy.component.ts b/src/app/modules/broadcomproxy_module/pages/wizards/broadcom-proxy/broadcom-proxy.component.ts new file mode 100644 index 000000000..70039d806 --- /dev/null +++ b/src/app/modules/broadcomproxy_module/pages/wizards/broadcom-proxy/broadcom-proxy.component.ts @@ -0,0 +1,98 @@ +import { ChangeDetectionStrategy, Component, inject, ViewChild } from '@angular/core'; +import { WizardsAbstractComponent } from '../../../../../pages/wizards/wizards-abstract/wizards-abstract.component'; +import { SelectKeyValueString } from '../../../../../layouts/primeng/select.interface'; +import { + WizardsDynamicfieldsComponent +} from '../../../../../components/wizards/wizards-dynamicfields/wizards-dynamicfields.component'; +import { RouterLink } from '@angular/router'; +import { FaIconComponent } from '@fortawesome/angular-fontawesome'; +import { + CardBodyComponent, + CardComponent, + CardHeaderComponent, + CardTitleDirective, + FormControlDirective, + FormLabelDirective +} from '@coreui/angular'; +import { TranslocoDirective, TranslocoPipe } from '@jsverse/transloco'; +import { RequiredIconComponent } from '../../../../../components/required-icon/required-icon.component'; +import { BackButtonDirective } from '../../../../../directives/back-button.directive'; +import { BroadcomProxyWizardPost } from './broadcom-proxy-wizard.interface'; +import { BroadcomProxyWizardService } from './broadcom-proxy-wizard.service'; +import { SelectComponent } from '../../../../../layouts/primeng/select/select/select.component'; +import { FormErrorDirective } from '../../../../../layouts/coreui/form-error.directive'; +import { FormFeedbackComponent } from '../../../../../layouts/coreui/form-feedback/form-feedback.component'; +import { FormsModule } from '@angular/forms'; +import { NgIf } from '@angular/common'; + +@Component({ + selector: 'oitc-broadcom-proxy', + imports: [ + RouterLink, + FaIconComponent, + CardComponent, + CardHeaderComponent, + CardBodyComponent, + BackButtonDirective, + TranslocoPipe, + RequiredIconComponent, + FormLabelDirective, + SelectComponent, + FormErrorDirective, + FormFeedbackComponent, + FormsModule, + FormControlDirective, + WizardsDynamicfieldsComponent, + TranslocoDirective, + CardTitleDirective, + NgIf + ], + templateUrl: './broadcom-proxy.component.html', + styleUrl: './broadcom-proxy.component.css', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class BroadcomProxyComponent extends WizardsAbstractComponent { + @ViewChild(WizardsDynamicfieldsComponent) childComponentLocal!: WizardsDynamicfieldsComponent; + protected override WizardService: BroadcomProxyWizardService = inject(BroadcomProxyWizardService); + public checked: boolean = false; + + protected override post: BroadcomProxyWizardPost = { +// Default fields from the base wizard + host_id: 0, + services: [], +// Fields for the wizard + authPassword: '', + authProtocol: 'md5', + privacyPassword: '', + privacyProtocol: 'des', + securityLevel: '1', + securityName: '', + snmpCommunity: '', + snmpVersion: '2' + } as BroadcomProxyWizardPost; + protected snmpVersions: SelectKeyValueString[] = [ + {value: '1', key: 'SNMP V 1'}, + {value: '2', key: 'SNMP V 2c'}, + {value: '3', key: 'SNMP V 3'}, + ] + + + protected securityLevels: SelectKeyValueString[] = [ + {key: 'authPriv', value: '1'}, + {key: 'authNoPriv', value: '2'}, + {key: 'noAuthNoPriv', value: '3'}, + ]; + protected authProtocols: SelectKeyValueString[] = [ + {key: 'MD5', value: 'md5'}, + {key: 'SHA', value: 'sha'}, + ]; + protected privacyProtocols: SelectKeyValueString[] = [ + {key: 'DES', value: 'des'}, + {key: 'AES', value: 'aes'}, + {key: 'AES128', value: 'aes128'}, + {key: '3DES', value: '3des'}, + {key: '3DESDE', value: '3desde'}, + ]; + +} + diff --git a/src/app/modules/cisco_module/cisco_module.routes.ts b/src/app/modules/cisco_module/cisco_module.routes.ts new file mode 100644 index 000000000..8d42dd2a3 --- /dev/null +++ b/src/app/modules/cisco_module/cisco_module.routes.ts @@ -0,0 +1,14 @@ +import { Routes } from '@angular/router'; +import { CiscoNetworkComponent } from './pages/wizards/cisco-network/cisco-network.component'; +import { CiscoWlcComponent } from './pages/wizards/cisco-wlc/cisco-wlc.component'; + +export const ciscoModuleRoutes: Routes = [ + { + path: 'cisco_module/wizards/:hostId/cisco_network', + loadComponent: () => import('./pages/wizards/cisco-network/cisco-network.component').then(m => CiscoNetworkComponent) + }, + { + path: 'cisco_module/wizards/:hostId/cisco_wlc', + loadComponent: () => import('./pages/wizards/cisco-wlc/cisco-wlc.component').then(m => CiscoWlcComponent) + }, +]; diff --git a/src/app/modules/cisco_module/pages/wizards/cisco-network/cisco-network-wizard.interface.ts b/src/app/modules/cisco_module/pages/wizards/cisco-network/cisco-network-wizard.interface.ts new file mode 100644 index 000000000..9e9f57b88 --- /dev/null +++ b/src/app/modules/cisco_module/pages/wizards/cisco-network/cisco-network-wizard.interface.ts @@ -0,0 +1,110 @@ +import { WizardGet, WizardPost } from '../../../../../pages/wizards/wizards.interface'; + +// WIZARD GET +export interface CiscoNetworkWizardGet extends WizardGet { + interfaceServicetemplate: InterfaceServicetemplate +} + + +export interface InterfaceServicetemplate { + id: number + uuid: string + template_name: string + name: string + container_id: number + servicetemplatetype_id: number + check_period_id: number + notify_period_id: number + description: string + command_id: number + check_command_args: string + checkcommand_info: string + eventhandler_command_id: number + timeperiod_id: number + check_interval: number + retry_interval: number + max_check_attempts: number + first_notification_delay: number + notification_interval: number + notify_on_warning: number + notify_on_unknown: number + notify_on_critical: number + notify_on_recovery: number + notify_on_flapping: number + notify_on_downtime: number + flap_detection_enabled: number + flap_detection_on_ok: number + flap_detection_on_warning: number + flap_detection_on_unknown: number + flap_detection_on_critical: number + low_flap_threshold: number + high_flap_threshold: number + process_performance_data: number + freshness_checks_enabled: number + freshness_threshold: any + passive_checks_enabled: number + event_handler_enabled: number + active_checks_enabled: number + retain_status_information: number + retain_nonstatus_information: number + notifications_enabled: number + notes: string + priority: number + tags: string + service_url: any + sla_relevant: number + is_volatile: number + check_freshness: number + created: string + modified: string + check_command: { + id: number + name: string + command_line: string + command_type: number + human_args: any + uuid: string + description: string + commandarguments: { + id: number + command_id: number + name: string + human_name: string + created: string + modified: string + }[] + } + servicetemplatecommandargumentvalues: Servicecommandargumentvalue[] +} + +export interface Servicecommandargumentvalue { + commandargument: Commandargument + commandargument_id: number + created: string + id: number + modified: string + servicetemplate_id: number + value: string +} + +export interface Commandargument { + command_id: number + created: string + human_name: string + id: number + modified: string + name: string +} + + +// WIZARD POST +export interface CiscoNetworkWizardPost extends WizardPost { + authPassword: string + authProtocol: string + privacyPassword: string + privacyProtocol: string + securityLevel: string + securityName: string + snmpCommunity: string + snmpVersion: string +} diff --git a/src/app/modules/cisco_module/pages/wizards/cisco-network/cisco-network-wizard.service.ts b/src/app/modules/cisco_module/pages/wizards/cisco-network/cisco-network-wizard.service.ts new file mode 100644 index 000000000..7e7078c66 --- /dev/null +++ b/src/app/modules/cisco_module/pages/wizards/cisco-network/cisco-network-wizard.service.ts @@ -0,0 +1,39 @@ +import { Injectable } from '@angular/core'; +import { catchError, map, Observable, of } from 'rxjs'; +import { WizardsService } from '../../../../../pages/wizards/wizards.service'; +import { GenericResponseWrapper, GenericValidationError } from '../../../../../generic-responses'; +import { CiscoNetworkWizardGet, CiscoNetworkWizardPost } from './cisco-network-wizard.interface'; + +@Injectable({ + providedIn: 'root' +}) +export class CiscoNetworkWizardService extends WizardsService { + + public fetch(hostId: number): Observable { + return this.http.get(`${this.proxyPath}/cisco_module/wizards/cisco_network/${hostId}.json?angular=true`).pipe( + map((data: CiscoNetworkWizardGet): CiscoNetworkWizardGet => { + return data; + }) + ); + } + + public submit(post: CiscoNetworkWizardPost): Observable { + return this.http.post(`${this.proxyPath}/cisco_module/wizards/cisco_network.json?angular=true`, post) + .pipe( + map(data => { + return { + success: true, + data: null + }; + }), + catchError((error: any) => { + const err = error.error.error as GenericValidationError; + return of({ + success: false, + data: err + }); + }) + ); + + } +} diff --git a/src/app/components/charts/service-radialbar-chart/service-radialbar-chart.component.css b/src/app/modules/cisco_module/pages/wizards/cisco-network/cisco-network.component.css similarity index 100% rename from src/app/components/charts/service-radialbar-chart/service-radialbar-chart.component.css rename to src/app/modules/cisco_module/pages/wizards/cisco-network/cisco-network.component.css diff --git a/src/app/modules/cisco_module/pages/wizards/cisco-network/cisco-network.component.html b/src/app/modules/cisco_module/pages/wizards/cisco-network/cisco-network.component.html new file mode 100644 index 000000000..87cebd080 --- /dev/null +++ b/src/app/modules/cisco_module/pages/wizards/cisco-network/cisco-network.component.html @@ -0,0 +1,218 @@ + + + + + + +
+ {{ t('Configuration Wizard: Cisco Network') }} +
+
+ + +
+
+ + + {{ t('Host Information') }} + +
+
+ + + {{ t('Configure SNMP for Cisco Network Monitoring') }} + +
+
+
+
+
+ + + +
+
+
+
+ + + +
+ +

+ {{ t('SNMP Server Settings') }} +

+
+ +
+ + + + +
+ +
+ + + + +
+ {{ t('Communication with authentication and privacy. The protocols used for Authentication are MD5 and SHA and for Privacy, DES (Data Encryption Standard) and AES (Advanced Encryption Standard).') }} + {{ t('Communication with authentication and without privacy. The protocols used for Authentication are MD5 and SHA (Secure Hash Algorithm).') }} + {{ t('Communication without authentication and privacy.') }} +
+
+ + +
+ + + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + + +
+ +
+ + + +
+ +
+ + + +
+
+ + + + +
+
+ +
diff --git a/src/app/components/charts/host-radialbar-chart/host-radialbar-chart.component.spec.ts b/src/app/modules/cisco_module/pages/wizards/cisco-network/cisco-network.component.spec.ts similarity index 50% rename from src/app/components/charts/host-radialbar-chart/host-radialbar-chart.component.spec.ts rename to src/app/modules/cisco_module/pages/wizards/cisco-network/cisco-network.component.spec.ts index 7ce1036e3..3e4f1f55a 100644 --- a/src/app/components/charts/host-radialbar-chart/host-radialbar-chart.component.spec.ts +++ b/src/app/modules/cisco_module/pages/wizards/cisco-network/cisco-network.component.spec.ts @@ -1,18 +1,18 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { HostRadialbarChartComponent } from './host-radialbar-chart.component'; +import { CiscoNetworkComponent } from './cisco-network.component'; -describe('HostRadialbarChartComponent', () => { - let component: HostRadialbarChartComponent; - let fixture: ComponentFixture; +describe('CiscoNetworkComponent', () => { + let component: CiscoNetworkComponent; + let fixture: ComponentFixture; beforeEach(async () => { await TestBed.configureTestingModule({ - imports: [HostRadialbarChartComponent] + imports: [CiscoNetworkComponent] }) .compileComponents(); - fixture = TestBed.createComponent(HostRadialbarChartComponent); + fixture = TestBed.createComponent(CiscoNetworkComponent); component = fixture.componentInstance; fixture.detectChanges(); }); diff --git a/src/app/modules/cisco_module/pages/wizards/cisco-network/cisco-network.component.ts b/src/app/modules/cisco_module/pages/wizards/cisco-network/cisco-network.component.ts new file mode 100644 index 000000000..eaef94118 --- /dev/null +++ b/src/app/modules/cisco_module/pages/wizards/cisco-network/cisco-network.component.ts @@ -0,0 +1,98 @@ +import { ChangeDetectionStrategy, Component, inject, ViewChild } from '@angular/core'; +import { WizardsAbstractComponent } from '../../../../../pages/wizards/wizards-abstract/wizards-abstract.component'; +import { SelectKeyValueString } from '../../../../../layouts/primeng/select.interface'; +import { + WizardsDynamicfieldsComponent +} from '../../../../../components/wizards/wizards-dynamicfields/wizards-dynamicfields.component'; +import { RouterLink } from '@angular/router'; +import { FaIconComponent } from '@fortawesome/angular-fontawesome'; +import { + CardBodyComponent, + CardComponent, + CardHeaderComponent, + CardTitleDirective, + FormControlDirective, + FormLabelDirective +} from '@coreui/angular'; +import { TranslocoDirective, TranslocoPipe } from '@jsverse/transloco'; +import { RequiredIconComponent } from '../../../../../components/required-icon/required-icon.component'; +import { BackButtonDirective } from '../../../../../directives/back-button.directive'; +import { CiscoNetworkWizardPost } from './cisco-network-wizard.interface'; +import { CiscoNetworkWizardService } from './cisco-network-wizard.service'; +import { SelectComponent } from '../../../../../layouts/primeng/select/select/select.component'; +import { FormErrorDirective } from '../../../../../layouts/coreui/form-error.directive'; +import { FormFeedbackComponent } from '../../../../../layouts/coreui/form-feedback/form-feedback.component'; +import { FormsModule } from '@angular/forms'; +import { NgIf } from '@angular/common'; + +@Component({ + selector: 'oitc-cisco-network', + imports: [ + RouterLink, + FaIconComponent, + CardComponent, + CardHeaderComponent, + CardBodyComponent, + BackButtonDirective, + TranslocoPipe, + RequiredIconComponent, + FormLabelDirective, + SelectComponent, + FormErrorDirective, + FormFeedbackComponent, + FormsModule, + FormControlDirective, + WizardsDynamicfieldsComponent, + TranslocoDirective, + CardTitleDirective, + NgIf + ], + templateUrl: './cisco-network.component.html', + styleUrl: './cisco-network.component.css', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class CiscoNetworkComponent extends WizardsAbstractComponent { + @ViewChild(WizardsDynamicfieldsComponent) childComponentLocal!: WizardsDynamicfieldsComponent; + protected override WizardService: CiscoNetworkWizardService = inject(CiscoNetworkWizardService); + public checked: boolean = false; + + protected override post: CiscoNetworkWizardPost = { +// Default fields from the base wizard + host_id: 0, + services: [], +// Fields for the wizard + authPassword: '', + authProtocol: 'md5', + privacyPassword: '', + privacyProtocol: 'des', + securityLevel: '1', + securityName: '', + snmpCommunity: '', + snmpVersion: '2' + } as CiscoNetworkWizardPost; + protected snmpVersions: SelectKeyValueString[] = [ + {value: '1', key: 'SNMP V 1'}, + {value: '2', key: 'SNMP V 2c'}, + {value: '3', key: 'SNMP V 3'}, + ] + + + protected securityLevels: SelectKeyValueString[] = [ + {key: 'authPriv', value: '1'}, + {key: 'authNoPriv', value: '2'}, + {key: 'noAuthNoPriv', value: '3'}, + ]; + protected authProtocols: SelectKeyValueString[] = [ + {key: 'MD5', value: 'md5'}, + {key: 'SHA', value: 'sha'}, + ]; + protected privacyProtocols: SelectKeyValueString[] = [ + {key: 'DES', value: 'des'}, + {key: 'AES', value: 'aes'}, + {key: 'AES128', value: 'aes128'}, + {key: '3DES', value: '3des'}, + {key: '3DESDE', value: '3desde'}, + ]; + +} + diff --git a/src/app/modules/cisco_module/pages/wizards/cisco-wlc/cisco-wlc-wizard.interface.ts b/src/app/modules/cisco_module/pages/wizards/cisco-wlc/cisco-wlc-wizard.interface.ts new file mode 100644 index 000000000..c5e156fcb --- /dev/null +++ b/src/app/modules/cisco_module/pages/wizards/cisco-wlc/cisco-wlc-wizard.interface.ts @@ -0,0 +1,137 @@ +import { WizardGet, WizardPost } from '../../../../../pages/wizards/wizards.interface'; +import { GenericValidationError } from '../../../../../generic-responses'; + +// WIZARD GET +export interface CiscoWlcWizardGet extends WizardGet { + interfaceServicetemplate: InterfaceServicetemplate +} + + +export interface InterfaceServicetemplate { + id: number + uuid: string + template_name: string + name: string + container_id: number + servicetemplatetype_id: number + check_period_id: number + notify_period_id: number + description: string + command_id: number + check_command_args: string + checkcommand_info: string + eventhandler_command_id: number + timeperiod_id: number + check_interval: number + retry_interval: number + max_check_attempts: number + first_notification_delay: number + notification_interval: number + notify_on_warning: number + notify_on_unknown: number + notify_on_critical: number + notify_on_recovery: number + notify_on_flapping: number + notify_on_downtime: number + flap_detection_enabled: number + flap_detection_on_ok: number + flap_detection_on_warning: number + flap_detection_on_unknown: number + flap_detection_on_critical: number + low_flap_threshold: number + high_flap_threshold: number + process_performance_data: number + freshness_checks_enabled: number + freshness_threshold: any + passive_checks_enabled: number + event_handler_enabled: number + active_checks_enabled: number + retain_status_information: number + retain_nonstatus_information: number + notifications_enabled: number + notes: string + priority: number + tags: string + service_url: any + sla_relevant: number + is_volatile: number + check_freshness: number + created: string + modified: string + check_command: { + id: number + name: string + command_line: string + command_type: number + human_args: any + uuid: string + description: string + commandarguments: { + id: number + command_id: number + name: string + human_name: string + created: string + modified: string + }[] + } + servicetemplatecommandargumentvalues: Servicecommandargumentvalue[] +} + +export interface Servicecommandargumentvalue { + commandargument: Commandargument + commandargument_id: number + created: string + id: number + modified: string + servicetemplate_id: number + value: string +} + +export interface Commandargument { + command_id: number + created: string + human_name: string + id: number + modified: string + name: string +} + + +// WIZARD POST +export interface CiscoWlcWizardPost extends WizardPost { + authPassword: string + authProtocol: string + interfaces: N0[] + privacyPassword: string + privacyProtocol: string + securityLevel: string + securityName: string + snmpCommunity: string + snmpVersion: string +} + +export interface N0 { + createService: boolean + description: string + host_id: number + name: string + servicecommandargumentvalues: Servicecommandargumentvalue[] + servicetemplate_id: number +} + +// SNMP Discovery +export interface SnmpDiscovery { + interfaces: Interface[] + success: boolean + errors: GenericValidationError | undefined + _csrfToken: any +} + +export interface Interface { + key: number + value: { + number: string + name: string + } +} diff --git a/src/app/modules/cisco_module/pages/wizards/cisco-wlc/cisco-wlc-wizard.service.ts b/src/app/modules/cisco_module/pages/wizards/cisco-wlc/cisco-wlc-wizard.service.ts new file mode 100644 index 000000000..9f40391fe --- /dev/null +++ b/src/app/modules/cisco_module/pages/wizards/cisco-wlc/cisco-wlc-wizard.service.ts @@ -0,0 +1,38 @@ +import { Injectable } from '@angular/core'; +import { catchError, map, Observable, of } from 'rxjs'; +import { WizardsService } from '../../../../../pages/wizards/wizards.service'; +import { GenericResponseWrapper, GenericValidationError } from '../../../../../generic-responses'; +import { CiscoWlcWizardGet, CiscoWlcWizardPost } from './cisco-wlc-wizard.interface'; + +@Injectable({ + providedIn: 'root' +}) +export class CiscoWlcWizardService extends WizardsService { + public fetch(hostId: number): Observable { + return this.http.get(`${this.proxyPath}/cisco_module/wizards/cisco_wlc/${hostId}.json?angular=true`).pipe( + map((data: CiscoWlcWizardGet): CiscoWlcWizardGet => { + return data; + }) + ); + } + + public submit(post: CiscoWlcWizardPost): Observable { + return this.http.post(`${this.proxyPath}/cisco_module/wizards/cisco_wlc.json?angular=true`, post) + .pipe( + map(data => { + return { + success: true, + data: null + }; + }), + catchError((error: any) => { + const err = error.error.error as GenericValidationError; + return of({ + success: false, + data: err + }); + }) + ); + } + +} diff --git a/src/app/modules/cisco_module/pages/wizards/cisco-wlc/cisco-wlc.component.css b/src/app/modules/cisco_module/pages/wizards/cisco-wlc/cisco-wlc.component.css new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/modules/cisco_module/pages/wizards/cisco-wlc/cisco-wlc.component.html b/src/app/modules/cisco_module/pages/wizards/cisco-wlc/cisco-wlc.component.html new file mode 100644 index 000000000..e6fb10e64 --- /dev/null +++ b/src/app/modules/cisco_module/pages/wizards/cisco-wlc/cisco-wlc.component.html @@ -0,0 +1,217 @@ + + + + + + +
+ {{ t('Configuration Wizard: Cisco WLC') }} +
+
+ + +
+
+ + + {{ t('Host Information') }} + +
+
+ + + {{ t('Configure SNMP for Cisco WLC') }} + +
+
+
+
+
+ + + +
+
+
+
+ + + +
+ +

+ {{ t('SNMP Server Settings') }} +

+
+ +
+ + + + +
+ +
+ + + + +
+ {{ t('Communication with authentication and privacy. The protocols used for Authentication are MD5 and SHA and for Privacy, DES (Data Encryption Standard) and AES (Advanced Encryption Standard).') }} + {{ t('Communication with authentication and without privacy. The protocols used for Authentication are MD5 and SHA (Secure Hash Algorithm).') }} + {{ t('Communication without authentication and privacy.') }} +
+
+ + +
+ + + + +
+ +
+ + + +
+ +
+ + + +
+ +
+ + + + +
+ +
+ + + +
+ +
+ + + +
+
+ + + +
+
+ +
diff --git a/src/app/modules/cisco_module/pages/wizards/cisco-wlc/cisco-wlc.component.spec.ts b/src/app/modules/cisco_module/pages/wizards/cisco-wlc/cisco-wlc.component.spec.ts new file mode 100644 index 000000000..59c208bee --- /dev/null +++ b/src/app/modules/cisco_module/pages/wizards/cisco-wlc/cisco-wlc.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { CiscoWlcComponent } from './cisco-wlc.component'; + +describe('CiscoWlcComponent', () => { + let component: CiscoWlcComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [CiscoWlcComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(CiscoWlcComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/modules/cisco_module/pages/wizards/cisco-wlc/cisco-wlc.component.ts b/src/app/modules/cisco_module/pages/wizards/cisco-wlc/cisco-wlc.component.ts new file mode 100644 index 000000000..1d184dabb --- /dev/null +++ b/src/app/modules/cisco_module/pages/wizards/cisco-wlc/cisco-wlc.component.ts @@ -0,0 +1,102 @@ +import { ChangeDetectionStrategy, Component, inject, ViewChild } from '@angular/core'; +import { WizardsAbstractComponent } from '../../../../../pages/wizards/wizards-abstract/wizards-abstract.component'; +import { SelectKeyValueString } from '../../../../../layouts/primeng/select.interface'; +import { CiscoWlcWizardService } from './cisco-wlc-wizard.service'; +import { CiscoWlcWizardPost } from './cisco-wlc-wizard.interface'; +import { RouterLink } from '@angular/router'; +import { FaIconComponent } from '@fortawesome/angular-fontawesome'; +import { + CardBodyComponent, + CardComponent, + CardHeaderComponent, + CardTitleDirective, + FormControlDirective, + FormLabelDirective +} from '@coreui/angular'; +import { TranslocoDirective, TranslocoPipe } from '@jsverse/transloco'; +import { RequiredIconComponent } from '../../../../../components/required-icon/required-icon.component'; +import { SelectComponent } from '../../../../../layouts/primeng/select/select/select.component'; +import { FormFeedbackComponent } from '../../../../../layouts/coreui/form-feedback/form-feedback.component'; +import { FormErrorDirective } from '../../../../../layouts/coreui/form-error.directive'; +import { FormsModule } from '@angular/forms'; +import { NgIf } from '@angular/common'; +import { + WizardsDynamicfieldsComponent +} from '../../../../../components/wizards/wizards-dynamicfields/wizards-dynamicfields.component'; +import { ProgressBarModule } from 'primeng/progressbar'; +import { BackButtonDirective } from '../../../../../directives/back-button.directive'; + +@Component({ + selector: 'oitc-cisco-wlc', + imports: [ + RouterLink, + FaIconComponent, + CardComponent, + CardHeaderComponent, + CardBodyComponent, + TranslocoPipe, + RequiredIconComponent, + SelectComponent, + FormLabelDirective, + FormControlDirective, + NgIf, + WizardsDynamicfieldsComponent, + TranslocoDirective, + ProgressBarModule, + CardTitleDirective, + BackButtonDirective, + FormFeedbackComponent, + FormErrorDirective, + FormsModule + ], + templateUrl: './cisco-wlc.component.html', + styleUrl: './cisco-wlc.component.css', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class CiscoWlcComponent extends WizardsAbstractComponent { + @ViewChild(WizardsDynamicfieldsComponent) childComponentLocal!: WizardsDynamicfieldsComponent; + protected override WizardService: CiscoWlcWizardService = inject(CiscoWlcWizardService); + public checked: boolean = false; + public accordionClosed: boolean = true; + + protected override post: CiscoWlcWizardPost = { +// Default fields from the base wizard + host_id: 0, + services: [], +// Fields for the wizard + authPassword: '', + authProtocol: 'md5', + interfaces: [], + privacyPassword: '', + privacyProtocol: 'des', + securityLevel: '1', + securityName: '', + snmpCommunity: '', + snmpVersion: '2' + } as CiscoWlcWizardPost; + protected snmpVersions: SelectKeyValueString[] = [ + {value: '1', key: 'SNMP V 1'}, + {value: '2', key: 'SNMP V 2c'}, + {value: '3', key: 'SNMP V 3'}, + ] + protected searchedTags: string[] = []; + + + protected securityLevels: SelectKeyValueString[] = [ + {key: 'authPriv', value: '1'}, + {key: 'authNoPriv', value: '2'}, + {key: 'noAuthNoPriv', value: '3'}, + ]; + protected authProtocols: SelectKeyValueString[] = [ + {key: 'MD5', value: 'md5'}, + {key: 'SHA', value: 'sha'}, + ]; + protected privacyProtocols: SelectKeyValueString[] = [ + {key: 'DES', value: 'des'}, + {key: 'AES', value: 'aes'}, + {key: 'AES128', value: 'aes128'}, + {key: '3DES', value: '3des'}, + {key: '3DESDE', value: '3desde'}, + ]; + +} diff --git a/src/app/modules/import_module/import_module.routes.ts b/src/app/modules/import_module/import_module.routes.ts index 3834e3ceb..3c4a41467 100644 --- a/src/app/modules/import_module/import_module.routes.ts +++ b/src/app/modules/import_module/import_module.routes.ts @@ -33,6 +33,9 @@ export const importModuleRoutes: Routes = [{ }, { path: 'import_module/ExternalMonitorings/edit/:id', loadComponent: () => import('./pages/externalmonitorings/external-monitorings-edit/external-monitorings-edit.component').then(m => m.ExternalMonitoringsEditComponent) +}, { + path: 'import_module/ExternalMonitorings/flowchiefNodeSelection/:id', + loadComponent: () => import('./pages/externalmonitorings/flowchief-node-selection/flowchief-node-selection.component').then(m => m.FlowchiefNodeSelectionComponent) }, { path: 'import_module/ImportedFiles/index', loadComponent: () => import('./pages/importedfiles/imported-files-index/imported-files-index.component').then(m => m.ImportedFilesIndexComponent) diff --git a/src/app/modules/import_module/pages/externalmonitorings/external-monitoring-systems.enum.ts b/src/app/modules/import_module/pages/externalmonitorings/external-monitoring-systems.enum.ts new file mode 100644 index 000000000..5f6858a82 --- /dev/null +++ b/src/app/modules/import_module/pages/externalmonitorings/external-monitoring-systems.enum.ts @@ -0,0 +1,6 @@ +export enum ExternalMonitoringSystems { + Icinga2 = 'icinga2', + OpManager = 'opmanager', + PRTG = 'prtg', + FlowChief = 'flowchief' +} diff --git a/src/app/modules/import_module/pages/externalmonitorings/external-monitorings-add/external-monitorings-add.component.html b/src/app/modules/import_module/pages/externalmonitorings/external-monitorings-add/external-monitorings-add.component.html index 540d24b3d..7f7545bb8 100644 --- a/src/app/modules/import_module/pages/externalmonitorings/external-monitorings-add/external-monitorings-add.component.html +++ b/src/app/modules/import_module/pages/externalmonitorings/external-monitorings-add/external-monitorings-add.component.html @@ -94,7 +94,7 @@
{{ t('Create new external monitoring system') }}
[showClear]="false">
- @if (post.system_type === 'icinga2') { + @if (post.system_type === ExternalMonitoringSystems.Icinga2) { {{ t('Synchronize status information from Icinga 2 with openITCOCKPIT.') }} }
diff --git a/src/app/modules/import_module/pages/externalmonitorings/external-monitorings-add/external-monitorings-add.component.ts b/src/app/modules/import_module/pages/externalmonitorings/external-monitorings-add/external-monitorings-add.component.ts index a57301fdd..ffa133549 100644 --- a/src/app/modules/import_module/pages/externalmonitorings/external-monitorings-add/external-monitorings-add.component.ts +++ b/src/app/modules/import_module/pages/externalmonitorings/external-monitorings-add/external-monitorings-add.component.ts @@ -2,19 +2,18 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, OnDestro import { BackButtonDirective } from '../../../../../directives/back-button.directive'; import { TranslocoDirective, TranslocoService } from '@jsverse/transloco'; import { - CardBodyComponent, - CardComponent, - CardFooterComponent, - CardHeaderComponent, - CardTitleDirective, - ContainerComponent, - FormControlDirective, - FormDirective, - FormLabelDirective, - NavComponent, - NavItemComponent + CardBodyComponent, + CardComponent, + CardFooterComponent, + CardHeaderComponent, + CardTitleDirective, + ContainerComponent, + FormControlDirective, + FormDirective, + FormLabelDirective, + NavComponent, + NavItemComponent } from '@coreui/angular'; -import { CoreuiComponent } from '../../../../../layouts/coreui/coreui.component'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { FormErrorDirective } from '../../../../../layouts/coreui/form-error.directive'; import { FormFeedbackComponent } from '../../../../../layouts/coreui/form-feedback/form-feedback.component'; @@ -32,6 +31,7 @@ import { SelectKeyValue } from '../../../../../layouts/primeng/select.interface' import { ContainersLoadContainersByStringParams } from '../../../../../pages/containers/containers.interface'; import { ExternalMonitoringConfig, + ExternalMonitoringConfigFlowChief, ExternalMonitoringConfigIcinga2, ExternalMonitoringConfigOpmanager, ExternalMonitoringConfigPrtg, @@ -54,37 +54,38 @@ import { DynamicalFormFields } from '../../../../../components/dynamical-form-fi import { DynamicalFormFieldsComponent } from '../../../../../components/dynamical-form-fields/dynamical-form-fields.component'; +import { ExternalMonitoringSystems } from '../external-monitoring-systems.enum'; @Component({ selector: 'oitc-external-monitorings-add', imports: [ - BackButtonDirective, - CardBodyComponent, - CardComponent, - CardFooterComponent, - CardHeaderComponent, - CardTitleDirective, - FaIconComponent, - FormControlDirective, - FormDirective, - FormErrorDirective, - FormFeedbackComponent, - FormLabelDirective, - FormsModule, - NavComponent, - NavItemComponent, - PermissionDirective, - ReactiveFormsModule, - RequiredIconComponent, - TranslocoDirective, - XsButtonDirective, - RouterLink, - NgIf, - SelectComponent, - ContainerComponent, - NgForOf, - DynamicalFormFieldsComponent -], + BackButtonDirective, + CardBodyComponent, + CardComponent, + CardFooterComponent, + CardHeaderComponent, + CardTitleDirective, + FaIconComponent, + FormControlDirective, + FormDirective, + FormErrorDirective, + FormFeedbackComponent, + FormLabelDirective, + FormsModule, + NavComponent, + NavItemComponent, + PermissionDirective, + ReactiveFormsModule, + RequiredIconComponent, + TranslocoDirective, + XsButtonDirective, + RouterLink, + NgIf, + SelectComponent, + ContainerComponent, + NgForOf, + DynamicalFormFieldsComponent + ], templateUrl: './external-monitorings-add.component.html', styleUrl: './external-monitorings-add.component.css', changeDetection: ChangeDetectionStrategy.OnPush @@ -106,15 +107,19 @@ export class ExternalMonitoringsAddComponent implements OnInit, OnDestroy { protected readonly ExternalMonitoringTypes = [ { - key: 'icinga2', + key: ExternalMonitoringSystems.FlowChief, + value: this.TranslocoService.translate('FlowChief') + }, + { + key: ExternalMonitoringSystems.Icinga2, value: this.TranslocoService.translate('Icinga 2') }, { - key: 'opmanager', + key: ExternalMonitoringSystems.OpManager, value: this.TranslocoService.translate('ManageEngine OpManager') }, { - key: 'prtg', + key: ExternalMonitoringSystems.PRTG, value: this.TranslocoService.translate('Paessler PRTG System') } ]; @@ -182,18 +187,25 @@ export class ExternalMonitoringsAddComponent implements OnInit, OnDestroy { .subscribe((result: ExternalMonitoringConfig) => { this.errors = null; switch (this.post.system_type) { - case 'icinga2': + case ExternalMonitoringSystems.Icinga2: const icinga2 = result.config.config as ExternalMonitoringConfigIcinga2; this.post.json_data = icinga2; break; - case 'opmanager': + + case ExternalMonitoringSystems.OpManager: const opmanager = result.config.config as ExternalMonitoringConfigOpmanager; this.post.json_data = opmanager; break; - case 'prtg': + + case ExternalMonitoringSystems.PRTG: const prtg = result.config.config as ExternalMonitoringConfigPrtg; this.post.json_data = prtg; break; + + case ExternalMonitoringSystems.FlowChief: + const flowChief = result.config.config as ExternalMonitoringConfigFlowChief; + this.post.json_data = flowChief; + break; } this.formFields = result.config.formFields; @@ -204,4 +216,5 @@ export class ExternalMonitoringsAddComponent implements OnInit, OnDestroy { } protected readonly Object = Object; + protected readonly ExternalMonitoringSystems = ExternalMonitoringSystems; } diff --git a/src/app/modules/import_module/pages/externalmonitorings/external-monitorings-edit/external-monitorings-edit.component.html b/src/app/modules/import_module/pages/externalmonitorings/external-monitorings-edit/external-monitorings-edit.component.html index d946b9eb4..caab22ec9 100644 --- a/src/app/modules/import_module/pages/externalmonitorings/external-monitorings-edit/external-monitorings-edit.component.html +++ b/src/app/modules/import_module/pages/externalmonitorings/external-monitorings-edit/external-monitorings-edit.component.html @@ -100,7 +100,7 @@
[showClear]="false">
- @if (post.system_type === 'icinga2') { + @if (post.system_type === ExternalMonitoringSystems.Icinga2) { {{ t('Synchronize status information from Icinga 2 with openITCOCKPIT.') }} }
@@ -123,6 +123,24 @@

[(ngModel)]="post.json_data[formField['ngModel']]"> + + @if (post.system_type === ExternalMonitoringSystems.FlowChief) { + + + + +
+ {{ t('FlowChief Nodes from the Plantexplorer can be assigend') }} + + {{ t('here') }}. + +
+
+
+
+ } + } diff --git a/src/app/modules/import_module/pages/externalmonitorings/external-monitorings-edit/external-monitorings-edit.component.ts b/src/app/modules/import_module/pages/externalmonitorings/external-monitorings-edit/external-monitorings-edit.component.ts index 168947546..e774d2e3a 100644 --- a/src/app/modules/import_module/pages/externalmonitorings/external-monitorings-edit/external-monitorings-edit.component.ts +++ b/src/app/modules/import_module/pages/externalmonitorings/external-monitorings-edit/external-monitorings-edit.component.ts @@ -1,19 +1,21 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, OnDestroy, OnInit } from '@angular/core'; import { BackButtonDirective } from '../../../../../directives/back-button.directive'; import { + AlertComponent, CardBodyComponent, CardComponent, CardFooterComponent, CardHeaderComponent, CardTitleDirective, + ColComponent, ContainerComponent, FormControlDirective, FormDirective, FormLabelDirective, NavComponent, - NavItemComponent + NavItemComponent, + RowComponent } from '@coreui/angular'; -import { CoreuiComponent } from '../../../../../layouts/coreui/coreui.component'; import { DynamicalFormFieldsComponent } from '../../../../../components/dynamical-form-fields/dynamical-form-fields.component'; @@ -42,6 +44,7 @@ import { ExternalMonitoringConfig, ExternalMonitoringPost } from '../external-mo import { PermissionsService } from '../../../../../permissions/permissions.service'; import { SystemnameService } from '../../../../../services/systemname.service'; import { FormLoaderComponent } from '../../../../../layouts/primeng/loading/form-loader/form-loader.component'; +import { ExternalMonitoringSystems } from '../external-monitoring-systems.enum'; @Component({ @@ -73,7 +76,10 @@ import { FormLoaderComponent } from '../../../../../layouts/primeng/loading/form TranslocoDirective, XsButtonDirective, RouterLink, - FormLoaderComponent + FormLoaderComponent, + RowComponent, + ColComponent, + AlertComponent ], templateUrl: './external-monitorings-edit.component.html', styleUrl: './external-monitorings-edit.component.css', @@ -97,15 +103,19 @@ export class ExternalMonitoringsEditComponent implements OnInit, OnDestroy { protected readonly ExternalMonitoringTypes = [ { - key: 'icinga2', + key: ExternalMonitoringSystems.FlowChief, + value: this.TranslocoService.translate('FlowChief') + }, + { + key: ExternalMonitoringSystems.Icinga2, value: this.TranslocoService.translate('Icinga 2') }, { - key: 'opmanager', + key: ExternalMonitoringSystems.OpManager, value: this.TranslocoService.translate('ManageEngine OpManager') }, { - key: 'prtg', + key: ExternalMonitoringSystems.PRTG, value: this.TranslocoService.translate('Paessler PRTG System') } ]; @@ -146,7 +156,7 @@ export class ExternalMonitoringsEditComponent implements OnInit, OnDestroy { this.subscriptions.add(this.ExternalMonitoringsService.getEdit(this.id) .subscribe((result) => { //Fire on page load - this.post = result.externalMonitoring; + this.post = result; this.cdr.markForCheck(); this.loadContainers(); this.loadConfigFieldsBySystemType(); @@ -181,4 +191,6 @@ export class ExternalMonitoringsEditComponent implements OnInit, OnDestroy { public ngOnDestroy(): void { this.subscriptions.unsubscribe(); } + + protected readonly ExternalMonitoringSystems = ExternalMonitoringSystems; } diff --git a/src/app/modules/import_module/pages/externalmonitorings/external-monitorings-index/external-monitorings-index.component.html b/src/app/modules/import_module/pages/externalmonitorings/external-monitorings-index/external-monitorings-index.component.html index 15feca3b9..b1628e394 100644 --- a/src/app/modules/import_module/pages/externalmonitorings/external-monitorings-index/external-monitorings-index.component.html +++ b/src/app/modules/import_module/pages/externalmonitorings/external-monitorings-index/external-monitorings-index.component.html @@ -174,6 +174,14 @@

{{ t('Edit') }} + @if (externalMonitoring.system_type === ExternalMonitoringSystems.FlowChief && externalMonitoring.allowEdit) { + + + {{ t('Select FlowChef Nodes') }} + + } diff --git a/src/app/modules/import_module/pages/externalmonitorings/external-monitorings-index/external-monitorings-index.component.ts b/src/app/modules/import_module/pages/externalmonitorings/external-monitorings-index/external-monitorings-index.component.ts index ba1fc50e4..06a0558ab 100644 --- a/src/app/modules/import_module/pages/externalmonitorings/external-monitorings-index/external-monitorings-index.component.ts +++ b/src/app/modules/import_module/pages/externalmonitorings/external-monitorings-index/external-monitorings-index.component.ts @@ -17,7 +17,6 @@ import { RowComponent, TableDirective } from '@coreui/angular'; -import { CoreuiComponent } from '../../../../../layouts/coreui/coreui.component'; import { DebounceDirective } from '../../../../../directives/debounce.directive'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { FormsModule, ReactiveFormsModule } from '@angular/forms'; @@ -52,6 +51,7 @@ import { import { SelectAllComponent } from '../../../../../layouts/coreui/select-all/select-all.component'; import { DELETE_SERVICE_TOKEN } from '../../../../../tokens/delete-injection.token'; import { IndexPage } from '../../../../../pages.interface'; +import { ExternalMonitoringSystems } from '../external-monitoring-systems.enum'; @Component({ selector: 'oitc-external-monitorings-index', @@ -94,7 +94,7 @@ import { IndexPage } from '../../../../../pages.interface'; TableDirective ], providers: [ - { provide: DELETE_SERVICE_TOKEN, useClass: ExternalMonitoringsService } // Inject the ExternalMonitoringsService into the DeleteAllModalComponent + {provide: DELETE_SERVICE_TOKEN, useClass: ExternalMonitoringsService} // Inject the ExternalMonitoringsService into the DeleteAllModalComponent ], templateUrl: './external-monitorings-index.component.html', styleUrl: './external-monitorings-index.component.css', @@ -204,4 +204,6 @@ export class ExternalMonitoringsIndexComponent implements OnInit, OnDestroy, Ind this.load(); } } + + protected readonly ExternalMonitoringSystems = ExternalMonitoringSystems; } diff --git a/src/app/modules/import_module/pages/externalmonitorings/external-monitorings.interface.ts b/src/app/modules/import_module/pages/externalmonitorings/external-monitorings.interface.ts index 210770f55..1bc19c7f6 100644 --- a/src/app/modules/import_module/pages/externalmonitorings/external-monitorings.interface.ts +++ b/src/app/modules/import_module/pages/externalmonitorings/external-monitorings.interface.ts @@ -1,6 +1,7 @@ import { PaginateOrScroll } from '../../../../layouts/coreui/paginator/paginator.interface'; import { DynamicalFormFields } from '../../../../components/dynamical-form-fields/dynamical-form-fields.interface'; import { SelectKeyValue } from '../../../../layouts/primeng/select.interface'; +import { ExternalMonitoringSystems } from './external-monitoring-systems.enum'; export interface ExternalMonitoringsIndexRoot extends PaginateOrScroll { externalMonitorings: ExternalMonitoring[] @@ -12,7 +13,7 @@ export interface ExternalMonitoring { name: string container_id: number description: string - system_type: string + system_type: ExternalMonitoringSystems container: string allowEdit: boolean } @@ -22,14 +23,10 @@ export interface ExternalMonitoringPost { container_id: number | null name: string description: string - system_type: string + system_type: ExternalMonitoringSystems | '' json_data: any } -export interface ExternalMonitoringGet { - externalMonitoring: ExternalMonitoringPost -} - export interface ExternalMonitoringsIndexParams { angular: true, scroll: boolean, @@ -58,11 +55,12 @@ export function getDefaultExternalMonitoringsIndexParams(): ExternalMonitoringsI export interface ExternalMonitoringConfig { config: { - config: ExternalMonitoringConfigIcinga2 | ExternalMonitoringConfigOpmanager | ExternalMonitoringConfigPrtg + config: ExternalMonitoringConfigIcinga2 | ExternalMonitoringConfigOpmanager | ExternalMonitoringConfigPrtg | ExternalMonitoringConfigFlowChief formFields: DynamicalFormFields } } +// Server code: IcingaExternalMonitoringJson.php export interface ExternalMonitoringConfigIcinga2 { api_url: string api_user: string @@ -74,6 +72,7 @@ export interface ExternalMonitoringConfigIcinga2 { receive_acknowledgements: number } +// Server code: OpManagerExternalMonitoringJson.php export interface ExternalMonitoringConfigOpmanager { api_url: string api_key: string @@ -82,6 +81,7 @@ export interface ExternalMonitoringConfigOpmanager { polling_interval: number } +// Server code: PrtgExternalMonitoringJson.php export interface ExternalMonitoringConfigPrtg { api_url: string api_token: string @@ -93,6 +93,38 @@ export interface ExternalMonitoringConfigPrtg { include_channels: number } -export interface ExternalMonitoringsAsList{ +// Server code: FlowChiefExternalMonitoringJson.php +export interface ExternalMonitoringConfigFlowChief { + api_url: string + api_user: string + api_password: string + use_proxy: number + ignore_ssl_certificate: number + host_prefix: string +} + +export interface ExternalMonitoringsAsList { externalMonitorings: SelectKeyValue[] } + + +export interface ExternalMonitoringWithFlowchiefNodesMembershipPost extends ExternalMonitoringPost { + flowchief_nodes_membership: FlowchiefNodesMembership[] +} + +export interface FlowchiefNodesMembership { + id?: number // auto increment of linking table + external_monitoring_id: number + flowchief_node_id: number + is_recursive: boolean + path?: string // only used by the Angular Frontend +} + +/********************** + * Global action * + **********************/ +export interface FlowchiefNodesByStringParams { + externalMonitoringId: number, + 'filter[FlowchiefNodes.path]': string, + 'selected[]': number[], +} diff --git a/src/app/modules/import_module/pages/externalmonitorings/external-monitorings.service.ts b/src/app/modules/import_module/pages/externalmonitorings/external-monitorings.service.ts index 9b2915f97..2bd505eee 100644 --- a/src/app/modules/import_module/pages/externalmonitorings/external-monitorings.service.ts +++ b/src/app/modules/import_module/pages/externalmonitorings/external-monitorings.service.ts @@ -1,16 +1,18 @@ import { inject, Injectable } from '@angular/core'; import { ExternalMonitoringConfig, - ExternalMonitoringGet, ExternalMonitoringPost, ExternalMonitoringsIndexParams, - ExternalMonitoringsIndexRoot + ExternalMonitoringsIndexRoot, + ExternalMonitoringWithFlowchiefNodesMembershipPost, + FlowchiefNodesByStringParams } from './external-monitorings.interface'; import { catchError, map, Observable, of } from 'rxjs'; import { HttpClient } from '@angular/common/http'; import { PROXY_PATH } from '../../../../tokens/proxy-path.token'; import { DeleteAllItem } from '../../../../layouts/coreui/delete-all-modal/delete-all.interface'; import { GenericIdResponse, GenericResponseWrapper, GenericValidationError } from '../../../../generic-responses'; +import { SelectKeyValue } from '../../../../layouts/primeng/select.interface'; @Injectable({ providedIn: 'root' @@ -38,15 +40,17 @@ export class ExternalMonitoringsService { return this.http.post(`${proxyPath}/import_module/external_monitorings/loadConfigFieldsBySystemType/${system_type}.json?angular=true`, {}); } - public getEdit(id: number): Observable { + public getEdit(id: number): Observable { const proxyPath = this.proxyPath; - return this.http.get(`${proxyPath}/import_module/external_monitorings/edit/${id}.json`, { + return this.http.get<{ + externalMonitoring: ExternalMonitoringPost + }>(`${proxyPath}/import_module/external_monitorings/edit/${id}.json`, { params: { angular: true } }).pipe( map(data => { - return data; + return data.externalMonitoring; }) ); } @@ -107,4 +111,52 @@ export class ExternalMonitoringsService { const proxyPath = this.proxyPath; return this.http.post(`${proxyPath}/import_module/external_monitorings/delete/${item.id}.json?angular=true`, {}); } + + /*************************************** + * flowchiefNodeSelection action * + ***************************************/ + public getFlowchiefNodeSelection(externalMonitoringId: number): Observable { + const proxyPath = this.proxyPath; + return this.http.get<{ + externalMonitoring: ExternalMonitoringWithFlowchiefNodesMembershipPost + }>(`${proxyPath}/import_module/external_monitorings/flowchiefNodeSelection/${externalMonitoringId}.json`).pipe( + map(data => { + return data.externalMonitoring; + }) + ); + } + + public editFlowchiefNodeSelection(externalMonitoring: ExternalMonitoringWithFlowchiefNodesMembershipPost): Observable { + const proxyPath = this.proxyPath; + return this.http.post(`${proxyPath}/import_module/external_monitorings/flowchiefNodeSelection/${externalMonitoring.id}.json`, externalMonitoring) + .pipe( + map(data => { + // Return true on 200 Ok + return { + success: true, + data: data.externalMonitoring as GenericIdResponse + }; + }), + catchError((error: any) => { + const err = error.error.error as GenericValidationError; + return of({ + success: false, + data: err + }); + }) + ); + } + + public loadFlowchiefNodesByString(params: FlowchiefNodesByStringParams): Observable { + const proxyPath = this.proxyPath; + return this.http.get<{ + nodes: SelectKeyValue[] + }>(`${proxyPath}/import_module/external_monitorings/loadFlowchiefNodesByString.json`, { + params: params as {} + }).pipe( + map(data => { + return data.nodes; + }) + ) + } } diff --git a/src/app/modules/import_module/pages/externalmonitorings/flowchief-node-selection/flowchief-node-selection.component.css b/src/app/modules/import_module/pages/externalmonitorings/flowchief-node-selection/flowchief-node-selection.component.css new file mode 100644 index 000000000..e69de29bb diff --git a/src/app/modules/import_module/pages/externalmonitorings/flowchief-node-selection/flowchief-node-selection.component.html b/src/app/modules/import_module/pages/externalmonitorings/flowchief-node-selection/flowchief-node-selection.component.html new file mode 100644 index 000000000..fd575623a --- /dev/null +++ b/src/app/modules/import_module/pages/externalmonitorings/flowchief-node-selection/flowchief-node-selection.component.html @@ -0,0 +1,140 @@ + + + + + + @if (post) { +
+ + +
{{ t('Select FlowChief Nodes') }}
+ + + + + +
+ +
+ + + +
+ {{ t('openITCOCKPIT will import all Process Variables from the selected nodes.') }} +
+ {{ t('In case the list is empty, execute the command') }} + + oitc import_module.flowchief_import_nodes + --external-monitoring-system-id {{ post.id }} + + {{ t('on the openITCOCKPIT server to get the list of available nodes.') }} +
+ +
+ +
+ @if (post.flowchief_nodes_membership.length > 0) { + +

+
+ + +
+ {{ t('Attention!') }} +

+

+ {{ t('Please note that enabling the "Include sub-tree PVs" option may result in a large number of unnecessary imports, which can cause performance issues.') }} +

+
+ } + + @for (nodeMembership of post.flowchief_nodes_membership; track $index) { +
+ @if (nodeMembership.is_recursive) { + + } @else { + + } + {{ nodeMembership.path }} + + + + + + +
+ } + +
+ +
+ + + + + +
+
+ } +
diff --git a/src/app/modules/import_module/pages/externalmonitorings/flowchief-node-selection/flowchief-node-selection.component.spec.ts b/src/app/modules/import_module/pages/externalmonitorings/flowchief-node-selection/flowchief-node-selection.component.spec.ts new file mode 100644 index 000000000..38db1197f --- /dev/null +++ b/src/app/modules/import_module/pages/externalmonitorings/flowchief-node-selection/flowchief-node-selection.component.spec.ts @@ -0,0 +1,23 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { FlowchiefNodeSelectionComponent } from './flowchief-node-selection.component'; + +describe('FlowchiefNodeSelectionComponent', () => { + let component: FlowchiefNodeSelectionComponent; + let fixture: ComponentFixture; + + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [FlowchiefNodeSelectionComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(FlowchiefNodeSelectionComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/modules/import_module/pages/externalmonitorings/flowchief-node-selection/flowchief-node-selection.component.ts b/src/app/modules/import_module/pages/externalmonitorings/flowchief-node-selection/flowchief-node-selection.component.ts new file mode 100644 index 000000000..234d30359 --- /dev/null +++ b/src/app/modules/import_module/pages/externalmonitorings/flowchief-node-selection/flowchief-node-selection.component.ts @@ -0,0 +1,202 @@ +import { ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, OnDestroy, OnInit } from '@angular/core'; +import { NotyService } from '../../../../../layouts/coreui/noty.service'; +import { ActivatedRoute, Router, RouterLink } from '@angular/router'; +import { Subscription } from 'rxjs'; +import { ExternalMonitoringsService } from '../external-monitorings.service'; +import { HistoryService } from '../../../../../history.service'; +import { FormFeedbackComponent } from '../../../../../layouts/coreui/form-feedback/form-feedback.component'; +import { + AlertComponent, + AlertHeadingDirective, + CardBodyComponent, + CardComponent, + CardFooterComponent, + CardHeaderComponent, + CardTitleDirective, + FormCheckComponent, + FormCheckInputDirective, + FormCheckLabelDirective, + FormDirective, + FormLabelDirective, + NavComponent, + NavItemComponent, + TextColorDirective +} from '@coreui/angular'; +import { MultiSelectComponent } from '../../../../../layouts/primeng/multi-select/multi-select/multi-select.component'; +import { TranslocoDirective, TranslocoService } from '@jsverse/transloco'; +import { BackButtonDirective } from '../../../../../directives/back-button.directive'; +import { FaIconComponent } from '@fortawesome/angular-fontawesome'; +import { FormsModule, ReactiveFormsModule } from '@angular/forms'; +import { PermissionDirective } from '../../../../../permissions/permission.directive'; +import { XsButtonDirective } from '../../../../../layouts/coreui/xsbutton-directive/xsbutton.directive'; +import { GenericIdResponse, GenericValidationError } from '../../../../../generic-responses'; +import { SelectKeyValue } from '../../../../../layouts/primeng/select.interface'; +import { FormLoaderComponent } from '../../../../../layouts/primeng/loading/form-loader/form-loader.component'; +import { + ExternalMonitoringWithFlowchiefNodesMembershipPost, + FlowchiefNodesByStringParams, + FlowchiefNodesMembership +} from '../external-monitorings.interface'; +import { NgClass } from '@angular/common'; +import { CopyToClipboardComponent } from '../../../../../layouts/coreui/copy-to-clipboard/copy-to-clipboard.component'; + +@Component({ + selector: 'oitc-flowchief-node-selection', + imports: [ + FormFeedbackComponent, + FormLabelDirective, + MultiSelectComponent, + TranslocoDirective, + BackButtonDirective, + CardBodyComponent, + CardComponent, + CardFooterComponent, + CardHeaderComponent, + CardTitleDirective, + FaIconComponent, + FormDirective, + FormsModule, + NavComponent, + NavItemComponent, + PermissionDirective, + ReactiveFormsModule, + XsButtonDirective, + RouterLink, + FormLoaderComponent, + FormCheckComponent, + FormCheckInputDirective, + FormCheckLabelDirective, + NgClass, + TextColorDirective, + CopyToClipboardComponent, + AlertComponent, + AlertHeadingDirective + ], + templateUrl: './flowchief-node-selection.component.html', + styleUrl: './flowchief-node-selection.component.css', + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class FlowchiefNodeSelectionComponent implements OnInit, OnDestroy { + + public post?: ExternalMonitoringWithFlowchiefNodesMembershipPost; + public selectedNodes: number[] = []; + public flowchiefNodes: SelectKeyValue[] = []; + public errors: GenericValidationError | null = null; + + + private subscriptions: Subscription = new Subscription(); + + private readonly ExternalMonitoringsService = inject(ExternalMonitoringsService); + private readonly TranslocoService = inject(TranslocoService); + private readonly notyService = inject(NotyService); + private readonly HistoryService: HistoryService = inject(HistoryService); + private readonly router: Router = inject(Router); + private readonly route: ActivatedRoute = inject(ActivatedRoute); + private cdr = inject(ChangeDetectorRef); + + public ngOnInit() { + this.route.queryParams.subscribe(params => { + const id = Number(this.route.snapshot.paramMap.get('id')); + this.loadExternalMonitoringFlowchiefConfig(id); + }); + } + + public ngOnDestroy(): void { + this.subscriptions.unsubscribe(); + } + + public loadExternalMonitoringFlowchiefConfig(id: number) { + this.subscriptions.add(this.ExternalMonitoringsService.getFlowchiefNodeSelection(id).subscribe(response => { + this.post = response; + this.selectedNodes = response.flowchief_nodes_membership.map(item => item.flowchief_node_id); + + this.loadFlowchiefNodes(''); + this.cdr.markForCheck(); + })); + } + + public loadFlowchiefNodes = (searchString: string): void => { + const params: FlowchiefNodesByStringParams = { + externalMonitoringId: this.post?.id || 0, + 'filter[FlowchiefNodes.path]': searchString, + 'selected[]': this.selectedNodes + } + + this.subscriptions.add(this.ExternalMonitoringsService.loadFlowchiefNodesByString(params) + .subscribe((result) => { + this.flowchiefNodes = result; + this.cdr.markForCheck(); + }) + ); + }; + + public updateNodesMembership() { + if (!this.post) { + return; + } + + // Cleanup if a node got deselected + const flowchief_nodes_membership: FlowchiefNodesMembership[] = []; + const alreadyInMembership: number[] = []; + + this.post.flowchief_nodes_membership.forEach((flowchief_node) => { + if (this.selectedNodes.includes(flowchief_node.flowchief_node_id)) { + flowchief_nodes_membership.push(flowchief_node); + } + alreadyInMembership.push(flowchief_node.flowchief_node_id); + }); + + // All selected nodes to membership (so we can sret is_recursive properly) + this.selectedNodes.forEach((nodeId) => { + let externalMonitoringId = 0; + if (this.post && this.post.id) { + // This is just to make TypeScript happy + externalMonitoringId = this.post.id; + } + + if (!alreadyInMembership.includes(nodeId)) { + + const node = this.flowchiefNodes.find(n => n.key === nodeId); + if (node) { + flowchief_nodes_membership.push({ + flowchief_node_id: nodeId, + external_monitoring_id: externalMonitoringId, + is_recursive: false, + path: node.value // only used by the Angular Frontend + }); + } + } + }); + + this.post.flowchief_nodes_membership = flowchief_nodes_membership; + } + + public submit() { + if (!this.post) { + return; + } + + this.subscriptions.add(this.ExternalMonitoringsService.editFlowchiefNodeSelection(this.post) + .subscribe((result) => { + this.cdr.markForCheck(); + if (result.success) { + const response = result.data as GenericIdResponse; + const title = this.TranslocoService.translate('FlowChief Nodes'); + const msg = this.TranslocoService.translate('updated successfully'); + const url = ['import_module', 'ExternalMonitorings', 'flowchiefNodeSelection', response.id]; + + this.notyService.genericSuccess(msg, title, url); + this.HistoryService.navigateWithFallback(['/import_module/ExternalMonitorings/index']); + this.notyService.scrollContentDivToTop(); + return; + } + + // Error + const errorResponse = result.data as GenericValidationError; + this.notyService.genericError(); + if (result) { + this.errors = errorResponse; + } + })); + } +} diff --git a/src/app/modules/import_module/pages/importedhosts/imported-hosts-edit/imported-hosts-edit.component.html b/src/app/modules/import_module/pages/importedhosts/imported-hosts-edit/imported-hosts-edit.component.html index b2b792118..fd4db054c 100644 --- a/src/app/modules/import_module/pages/importedhosts/imported-hosts-edit/imported-hosts-edit.component.html +++ b/src/app/modules/import_module/pages/importedhosts/imported-hosts-edit/imported-hosts-edit.component.html @@ -140,6 +140,29 @@
+
+ + + + + + +
+ {{ t('Press return to separate tags') }} +
+
+
+ + +
+ + + + +
+ {{ t('Determines the time at which a status had to be transmitted (hh:mm).') }} +
+ @if (serverTimezone) { +
+ {{ t('Server timezone is:') }} + + {{ serverTimezone.server_timezone }}. + + {{ t('Current server time:') }} + + {{ serverTimezone.server_time }} + +
+ } +
+ +
+ + + + +
+ {{ t('Sends a reminder email to all users if no status had been transmitted before the deadline exceeded (in minutes).') }} +
+
+ + +
+ + + + + + + @if (this.PermissionsService.hasPermissionObservable(['timeperiods', 'viewdetails'])|async) { + @if (post.timeperiod_id) { + + + } + + } @else { + + } + + +
+ +
diff --git a/src/app/modules/scm_module/pages/resourcegroups/resourcegroups-add/resourcegroups-add.component.ts b/src/app/modules/scm_module/pages/resourcegroups/resourcegroups-add/resourcegroups-add.component.ts index b2195ed9b..ed9f45e90 100644 --- a/src/app/modules/scm_module/pages/resourcegroups/resourcegroups-add/resourcegroups-add.component.ts +++ b/src/app/modules/scm_module/pages/resourcegroups/resourcegroups-add/resourcegroups-add.component.ts @@ -27,7 +27,7 @@ import { AsyncPipe } from '@angular/common'; import { PermissionDirective } from '../../../../../permissions/permission.directive'; import { RequiredIconComponent } from '../../../../../components/required-icon/required-icon.component'; import { SelectComponent } from '../../../../../layouts/primeng/select/select/select.component'; -import { TranslocoDirective, TranslocoService } from '@jsverse/transloco'; +import { TranslocoDirective, TranslocoPipe, TranslocoService } from '@jsverse/transloco'; import { XsButtonDirective } from '../../../../../layouts/coreui/xsbutton-directive/xsbutton.directive'; import { Router, RouterLink } from '@angular/router'; import { Subscription } from 'rxjs'; @@ -45,6 +45,13 @@ import { UsersService } from '../../../../../pages/users/users.service'; import { MultiSelectComponent } from '../../../../../layouts/primeng/multi-select/multi-select/multi-select.component'; import { ResourcegroupsSubmitType } from '../resourcegroups.enum'; import { MailinglistsService } from '../../mailinglists/mailinglists.service'; +import { TimeperiodsService } from '../../../../../pages/timeperiods/timeperiods.service'; +import { TimezoneConfiguration, TimezoneService } from '../../../../../services/timezone.service'; +import { ScmSettingsIndex } from '../../scmsettings/scmsettings.interface'; +import { ScmsettingsService } from '../../scmsettings/scmsettings.service'; +import { + TimeperiodDetailsTooltipComponent +} from '../../../../sla_module/components/timeperiod-details-tooltip/timeperiod-details-tooltip.component'; @Component({ selector: 'oitc-resourcegroups-add', @@ -79,7 +86,9 @@ import { MailinglistsService } from '../../mailinglists/mailinglists.service'; DropdownMenuDirective, DropdownToggleDirective, InputGroupComponent, - InputGroupTextDirective + InputGroupTextDirective, + TimeperiodDetailsTooltipComponent, + TranslocoPipe ], templateUrl: './resourcegroups-add.component.html', styleUrl: './resourcegroups-add.component.css', @@ -91,6 +100,9 @@ export class ResourcegroupsAddComponent implements OnInit, OnDestroy { private readonly ContainersService = inject(ContainersService); private readonly UsersService = inject(UsersService); private readonly MailinglistsService = inject(MailinglistsService); + private readonly TimeperiodsService: TimeperiodsService = inject(TimeperiodsService); + private readonly ScmsettingsService: ScmsettingsService = inject(ScmsettingsService); + private readonly TimezoneService = inject(TimezoneService); private readonly notyService = inject(NotyService); private readonly HistoryService: HistoryService = inject(HistoryService); public post = this.getClearForm(); @@ -98,6 +110,9 @@ export class ResourcegroupsAddComponent implements OnInit, OnDestroy { public PermissionsService: PermissionsService = inject(PermissionsService); private ResourcegroupsService = inject(ResourcegroupsService); public containers: SelectKeyValue[] = []; + public timeperiods: SelectKeyValue[] = []; + public serverTimezone: TimezoneConfiguration | null = null; + public users: SelectKeyValue[] = []; public managers: SelectKeyValue[] = []; public region_managers: SelectKeyValue[] = []; @@ -117,6 +132,21 @@ export class ResourcegroupsAddComponent implements OnInit, OnDestroy { public ngOnInit(): void { this.loadContainers(); + this.load(); + } + + private load(): void { + this.subscriptions.add(this.ScmsettingsService.loadScmSettings() + .subscribe((result: ScmSettingsIndex) => { + this.post.reminder_time = result.scm_settings.reminder_time; + this.post.deadline = result.scm_settings.deadline; + this.cdr.markForCheck(); + })); + + this.subscriptions.add(this.TimezoneService.getTimezoneConfiguration().subscribe(data => { + this.serverTimezone = data; + this.cdr.markForCheck(); + })); } public loadContainers = (): void => { @@ -151,10 +181,21 @@ export class ResourcegroupsAddComponent implements OnInit, OnDestroy { })); } + private loadTimeperiods() { + if (this.post.container.parent_id === null) { + return; + } + this.subscriptions.add(this.TimeperiodsService.loadTimeperiodsByContainerId(this.post.container.parent_id).subscribe((result) => { + this.timeperiods = result; + this.cdr.markForCheck(); + })); + } + public onContainerChange() { this.loadUsers(); this.loadMailinglists(); + this.loadTimeperiods(); this.cdr.markForCheck(); } @@ -165,6 +206,9 @@ export class ResourcegroupsAddComponent implements OnInit, OnDestroy { public getClearForm(): ResourcegroupsPost { return { description: '', + timeperiod_id: null, + reminder_time: 15, + deadline: '', container: { parent_id: null, name: '' diff --git a/src/app/modules/scm_module/pages/resourcegroups/resourcegroups-edit/resourcegroups-edit.component.html b/src/app/modules/scm_module/pages/resourcegroups/resourcegroups-edit/resourcegroups-edit.component.html index 256f2e64c..35de61e09 100644 --- a/src/app/modules/scm_module/pages/resourcegroups/resourcegroups-edit/resourcegroups-edit.component.html +++ b/src/app/modules/scm_module/pages/resourcegroups/resourcegroups-edit/resourcegroups-edit.component.html @@ -87,6 +87,101 @@
{{ t('Edit resource group:') }} [(ngModel)]="post.description" oitcFormError [errors]="errors" errorField="description"> + +
+ + + + +
+ {{ t('Determines the time at which a status had to be transmitted (hh:mm).') }} +
+ @if (serverTimezone) { +
+ {{ t('Server timezone is:') }} + + {{ serverTimezone.server_timezone }}. + + {{ t('Current server time:') }} + + {{ serverTimezone.server_time }} + +
+ } +
+ +
+ + + + +
+ {{ t('Sends a reminder email to all users if no status had been transmitted before the deadline exceeded (in minutes).') }} +
+
+ + +
+ + + + + + + @if (this.PermissionsService.hasPermissionObservable(['timeperiods', 'viewdetails'])|async) { + @if (post.timeperiod_id) { + + + } + + } @else { + + } + + +
+
diff --git a/src/app/modules/scm_module/pages/resourcegroups/resourcegroups-edit/resourcegroups-edit.component.ts b/src/app/modules/scm_module/pages/resourcegroups/resourcegroups-edit/resourcegroups-edit.component.ts index 84db09d28..64f2f0ca1 100644 --- a/src/app/modules/scm_module/pages/resourcegroups/resourcegroups-edit/resourcegroups-edit.component.ts +++ b/src/app/modules/scm_module/pages/resourcegroups/resourcegroups-edit/resourcegroups-edit.component.ts @@ -27,7 +27,7 @@ import { AsyncPipe, NgIf } from '@angular/common'; import { PermissionDirective } from '../../../../../permissions/permission.directive'; import { RequiredIconComponent } from '../../../../../components/required-icon/required-icon.component'; import { SelectComponent } from '../../../../../layouts/primeng/select/select/select.component'; -import { TranslocoDirective, TranslocoService } from '@jsverse/transloco'; +import { TranslocoDirective, TranslocoPipe, TranslocoService } from '@jsverse/transloco'; import { XsButtonDirective } from '../../../../../layouts/coreui/xsbutton-directive/xsbutton.directive'; import { ActivatedRoute, Router, RouterLink } from '@angular/router'; import { Subscription } from 'rxjs'; @@ -46,6 +46,11 @@ import { MultiSelectComponent } from '../../../../../layouts/primeng/multi-selec import { FormLoaderComponent } from '../../../../../layouts/primeng/loading/form-loader/form-loader.component'; import { ResourcegroupsSubmitType } from '../resourcegroups.enum'; import { MailinglistsService } from '../../mailinglists/mailinglists.service'; +import { TimeperiodsService } from '../../../../../pages/timeperiods/timeperiods.service'; +import { TimezoneConfiguration, TimezoneService } from '../../../../../services/timezone.service'; +import { + TimeperiodDetailsTooltipComponent +} from '../../../../sla_module/components/timeperiod-details-tooltip/timeperiod-details-tooltip.component'; @Component({ selector: 'oitc-resourcegroups-edit', @@ -82,7 +87,9 @@ import { MailinglistsService } from '../../mailinglists/mailinglists.service'; DropdownMenuDirective, DropdownToggleDirective, InputGroupComponent, - InputGroupTextDirective + InputGroupTextDirective, + TimeperiodDetailsTooltipComponent, + TranslocoPipe ], templateUrl: './resourcegroups-edit.component.html', styleUrl: './resourcegroups-edit.component.css', @@ -95,6 +102,8 @@ export class ResourcegroupsEditComponent implements OnInit, OnDestroy { private readonly ContainersService = inject(ContainersService); private readonly UsersService = inject(UsersService); private readonly MailinglistsService = inject(MailinglistsService); + private readonly TimeperiodsService: TimeperiodsService = inject(TimeperiodsService); + private readonly TimezoneService = inject(TimezoneService); private readonly notyService = inject(NotyService); private readonly HistoryService: HistoryService = inject(HistoryService); public post!: ResourcegroupsPost; @@ -102,6 +111,8 @@ export class ResourcegroupsEditComponent implements OnInit, OnDestroy { public PermissionsService: PermissionsService = inject(PermissionsService); private ResourcegroupsService = inject(ResourcegroupsService); public containers: SelectKeyValue[] = []; + public timeperiods: SelectKeyValue[] = []; + public serverTimezone: TimezoneConfiguration | null = null; public users: SelectKeyValue[] = []; public managers: SelectKeyValue[] = []; @@ -122,6 +133,14 @@ export class ResourcegroupsEditComponent implements OnInit, OnDestroy { ngOnInit(): void { this.id = Number(this.route.snapshot.paramMap.get('id')); this.loadResourcegroup(); + this.load(); + } + + private load(): void { + this.subscriptions.add(this.TimezoneService.getTimezoneConfiguration().subscribe(data => { + this.serverTimezone = data; + this.cdr.markForCheck(); + })); } public loadResourcegroup() { @@ -133,6 +152,7 @@ export class ResourcegroupsEditComponent implements OnInit, OnDestroy { this.loadContainers(); this.loadUsers(); this.loadMailinglists(); + this.loadTimeperiods(); })); } @@ -168,10 +188,21 @@ export class ResourcegroupsEditComponent implements OnInit, OnDestroy { })); } + private loadTimeperiods() { + if (this.post.container.parent_id === null) { + return; + } + this.subscriptions.add(this.TimeperiodsService.loadTimeperiodsByContainerId(this.post.container.parent_id).subscribe((result) => { + this.timeperiods = result; + this.cdr.markForCheck(); + })); + } + public onContainerChange() { this.loadUsers(); this.loadMailinglists(); + this.loadTimeperiods(); this.cdr.markForCheck(); } @@ -182,6 +213,9 @@ export class ResourcegroupsEditComponent implements OnInit, OnDestroy { public getClearForm(): ResourcegroupsPost { return { description: '', + timeperiod_id: null, + reminder_time: 15, + deadline: '', container: { parent_id: null, name: '' diff --git a/src/app/modules/scm_module/pages/resourcegroups/resourcegroups-index/resourcegroups-index.component.css b/src/app/modules/scm_module/pages/resourcegroups/resourcegroups-index/resourcegroups-index.component.css index e69de29bb..7f0bf1564 100644 --- a/src/app/modules/scm_module/pages/resourcegroups/resourcegroups-index/resourcegroups-index.component.css +++ b/src/app/modules/scm_module/pages/resourcegroups/resourcegroups-index/resourcegroups-index.component.css @@ -0,0 +1,3 @@ +.weekdays-ul-padding-inline-start{ + padding-inline-start: 1rem; +} diff --git a/src/app/modules/scm_module/pages/resourcegroups/resourcegroups-index/resourcegroups-index.component.html b/src/app/modules/scm_module/pages/resourcegroups/resourcegroups-index/resourcegroups-index.component.html index aecf45a7a..e5b03c32e 100644 --- a/src/app/modules/scm_module/pages/resourcegroups/resourcegroups-index/resourcegroups-index.component.html +++ b/src/app/modules/scm_module/pages/resourcegroups/resourcegroups-index/resourcegroups-index.component.html @@ -139,11 +139,7 @@
{{ t('Description') }} - - {{ t('Created') }} - - {{ t('Last update') }} - + @@ -151,9 +147,17 @@
{{ t('Resource summary') }} + + + {{ t('Users') }} + + {{ t('Created') }} + + {{ t('Last update') }} + @@ -184,28 +188,6 @@
{{ resourcegroup.description }} - - {{ resourcegroup.created }} - - - @if (resourcegroup.last_update) { - {{ resourcegroup.last_update }} - } - @if (!resourcegroup.last_update) { - - {{ t('n/a') }} - - } - @if (resourcegroup.last_update_failed) { - - - - } - - - @if (resourcegroup.resource_count > 0 && ( PermissionsService.hasPermissionObservable(['scmmodule', 'resources', 'index'])|async )) { } + + +
+ + {{ t('Reminder time') }} + + {{ t('minute(s)') }} +
+
+ + {{ t('Deadline') }} + +
+ @if (resourcegroup.timeperiod_id && ( this.PermissionsService.hasPermissionObservable(['timeperiods', 'viewdetails'])|async )) { +
+ + {{ resourcegroup.timeperiod?.name }} + +
    + @for ( + timerange of resourcegroup.timeperiod?.timeperiod_timeranges; track $index) { +
  • +
    +
    + + {{ weekdays[timerange.day] }}: +
    +
    + {{ timerange.start }} - + {{ timerange.end }} +
    +
    +
  • + } + @if (resourcegroup.timeperiod?.timeperiod_timeranges?.length === 0) { +
  • + +
  • + } + @if (resourcegroup.timeperiod?.calendar) { +
  • +
    +
    + + + {{ resourcegroup.timeperiod?.calendar?.name }} + +
    +
    +
  • + } +
+ } @if (resourcegroup.region_managers.length > 0 || resourcegroup.mailinglist_region_managers.length > 0) { @@ -359,6 +403,26 @@
} + + {{ resourcegroup.created }} + + + @if (resourcegroup.last_update) { + {{ resourcegroup.last_update }} + } + @if (!resourcegroup.last_update) { + + {{ t('n/a') }} + + } + @if (resourcegroup.last_update_failed) { + + + + } + { // Here, params is an object containing the current query parameters. // You can do something with these parameters here. let resourcegroupId = params['id'] || params['id']; + if (resourcegroupId) { this.params['filter[Resourcegroups.id][]'] = [].concat(resourcegroupId); // make sure we always get an array } diff --git a/src/app/modules/scm_module/pages/resourcegroups/resourcegroups.interface.ts b/src/app/modules/scm_module/pages/resourcegroups/resourcegroups.interface.ts index 774d63b08..e9e14b74f 100644 --- a/src/app/modules/scm_module/pages/resourcegroups/resourcegroups.interface.ts +++ b/src/app/modules/scm_module/pages/resourcegroups/resourcegroups.interface.ts @@ -8,6 +8,7 @@ import { import { Resource } from '../resources/resources.interface'; import { GenericIdAndName } from '../../../../generic.interfaces'; import { getUserDate } from '../../../../services/timezone.service'; +import { Timeperiod } from '../../../../pages/timeperiods/timeperiods.interface'; export interface ResourcegroupsIndex extends PaginateOrScroll { all_resourcegroups: Resourcegroup[] @@ -18,6 +19,10 @@ export interface Resourcegroup { id: number container_id: number description: string + deadline: string + timeperiod_id: number | null + timeperiod?: Timeperiod + reminder_time: number last_state: number last_update: string last_update_failed: boolean @@ -73,6 +78,9 @@ export interface ResourcegroupsGet { export interface ResourcegroupsPost { id?: number description: string + timeperiod_id: number | null + reminder_time: number + deadline: string container: { parent_id: number | null name: string diff --git a/src/app/modules/scm_module/pages/scmsettings/scm-settings-index/scm-settings-index.component.html b/src/app/modules/scm_module/pages/scmsettings/scm-settings-index/scm-settings-index.component.html index 8276a8fdc..2ddbc5dd1 100644 --- a/src/app/modules/scm_module/pages/scmsettings/scm-settings-index/scm-settings-index.component.html +++ b/src/app/modules/scm_module/pages/scmsettings/scm-settings-index/scm-settings-index.component.html @@ -57,7 +57,7 @@
- {{ timeperiod.name }} + {{ timeperiod?.name }} @@ -26,32 +26,37 @@

- - - - - - + @if (weekDays) { + + @for (day of weekDays | keyvalue; track $index) { + + } + + }
- @if (day.value.length > 0) { - - {{ label.messageExists }} - - } @else { - - {{ label.messageNotExists }} - - } -
- @if (day.value.length > 0) { -
    -
  • - {{ timerange.start + ' - ' + timerange.end }} -
  • -
- } @else { - - } -
+ @if (day.value.length > 0) { + + {{ label.messageExists }} + + } @else { + + {{ label.messageNotExists }} + + } +
+ @if (day.value.length > 0) { +
    + @for (timerange of day.value; track $index) { +
  • + {{ timerange.start + ' - ' + timerange.end }} +
  • + } +
+ } @else { + + } +
+
diff --git a/src/app/modules/sla_module/components/timeperiod-details-tooltip/timeperiod-details-tooltip.component.ts b/src/app/modules/sla_module/components/timeperiod-details-tooltip/timeperiod-details-tooltip.component.ts index 19db62e21..f3fdb0169 100644 --- a/src/app/modules/sla_module/components/timeperiod-details-tooltip/timeperiod-details-tooltip.component.ts +++ b/src/app/modules/sla_module/components/timeperiod-details-tooltip/timeperiod-details-tooltip.component.ts @@ -6,7 +6,6 @@ import { Input, OnChanges, OnDestroy, - OnInit, SimpleChanges } from '@angular/core'; import { Subscription } from 'rxjs'; @@ -16,25 +15,24 @@ import { Timeperiod, TimeperiodRoot, WeekDays } from './timeperiod-details-toolt import { TranslocoDirective, TranslocoService } from '@jsverse/transloco'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { BadgeComponent, PopoverDirective, TableDirective } from '@coreui/angular'; -import { KeyValuePipe, NgForOf, NgIf } from '@angular/common'; +import { KeyValuePipe } from '@angular/common'; +import _ from 'lodash'; @Component({ selector: 'oitc-timeperiod-details-tooltip', imports: [ - FaIconComponent, - TranslocoDirective, - PopoverDirective, - TableDirective, - BadgeComponent, - NgIf, - NgForOf, - KeyValuePipe -], + FaIconComponent, + TranslocoDirective, + PopoverDirective, + TableDirective, + BadgeComponent, + KeyValuePipe + ], templateUrl: './timeperiod-details-tooltip.component.html', styleUrl: './timeperiod-details-tooltip.component.css', changeDetection: ChangeDetectionStrategy.OnPush }) -export class TimeperiodDetailsTooltipComponent implements OnInit, OnDestroy, OnChanges { +export class TimeperiodDetailsTooltipComponent implements OnDestroy, OnChanges { @Input() timeperiodId: number = 0; @@ -43,7 +41,7 @@ export class TimeperiodDetailsTooltipComponent implements OnInit, OnDestroy, OnC private readonly TranslocoService = inject(TranslocoService); private readonly TimeperiodDetailsTooltipService = inject(TimeperiodDetailsTooltipService); public isLoading: boolean = true; - public timeperiod: Timeperiod = {} as Timeperiod; + public timeperiod?: Timeperiod; public weekDays: WeekDays = {} as WeekDays; public label = { messageExists: this.TranslocoService.translate('Yes'), @@ -56,16 +54,6 @@ export class TimeperiodDetailsTooltipComponent implements OnInit, OnDestroy, OnC this.loadTimeperiodDetails(); } - - public ngOnInit() { - this.subscriptions.add(this.route.queryParams.subscribe(params => { - // Here, params is an object containing the current query parameters. - // You can do something with these parameters here. - //console.log(params); - this.loadTimeperiodDetails(); - })); - } - public ngOnDestroy() { this.subscriptions.unsubscribe(); } @@ -97,6 +85,11 @@ export class TimeperiodDetailsTooltipComponent implements OnInit, OnDestroy, OnC }); } + for (let day in this.weekDays) { + this.weekDays[day] = _.sortBy(this.weekDays[day], [function (o) { + return o.start; + }]); + } })); } this.cdr.markForCheck(); diff --git a/src/app/pages/agentconnector/agentconfig.interface.ts b/src/app/pages/agentconnector/agentconfig.interface.ts index 14c405622..2e1c96fb4 100644 --- a/src/app/pages/agentconnector/agentconfig.interface.ts +++ b/src/app/pages/agentconnector/agentconfig.interface.ts @@ -21,6 +21,7 @@ export interface AgentConfigStringFields { autossl_crt_file: string autossl_key_file: string autossl_ca_file: string + tls_security_level: 'lax' | 'intermediate' | 'modern' config_version: string } @@ -33,6 +34,7 @@ export interface AgentConfigBoolFields { push_enable_webserver: boolean push_webserver_use_https: boolean use_autossl: boolean + verify_autossl_expiry: boolean use_https: boolean use_https_verify: boolean cpustats: boolean diff --git a/src/app/pages/agentconnector/agentconnector-config/agentconnector-config.component.html b/src/app/pages/agentconnector/agentconnector-config/agentconnector-config.component.html index 5840e876e..484973b80 100644 --- a/src/app/pages/agentconnector/agentconnector-config/agentconnector-config.component.html +++ b/src/app/pages/agentconnector/agentconnector-config/agentconnector-config.component.html @@ -393,6 +393,25 @@

*ngIf="connectionType === AgentconnectorConnectionTypes.AutoTls"> {{ t('openITCOCKPIT automatically generate a TLS certificate for authentication and encryption purpose. Auto-TLS is only available in Pull mode.') }}

+ + @if (connectionType === AgentconnectorConnectionTypes.AutoTls) { +
+ + + +
+ {{ t('The generated client certificate is valid for 10 years. If enabled, openITCOCKPIT will not be able to query the Agent after the certificate has been expired.') }} +
+
+
+ } +
{{ t('Start the Agents web server with own TLS certificates from Let\'s Encrypt for example.') }} @@ -448,6 +467,29 @@

+ + @if (connectionType === AgentconnectorConnectionTypes.AutoTls || connectionType === AgentconnectorConnectionTypes.Https || config.bool.enable_push_mode) { +
+ + +
+ } diff --git a/src/app/pages/browsers/browsers-index/browsers-index.component.html b/src/app/pages/browsers/browsers-index/browsers-index.component.html index f9fd13865..6dda48d47 100644 --- a/src/app/pages/browsers/browsers-index/browsers-index.component.html +++ b/src/app/pages/browsers/browsers-index/browsers-index.component.html @@ -159,14 +159,12 @@
@if (statusCounts && statusCounts.hoststatusCount.length === 3) {
- - +
+ + +
@if (PermissionsService.hasPermissionObservable(['hosts', 'index'])|async) { @@ -235,15 +233,11 @@
} @if (statusCounts && statusCounts.servicestatusCount.length === 4) {
- - - - +
+ + +
@if (PermissionsService.hasPermissionObservable(['services', 'index'])|async) { diff --git a/src/app/pages/browsers/browsers-index/browsers-index.component.ts b/src/app/pages/browsers/browsers-index/browsers-index.component.ts index e79ea3175..0ccd6c726 100644 --- a/src/app/pages/browsers/browsers-index/browsers-index.component.ts +++ b/src/app/pages/browsers/browsers-index/browsers-index.component.ts @@ -200,7 +200,8 @@ import { TitleService } from '../../../services/title.service'; ServicePieChartComponent, ColumnsConfigExportModalComponent, ColumnsConfigImportModalComponent, - AsyncPipe + AsyncPipe, + ServicePieChartComponent ], templateUrl: './browsers-index.component.html', styleUrl: './browsers-index.component.css', diff --git a/src/app/pages/dashboards/dashboards-index/dashboards-index.component.html b/src/app/pages/dashboards/dashboards-index/dashboards-index.component.html index 45422bdb6..d4dc75519 100644 --- a/src/app/pages/dashboards/dashboards-index/dashboards-index.component.html +++ b/src/app/pages/dashboards/dashboards-index/dashboards-index.component.html @@ -67,6 +67,7 @@
{{ t('Dashboard') }}
@@ -170,8 +171,8 @@
+ [widgetInput]="widget" + [isReadonly]="isReadonly"> diff --git a/src/app/pages/dashboards/widgets/hosts-piechart-widget/hosts-piechart-widget.component.css b/src/app/pages/dashboards/widgets/hosts-piechart-widget/hosts-piechart-widget.component.css index e69de29bb..66254c08a 100644 --- a/src/app/pages/dashboards/widgets/hosts-piechart-widget/hosts-piechart-widget.component.css +++ b/src/app/pages/dashboards/widgets/hosts-piechart-widget/hosts-piechart-widget.component.css @@ -0,0 +1,3 @@ +.h-85 { + height: 85% !important; +} diff --git a/src/app/pages/dashboards/widgets/hosts-piechart-widget/hosts-piechart-widget.component.html b/src/app/pages/dashboards/widgets/hosts-piechart-widget/hosts-piechart-widget.component.html index 19504d8ec..071d52e1f 100644 --- a/src/app/pages/dashboards/widgets/hosts-piechart-widget/hosts-piechart-widget.component.html +++ b/src/app/pages/dashboards/widgets/hosts-piechart-widget/hosts-piechart-widget.component.html @@ -1,63 +1,62 @@ - +
+ @if (!statusCounts) { + + } + @if (statusCounts && statusCounts.hoststatusCount.length === 3) { +
-
+
- +
+
+ @if (PermissionsService.hasPermissionObservable(['hosts', 'index'])|async) { + + + {{ statusCounts.hoststatusCount[0] }} + ({{ statusCounts.hoststatusCountPercentage[0] }} %) + + } @else { + + {{ statusCounts.hoststatusCount[0] }} ({{ statusCounts.hoststatusCountPercentage[0] }} %) + } +
-
-
- @if (PermissionsService.hasPermissionObservable(['hosts', 'index'])|async) { - - - {{ statusCounts.hoststatusCount[2] }} - ({{ statusCounts.hoststatusCountPercentage[2] }} %) - - } @else { - - {{ statusCounts.hoststatusCount[2] }} ({{ statusCounts.hoststatusCountPercentage[2] }} %) - }
-
+ }
diff --git a/src/app/pages/dashboards/widgets/hosts-piechart-widget/hosts-piechart-widget.component.ts b/src/app/pages/dashboards/widgets/hosts-piechart-widget/hosts-piechart-widget.component.ts index 07b5afa35..9dae0f472 100644 --- a/src/app/pages/dashboards/widgets/hosts-piechart-widget/hosts-piechart-widget.component.ts +++ b/src/app/pages/dashboards/widgets/hosts-piechart-widget/hosts-piechart-widget.component.ts @@ -1,80 +1,82 @@ -import { ChangeDetectionStrategy, Component, inject, ViewChild } from '@angular/core'; -import { BaseWidgetComponent } from '../base-widget/base-widget.component'; -import { KtdGridLayout, KtdResizeEnd } from '@katoid/angular-grid-layout'; -import { AsyncPipe, NgIf } from '@angular/common'; -import { FaIconComponent } from '@fortawesome/angular-fontawesome'; -import { TranslocoDirective } from '@jsverse/transloco'; +import { ChangeDetectionStrategy, Component, effect, inject, input, OnDestroy } from '@angular/core'; +import { NgxEchartsDirective, provideEchartsCore } from 'ngx-echarts'; +import * as echarts from 'echarts/core'; +import 'echarts/theme/dark.js'; +import { EChartsOption } from 'echarts'; + +import { LegendComponent, PolarComponent, TitleComponent, TooltipComponent } from 'echarts/components'; +import { PieChart } from 'echarts/charts'; +import { BlockLoaderComponent } from '../../../../layouts/primeng/loading/block-loader/block-loader.component'; +import { AsyncPipe } from '@angular/common'; import { RouterLink } from '@angular/router'; import { StatuscountResponse } from '../../../browsers/browsers.interface'; -import { ApexGrid, ChartComponent } from "ng-apexcharts"; -import { ChartOptions } from '../../../../components/charts/host-pie-chart/host-pie-chart.component'; +import { FaIconComponent } from '@fortawesome/angular-fontawesome'; +import { BaseWidgetComponent } from '../base-widget/base-widget.component'; +import { PieChartMetric } from '../../../../components/charts/charts.interface'; import { LayoutService } from '../../../../layouts/coreui/layout.service'; -import { BlockLoaderComponent } from '../../../../layouts/primeng/loading/block-loader/block-loader.component'; +import { TranslocoDirective } from '@jsverse/transloco'; +import { KtdGridLayout } from '@katoid/angular-grid-layout'; + +echarts.use([PieChart, LegendComponent, TitleComponent, TooltipComponent, PolarComponent]); @Component({ selector: 'oitc-hosts-piechart-widget', imports: [ + NgxEchartsDirective, + BlockLoaderComponent, AsyncPipe, - FaIconComponent, - NgIf, - TranslocoDirective, RouterLink, - ChartComponent, - BlockLoaderComponent + FaIconComponent, + TranslocoDirective + ], + providers: [ + provideEchartsCore({echarts}), ], templateUrl: './hosts-piechart-widget.component.html', styleUrl: './hosts-piechart-widget.component.css', changeDetection: ChangeDetectionStrategy.OnPush }) -export class HostsPiechartWidgetComponent extends BaseWidgetComponent { +export class HostsPiechartWidgetComponent extends BaseWidgetComponent implements OnDestroy { + public widgetHeight: number = 0; - public statusCounts?: StatuscountResponse; + public title = input('Host availability'); + public showLegend = input(true); + public chartData = input([]); + public scaleSize = input(20); + public scale = input(true); + + + public theme: string = ''; + public chartOption: EChartsOption = {}; - @ViewChild("chart") chart?: ChartComponent; - public chartOptions!: Partial; + public echartsInstance: any; private readonly LayoutService = inject(LayoutService); - public apexGridOptions: ApexGrid = { - padding: { - top: 10, - right: 0, - bottom: 0, - left: 0 - }, - }; + public statusCounts?: StatuscountResponse; public constructor() { super(); - - // Subscribe to the color mode changes (drop down menu in header) this.subscriptions.add(this.LayoutService.theme$.subscribe((theme) => { - //console.log('Change in theme detected', theme); - if (this.chart && this.chartOptions) { - - // Read the background color value from the CSS variable and update the chart - let cuiSecondaryBg = getComputedStyle(document.documentElement).getPropertyValue('--cui-secondary-bg').trim(); - - //this.chartOptions.plotOptions?.radialBar?.track?.background = cuiSecondaryBg; - this.chart.updateOptions({ - plotOptions: { - radialBar: { - track: { - background: cuiSecondaryBg - } - } - } - }); + this.theme = ''; + if (theme === 'dark') { + this.theme = 'dark'; } + this.cdr.markForCheck(); })); + + effect(() => { + this.renderChart(); + this.cdr.markForCheck(); + }); } public override load() { this.subscriptions.add(this.WidgetsService.loadStatusCount() .subscribe((result) => { this.statusCounts = result; - this.updateChart(); + this.renderChart(); this.cdr.markForCheck(); }) ); @@ -84,89 +86,129 @@ export class HostsPiechartWidgetComponent extends BaseWidgetComponent { super.ngOnDestroy(); } - private updateChart() { + onChartInit(ec: any) { + this.echartsInstance = ec; + this.cdr.markForCheck(); + } + + private renderChart() { if (!this.statusCounts) { return; } - - let cuiSecondaryBg = getComputedStyle(document.documentElement).getPropertyValue('--cui-secondary-bg').trim(); - this.chartOptions = { - series: [ - this.statusCounts.hoststatusCountPercentage[0], - this.statusCounts.hoststatusCountPercentage[1], - this.statusCounts.hoststatusCountPercentage[2], - ], - chart: { - height: 180, - offsetY: -20, - type: "radialBar", - - // The sparkline option remove all paddings - // https://github.com/apexcharts/apexcharts.js/issues/1272#issuecomment-591388290 - sparkline: { - enabled: true - } + let labels = [ + this.TranslocoService.translate('Unreachable'), + this.TranslocoService.translate('Down'), + this.TranslocoService.translate('Up') + ]; + + this.chartOption = { + polar: { + radius: [30, '80%'], }, - plotOptions: { - radialBar: { - offsetY: 0, - startAngle: -180, - endAngle: 180, - hollow: { - margin: 5, - size: "30%", - background: "transparent", - image: undefined, - }, - track: { - show: true, - background: cuiSecondaryBg, - opacity: 0.5, - dropShadow: { - enabled: false, - top: 2, - left: 0, - color: '#999999', - opacity: 0.2, - blur: 2 - } - }, - dataLabels: { - name: { - show: false - }, - value: { - show: true - } - } - }, + backgroundColor: 'transparent', + angleAxis: { + show: false, + max: 100, + startAngle: 270 }, - colors: ["#00bc4c", "#bf0000", "#6b737c"], - labels: [ - this.TranslocoService.translate('Up'), - this.TranslocoService.translate('Down'), - this.TranslocoService.translate('Unreachable') - ], - legend: { - show: false + radiusAxis: { + type: 'category', + show: false, + data: labels }, - responsive: [ + tooltip: { + show: true, + formatter: '{b} ({c}%)' + }, + series: [ { - breakpoint: 480, - options: { - legend: { - show: false + type: 'bar', + barWidth: '50%', + data: [ + { + value: this.statusCounts.hoststatusCountPercentage[2], + itemStyle: { + color: { + type: 'linear', + x: 0.5, + y: 0.5, + x2: 1.5, + y2: 1.5, + colorStops: [ + { + offset: 0.1, + color: '#6b737c' // color at 0% + }, + { + offset: 1, + color: '#4f555a' // color at 100% + } + ], + global: false // default is false + } + } + }, + { + value: this.statusCounts.hoststatusCountPercentage[1], + itemStyle: { + color: { + type: 'linear', + x: 0.5, + y: 0.5, + x2: 1.5, + y2: 1.5, + colorStops: [ + { + offset: 0.1, + color: '#CC0000' // color at 0% + }, + { + offset: 1, + color: '#c0022e' // color at 100% + } + ], + global: false // default is false + } + } + }, + { + value: this.statusCounts.hoststatusCountPercentage[0], + itemStyle: { + color: { + type: 'linear', + x: 0.5, + y: 0.5, + x2: 1.5, + y2: 1.5, + colorStops: [ + { + offset: 0.1, + color: '#00bc4c' // color at 0% + }, + { + offset: 1, + color: '#039a3f' // color at 100% + } + ], + global: false // default is false + }, + borderWidth: 1 + } } - } + ], + + colorBy: 'series', + showBackground: true, + backgroundStyle: { + color: 'rgba(180, 180, 180, 0.1)', + borderRadius: [5, 5, 0, 0] + }, + + coordinateSystem: 'polar' } ] }; - } - - public override resizeWidget(event?: KtdResizeEnd) { - if (this.chart) { - this.chart.updateOptions(this.chartOptions); - } + this.cdr.markForCheck(); } public override layoutUpdate(event: KtdGridLayout) { diff --git a/src/app/pages/dashboards/widgets/services-piechart-widget/services-piechart-widget.component.css b/src/app/pages/dashboards/widgets/services-piechart-widget/services-piechart-widget.component.css index e69de29bb..66254c08a 100644 --- a/src/app/pages/dashboards/widgets/services-piechart-widget/services-piechart-widget.component.css +++ b/src/app/pages/dashboards/widgets/services-piechart-widget/services-piechart-widget.component.css @@ -0,0 +1,3 @@ +.h-85 { + height: 85% !important; +} diff --git a/src/app/pages/dashboards/widgets/services-piechart-widget/services-piechart-widget.component.html b/src/app/pages/dashboards/widgets/services-piechart-widget/services-piechart-widget.component.html index 3430bf716..a0a86a442 100644 --- a/src/app/pages/dashboards/widgets/services-piechart-widget/services-piechart-widget.component.html +++ b/src/app/pages/dashboards/widgets/services-piechart-widget/services-piechart-widget.component.html @@ -1,76 +1,74 @@ - +
-
+ @if (!statusCounts) { + + } + @if (statusCounts && statusCounts.servicestatusCount.length === 4) { +
+
- +
+
+ @if (PermissionsService.hasPermissionObservable(['services', 'index'])|async) { + + + {{ statusCounts.servicestatusCount[0] }} + ({{ statusCounts.servicestatusCountPercentage[0] }} %) + + } @else { + + {{ statusCounts.servicestatusCount[0] }} ({{ statusCounts.servicestatusCountPercentage[0] }} %) + } +
-
-
- @if (PermissionsService.hasPermissionObservable(['services', 'index'])|async) { - - - {{ statusCounts.servicestatusCount[0] }} - ({{ statusCounts.servicestatusCountPercentage[0] }} %) - - } @else { - - {{ statusCounts.servicestatusCount[0] }} ({{ statusCounts.servicestatusCountPercentage[0] }} %) - } -
- -
- @if (PermissionsService.hasPermissionObservable(['services', 'index'])|async) { - - - {{ statusCounts.servicestatusCount[1] }} - ({{ statusCounts.servicestatusCountPercentage[1] }} %) - - } @else { - - {{ statusCounts.servicestatusCount[1] }} ({{ statusCounts.servicestatusCountPercentage[1] }} %) - } -
+
+ @if (PermissionsService.hasPermissionObservable(['services', 'index'])|async) { + + + {{ statusCounts.servicestatusCount[1] }} + ({{ statusCounts.servicestatusCountPercentage[1] }} %) + + } @else { + + {{ statusCounts.servicestatusCount[1] }} ({{ statusCounts.servicestatusCountPercentage[1] }} %) + } +
-
- @if (PermissionsService.hasPermissionObservable(['services', 'index'])|async) { - - - {{ statusCounts.servicestatusCount[2] }} - ({{ statusCounts.servicestatusCountPercentage[2] }} %) - - } @else { - - {{ statusCounts.servicestatusCount[2] }} ({{ statusCounts.servicestatusCountPercentage[2] }} %) - } -
+
+ @if (PermissionsService.hasPermissionObservable(['services', 'index'])|async) { + + + {{ statusCounts.servicestatusCount[2] }} + ({{ statusCounts.servicestatusCountPercentage[2] }} %) + + } @else { + + {{ statusCounts.servicestatusCount[2] }} ({{ statusCounts.servicestatusCountPercentage[2] }} %) + } +
-
- @if (PermissionsService.hasPermissionObservable(['services', 'index'])|async) { - - - {{ statusCounts.servicestatusCount[3] }} - ({{ statusCounts.servicestatusCountPercentage[3] }} %) - - } @else { - - {{ statusCounts.servicestatusCount[3] }} ({{ statusCounts.servicestatusCountPercentage[3] }} %) - } +
+ @if (PermissionsService.hasPermissionObservable(['services', 'index'])|async) { + + + {{ statusCounts.servicestatusCount[3] }} + ({{ statusCounts.servicestatusCountPercentage[3] }} %) + + } @else { + + {{ statusCounts.servicestatusCount[3] }} ({{ statusCounts.servicestatusCountPercentage[3] }} %) + } +
+
-
+ }
diff --git a/src/app/pages/dashboards/widgets/services-piechart-widget/services-piechart-widget.component.ts b/src/app/pages/dashboards/widgets/services-piechart-widget/services-piechart-widget.component.ts index f48fa8581..2a631cb06 100644 --- a/src/app/pages/dashboards/widgets/services-piechart-widget/services-piechart-widget.component.ts +++ b/src/app/pages/dashboards/widgets/services-piechart-widget/services-piechart-widget.component.ts @@ -1,79 +1,82 @@ -import { ChangeDetectionStrategy, Component, inject, ViewChild } from '@angular/core'; -import { BaseWidgetComponent } from '../base-widget/base-widget.component'; -import { KtdGridLayout, KtdResizeEnd } from '@katoid/angular-grid-layout'; -import { AsyncPipe, NgIf } from '@angular/common'; -import { FaIconComponent } from '@fortawesome/angular-fontawesome'; -import { TranslocoDirective } from '@jsverse/transloco'; -import { StatuscountResponse } from '../../../browsers/browsers.interface'; +import { ChangeDetectionStrategy, Component, effect, inject, input, OnDestroy } from '@angular/core'; +import { NgxEchartsDirective, provideEchartsCore } from 'ngx-echarts'; +import * as echarts from 'echarts/core'; +import 'echarts/theme/dark.js'; +import { EChartsOption } from 'echarts'; + +import { LegendComponent, PolarComponent, TitleComponent, TooltipComponent } from 'echarts/components'; +import { PieChart } from 'echarts/charts'; +import { BlockLoaderComponent } from '../../../../layouts/primeng/loading/block-loader/block-loader.component'; +import { AsyncPipe } from '@angular/common'; import { RouterLink } from '@angular/router'; -import { ApexGrid, ChartComponent } from 'ng-apexcharts'; -import { ChartOptions } from '../../../../components/charts/host-pie-chart/host-pie-chart.component'; +import { StatuscountResponse } from '../../../browsers/browsers.interface'; +import { FaIconComponent } from '@fortawesome/angular-fontawesome'; +import { BaseWidgetComponent } from '../base-widget/base-widget.component'; +import { PieChartMetric } from '../../../../components/charts/charts.interface'; import { LayoutService } from '../../../../layouts/coreui/layout.service'; -import { BlockLoaderComponent } from '../../../../layouts/primeng/loading/block-loader/block-loader.component'; +import { TranslocoDirective } from '@jsverse/transloco'; +import { KtdGridLayout } from '@katoid/angular-grid-layout'; + +echarts.use([PieChart, LegendComponent, TitleComponent, TooltipComponent, PolarComponent]); @Component({ selector: 'oitc-services-piechart-widget', imports: [ + NgxEchartsDirective, + BlockLoaderComponent, AsyncPipe, - FaIconComponent, - NgIf, - TranslocoDirective, RouterLink, - ChartComponent, - BlockLoaderComponent + FaIconComponent, + TranslocoDirective + ], + providers: [ + provideEchartsCore({echarts}), ], templateUrl: './services-piechart-widget.component.html', styleUrl: './services-piechart-widget.component.css', changeDetection: ChangeDetectionStrategy.OnPush }) -export class ServicesPiechartWidgetComponent extends BaseWidgetComponent { - public statusCounts?: StatuscountResponse; +export class ServicesPiechartWidgetComponent extends BaseWidgetComponent implements OnDestroy { + public widgetHeight: number = 0; + + public title = input('Services availability'); + public showLegend = input(true); + public chartData = input([]); + public scaleSize = input(20); + public scale = input(true); + + + public theme: string = ''; + public chartOption: EChartsOption = {}; - @ViewChild("chart") chart?: ChartComponent; - public chartOptions!: Partial; + public echartsInstance: any; private readonly LayoutService = inject(LayoutService); - public apexGridOptions: ApexGrid = { - padding: { - top: 10, - right: 0, - bottom: 0, - left: 0 - }, - }; + public statusCounts?: StatuscountResponse; public constructor() { super(); - - // Subscribe to the color mode changes (drop down menu in header) this.subscriptions.add(this.LayoutService.theme$.subscribe((theme) => { - //console.log('Change in theme detected', theme); - if (this.chart && this.chartOptions) { - - // Read the background color value from the CSS variable and update the chart - let cuiSecondaryBg = getComputedStyle(document.documentElement).getPropertyValue('--cui-secondary-bg').trim(); - - //this.chartOptions.plotOptions?.radialBar?.track?.background = cuiSecondaryBg; - this.chart.updateOptions({ - plotOptions: { - radialBar: { - track: { - background: cuiSecondaryBg - } - } - } - }); + this.theme = ''; + if (theme === 'dark') { + this.theme = 'dark'; } + this.cdr.markForCheck(); })); + + effect(() => { + this.renderChart(); + this.cdr.markForCheck(); + }); } public override load() { this.subscriptions.add(this.WidgetsService.loadStatusCount() .subscribe((result) => { this.statusCounts = result; - this.updateChart(); + this.renderChart(); this.cdr.markForCheck(); }) ); @@ -83,91 +86,153 @@ export class ServicesPiechartWidgetComponent extends BaseWidgetComponent { super.ngOnDestroy(); } - private updateChart() { + onChartInit(ec: any) { + this.echartsInstance = ec; + this.cdr.markForCheck(); + } + + private renderChart() { if (!this.statusCounts) { return; } + let labels = [ + this.TranslocoService.translate('Unknown'), + this.TranslocoService.translate('Critical'), + this.TranslocoService.translate('Warning'), + this.TranslocoService.translate('Ok'), + ]; - let cuiSecondaryBg = getComputedStyle(document.documentElement).getPropertyValue('--cui-secondary-bg').trim(); - this.chartOptions = { - series: [ - this.statusCounts.servicestatusCountPercentage[0], - this.statusCounts.servicestatusCountPercentage[1], - this.statusCounts.servicestatusCountPercentage[2], - this.statusCounts.servicestatusCountPercentage[3], - ], - chart: { - height: 180, - offsetY: -20, - type: "radialBar", - - // The sparkline option remove all paddings - // https://github.com/apexcharts/apexcharts.js/issues/1272#issuecomment-591388290 - sparkline: { - enabled: true - }, + this.chartOption = { + polar: { + radius: [20, '80%'], }, - plotOptions: { - radialBar: { - offsetY: 0, - startAngle: -180, - endAngle: 180, - hollow: { - margin: 5, - size: "30%", - background: "transparent", - image: undefined, - }, - track: { - show: true, - background: cuiSecondaryBg, - opacity: 0.5, - dropShadow: { - enabled: false, - top: 2, - left: 0, - color: '#999999', - opacity: 0.2, - blur: 2 - } - }, - dataLabels: { - name: { - show: false - }, - value: { - show: true - } - } - }, + backgroundColor: 'transparent', + angleAxis: { + show: false, + max: 100, + startAngle: 270 }, - colors: ["#00bc4c", '#efaf2f', "#bf0000", "#6b737c"], - labels: [ - this.TranslocoService.translate('Ok'), - this.TranslocoService.translate('Warning'), - this.TranslocoService.translate('Critical'), - this.TranslocoService.translate('Unknown'), - ], - legend: { - show: false + radiusAxis: { + type: 'category', + show: false, + data: labels }, - responsive: [ + tooltip: { + show: true, + formatter: '{b} ({c}%)' + }, + series: [ { - breakpoint: 480, - options: { - legend: { - show: false + type: 'bar', + barWidth: '50%', + data: [ + { + value: this.statusCounts.servicestatusCountPercentage[3], + itemStyle: { + color: { + type: 'linear', + x: 0.5, + y: 0.5, + x2: 1.5, + y2: 1.5, + colorStops: [ + { + offset: 0.1, + color: '#6b737c' // color at 0% + }, + { + offset: 1, + color: '#4f555a' // color at 100% + } + ], + global: false // default is false + } + } + }, + { + value: this.statusCounts.servicestatusCountPercentage[2], + itemStyle: { + color: { + type: 'linear', + x: 0.5, + y: 0.5, + x2: 1.5, + y2: 1.5, + colorStops: [ + { + offset: 0.1, + color: '#CC0000' // color at 0% + }, + { + offset: 1, + color: '#c0022e' // color at 100% + } + ], + global: false // default is false + } + } + }, + { + value: this.statusCounts.servicestatusCountPercentage[1], + itemStyle: { + color: { + type: 'linear', + x: 0.5, + y: 0.5, + x2: 1.5, + y2: 1.5, + colorStops: [ + { + offset: 0.1, + color: '#ffbb33' // color at 0% + }, + { + offset: 1, + color: '#ffbb33' // color at 100% + } + ], + global: false // default is false + } + } + }, + { + value: this.statusCounts.servicestatusCountPercentage[0], + itemStyle: { + color: { + type: 'linear', + x: 0.5, + y: 0.5, + x2: 1.5, + y2: 1.5, + colorStops: [ + { + offset: 0.1, + color: '#00bc4c' // color at 0% + }, + { + offset: 1, + color: '#039a3f' // color at 100% + } + ], + global: false // default is false + }, + borderWidth: 1 + } } - } + ], + + colorBy: 'series', + showBackground: true, + backgroundStyle: { + color: 'rgba(180, 180, 180, 0.1)', + borderRadius: [5, 5, 0, 0] + }, + + coordinateSystem: 'polar' } ] }; - } - - public override resizeWidget(event?: KtdResizeEnd) { - if (this.chart) { - this.chart.updateOptions(this.chartOptions); - } + this.cdr.markForCheck(); } public override layoutUpdate(event: KtdGridLayout) { diff --git a/src/app/pages/hostgroups/hostgroups-append/hostgroups-append.component.html b/src/app/pages/hostgroups/hostgroups-append/hostgroups-append.component.html index 696c45272..36f773491 100644 --- a/src/app/pages/hostgroups/hostgroups-append/hostgroups-append.component.html +++ b/src/app/pages/hostgroups/hostgroups-append/hostgroups-append.component.html @@ -54,8 +54,12 @@
[(ngModel)]="post.Hostgroup.id" optionValue="key" optionLabel="value" - [options]="hostgroups"> + [options]="hostgroups" + [searchCallback]="loadHostgroups" + oitcFormError [errors]="errors" errorField="hostgroup_id"> +
diff --git a/src/app/pages/hostgroups/hostgroups-append/hostgroups-append.component.ts b/src/app/pages/hostgroups/hostgroups-append/hostgroups-append.component.ts index 7c531a929..26ed5602f 100644 --- a/src/app/pages/hostgroups/hostgroups-append/hostgroups-append.component.ts +++ b/src/app/pages/hostgroups/hostgroups-append/hostgroups-append.component.ts @@ -2,24 +2,19 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, OnDestro import { BackButtonDirective } from '../../../directives/back-button.directive'; import { HistoryService } from '../../../history.service'; import { - AlertComponent, - CardBodyComponent, - CardComponent, - CardFooterComponent, - CardHeaderComponent, - CardTitleDirective, - FormDirective, - FormLabelDirective, - NavComponent, - NavItemComponent + AlertComponent, + CardBodyComponent, + CardComponent, + CardFooterComponent, + CardHeaderComponent, + CardTitleDirective, + FormDirective, + FormLabelDirective, + NavComponent, + NavItemComponent } from '@coreui/angular'; -import { CoreuiComponent } from '../../../layouts/coreui/coreui.component'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; - - import { FormsModule } from '@angular/forms'; - - import { PaginatorModule } from 'primeng/paginator'; import { PermissionDirective } from '../../../permissions/permission.directive'; import { RequiredIconComponent } from '../../../components/required-icon/required-icon.component'; @@ -30,40 +25,45 @@ import { HostgroupsService } from '../hostgroups.service'; import { HostgroupAppend, HostgroupsLoadHostgroupsByStringParams } from '../hostgroups.interface'; import { Subscription } from 'rxjs'; import { SelectKeyValue } from '../../../layouts/primeng/select.interface'; -import { ActivatedRoute, Router, RouterLink } from '@angular/router'; +import { ActivatedRoute, RouterLink } from '@angular/router'; import { NotyService } from '../../../layouts/coreui/noty.service'; -import { GenericResponseWrapper } from '../../../generic-responses'; +import { GenericIdResponse, GenericValidationError } from '../../../generic-responses'; +import { FormErrorDirective } from '../../../layouts/coreui/form-error.directive'; +import { FormFeedbackComponent } from '../../../layouts/coreui/form-feedback/form-feedback.component'; + @Component({ selector: 'oitc-hostgroups-append', imports: [ - BackButtonDirective, - CardBodyComponent, - CardComponent, - CardFooterComponent, - CardHeaderComponent, - CardTitleDirective, - FaIconComponent, - FormDirective, - FormLabelDirective, - FormsModule, - NavComponent, - NavItemComponent, - PaginatorModule, - PermissionDirective, - RequiredIconComponent, - SelectComponent, - TranslocoDirective, - XsButtonDirective, - AlertComponent, - RouterLink -], + BackButtonDirective, + CardBodyComponent, + CardComponent, + CardFooterComponent, + CardHeaderComponent, + CardTitleDirective, + FaIconComponent, + FormDirective, + FormLabelDirective, + FormErrorDirective, + FormsModule, + NavComponent, + NavItemComponent, + PaginatorModule, + PermissionDirective, + RequiredIconComponent, + SelectComponent, + TranslocoDirective, + XsButtonDirective, + AlertComponent, + RouterLink, + FormFeedbackComponent + ], templateUrl: './hostgroups-append.component.html', styleUrl: './hostgroups-append.component.css', changeDetection: ChangeDetectionStrategy.OnPush }) export class HostgroupsAppendComponent implements OnInit, OnDestroy { - private readonly Subscription: Subscription = new Subscription(); + private readonly subscriptions: Subscription = new Subscription(); private readonly HostgroupsService: HostgroupsService = inject(HostgroupsService); private readonly notyService: NotyService = inject(NotyService); private readonly TranslocoService: TranslocoService = inject(TranslocoService); @@ -78,34 +78,57 @@ export class HostgroupsAppendComponent implements OnInit, OnDestroy { } }; protected hostgroups: SelectKeyValue[] = []; + public errors: GenericValidationError | null = null; private cdr = inject(ChangeDetectorRef); + public ngOnInit() { + this.loadHostgroups(''); + this.cdr.markForCheck(); + } + + ngOnDestroy(): void { + this.subscriptions.unsubscribe(); + } + + protected loadHostgroups = (search: string) => { + this.subscriptions.add(this.HostgroupsService.loadHostgroupsByString({ + 'filter[Containers.name]': search + } as HostgroupsLoadHostgroupsByStringParams).subscribe((data: SelectKeyValue[]) => { + this.hostgroups = data; + this.cdr.markForCheck(); + })); + } + protected submit(): void { const hostIds = this.route.snapshot.paramMap.get('hostids'); if (hostIds) { this.post.Hostgroup.hosts._ids = hostIds.split(',').map(Number); } - this.Subscription.add(this.HostgroupsService.appendHosts(this.post).subscribe((result: GenericResponseWrapper) => { - this.cdr.markForCheck(); - const title = this.TranslocoService.translate('Host group'); - const msg = this.TranslocoService.translate('saved successfully'); - const url = ['hostgroups', 'edit', this.post.Hostgroup.id]; + this.subscriptions.add(this.HostgroupsService.appendHosts(this.post) + .subscribe((result) => { + this.cdr.markForCheck(); + if (result.success) { + const response = result.data as GenericIdResponse; - this.notyService.genericSuccess(msg, title, url); - this.HistoryService.navigateWithFallback(['/hostgroups/index']); - })); - } + const title = this.TranslocoService.translate('Append hosts to host group'); + const msg = this.TranslocoService.translate(' successfully'); + const url = ['hostgroups', 'edit', response.id]; - public ngOnInit() { - this.Subscription.add(this.HostgroupsService.loadHostgroupsByString({} as HostgroupsLoadHostgroupsByStringParams).subscribe((data: SelectKeyValue[]) => { - this.hostgroups = data; - this.cdr.markForCheck(); - })); - } + this.notyService.genericSuccess(msg, title, url); - ngOnDestroy(): void { - this.Subscription.unsubscribe(); - } + this.notyService.scrollContentDivToTop(); + this.HistoryService.navigateWithFallback(['/hosts/index']); + return; + } + // Error + const errorResponse = result.data as GenericValidationError; + this.notyService.genericError(); + if (result) { + this.errors = errorResponse; + } + }) + ); + } } diff --git a/src/app/pages/hostgroups/hostgroups.service.ts b/src/app/pages/hostgroups/hostgroups.service.ts index d1e894c4c..62c77eed1 100644 --- a/src/app/pages/hostgroups/hostgroups.service.ts +++ b/src/app/pages/hostgroups/hostgroups.service.ts @@ -24,7 +24,12 @@ import { } from "./hostgroups.interface"; import { HttpClient } from "@angular/common/http"; import { PROXY_PATH } from "../../tokens/proxy-path.token"; -import { GenericIdResponse, GenericResponseWrapper, GenericValidationError } from "../../generic-responses"; +import { + GenericActionErrorResponse, + GenericIdResponse, + GenericResponseWrapper, + GenericValidationError +} from "../../generic-responses"; import { DeleteAllItem } from "../../layouts/coreui/delete-all-modal/delete-all.interface"; import { SelectKeyValue } from '../../layouts/primeng/select.interface'; @@ -243,7 +248,27 @@ export class HostgroupsService { public appendHosts(param: HostgroupAppend): Observable { const proxyPath: string = this.proxyPath; - return this.http.post(`${proxyPath}/hostgroups/append/.json?angular=true`, param); + return this.http.post(`${proxyPath}/hostgroups/append/.json?angular=true`, param) + .pipe( + map(data => { + // Return true on 200 Ok + return { + success: true, + data: data as GenericIdResponse + }; + }), + catchError((error: any) => { + const err = error.error.message as GenericActionErrorResponse; + return of({ + success: false, + data: { + hostgroup_id: { + err + } + } + }); + }) + ); } public loadHostgroupsByContainerId(containerId: number, selected: any[], resolveContainerIds: boolean = true): Observable { diff --git a/src/app/pages/hosts/hosts-add/hosts-add.component.html b/src/app/pages/hosts/hosts-add/hosts-add.component.html index cd21e4900..8253602e8 100644 --- a/src/app/pages/hosts/hosts-add/hosts-add.component.html +++ b/src/app/pages/hosts/hosts-add/hosts-add.component.html @@ -166,7 +166,7 @@
{{ t('Create new host') }}
{{ t('A host with the name {0} already exists. Duplicate host names could lead to confusion.', { - '0': post.name + '0': hostnameCheckedForDuplicates }) }} diff --git a/src/app/pages/hosts/hosts-add/hosts-add.component.ts b/src/app/pages/hosts/hosts-add/hosts-add.component.ts index f91905cc9..e03a1f8c7 100644 --- a/src/app/pages/hosts/hosts-add/hosts-add.component.ts +++ b/src/app/pages/hosts/hosts-add/hosts-add.component.ts @@ -135,6 +135,9 @@ export class HostsAddComponent implements OnInit, OnDestroy { isHostnameInUse: false }; + public hostnameCheckedForDuplicates: string = ''; + + public hosttemplates: SelectKeyValue[] = []; public hostgroups: SelectKeyValue[] = []; public timeperiods: SelectKeyValue[] = []; @@ -417,6 +420,8 @@ export class HostsAddComponent implements OnInit, OnDestroy { this.subscriptions.add(this.HostsService.checkForDuplicateHostname(this.post.name) .subscribe((result) => { this.data.isHostnameInUse = result; + // Ensure that the host name in the warning box will not change until the user changes the name again + this.hostnameCheckedForDuplicates = this.post.name; this.cdr.markForCheck(); }) ); diff --git a/src/app/pages/hosts/hosts-browser/hosts-browser.component.html b/src/app/pages/hosts/hosts-browser/hosts-browser.component.html index 4a207cd34..449f726ec 100644 --- a/src/app/pages/hosts/hosts-browser/hosts-browser.component.html +++ b/src/app/pages/hosts/hosts-browser/hosts-browser.component.html @@ -387,6 +387,62 @@
{{ t('The state of this host is currently flapping!') }} + + @if (result && result.plannedDowntimes && result.plannedDowntimes.length > 0) { + + + + +
+ + {{ t('Upcoming planned maintenance periods for the host') }} +
+
+ + + + + + + + + + + + + @for (downtime of result.plannedDowntimes; track $index) { + + + + + + + + } + +
{{ t('User') }}{{ t('Start time') }}{{ t('End time') }} {{ t('Comment') }}{{ t('Cancel') }}
{{ downtime.authorName }}{{ downtime.scheduledStartTime }}{{ downtime.scheduledEndTime }}{{ downtime.commentData }} + @if (downtime.allowEdit && downtime.isCancellable) { +
+ +
+ } +
+
+
+
+
+ } + diff --git a/src/app/pages/hosts/hosts-edit-details/hosts-edit-details.component.html b/src/app/pages/hosts/hosts-edit-details/hosts-edit-details.component.html index 5eee13abe..0bbad5b92 100644 --- a/src/app/pages/hosts/hosts-edit-details/hosts-edit-details.component.html +++ b/src/app/pages/hosts/hosts-edit-details/hosts-edit-details.component.html @@ -471,21 +471,14 @@
- - - - - - - - diff --git a/src/app/pages/hosts/hosts-edit/hosts-edit.component.html b/src/app/pages/hosts/hosts-edit/hosts-edit.component.html index bb9ece641..f70c17bd4 100644 --- a/src/app/pages/hosts/hosts-edit/hosts-edit.component.html +++ b/src/app/pages/hosts/hosts-edit/hosts-edit.component.html @@ -204,7 +204,7 @@
{{ t('Edit host:') }} {{ t('A host with the name {0} already exists. Duplicate host names could lead to confusion.', { - '0': post.name + '0': hostnameCheckedForDuplicates }) }} diff --git a/src/app/pages/hosts/hosts-edit/hosts-edit.component.ts b/src/app/pages/hosts/hosts-edit/hosts-edit.component.ts index 022cb02fb..fd7ac6433 100644 --- a/src/app/pages/hosts/hosts-edit/hosts-edit.component.ts +++ b/src/app/pages/hosts/hosts-edit/hosts-edit.component.ts @@ -1,26 +1,26 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, OnDestroy, OnInit } from '@angular/core'; import { - AlertComponent, - CardBodyComponent, - CardComponent, - CardFooterComponent, - CardHeaderComponent, - CardTitleDirective, - DropdownComponent, - DropdownItemDirective, - DropdownMenuDirective, - DropdownToggleDirective, - FormCheckComponent, - FormCheckInputDirective, - FormCheckLabelDirective, - FormControlDirective, - FormDirective, - FormLabelDirective, - InputGroupComponent, - InputGroupTextDirective, - NavComponent, - NavItemComponent + AlertComponent, + CardBodyComponent, + CardComponent, + CardFooterComponent, + CardHeaderComponent, + CardTitleDirective, + DropdownComponent, + DropdownItemDirective, + DropdownMenuDirective, + DropdownToggleDirective, + FormCheckComponent, + FormCheckInputDirective, + FormCheckLabelDirective, + FormControlDirective, + FormDirective, + FormLabelDirective, + InputGroupComponent, + InputGroupTextDirective, + NavComponent, + NavItemComponent } from '@coreui/angular'; import { BackButtonDirective } from '../../../directives/back-button.directive'; import { @@ -69,59 +69,59 @@ import { HistoryService } from '../../../history.service'; @Component({ selector: 'oitc-hosts-edit', imports: [ - AlertComponent, - BackButtonDirective, - CardBodyComponent, - CardComponent, - CardFooterComponent, - CardHeaderComponent, - CardTitleDirective, - CheckAttemptsInputComponent, - DropdownComponent, - DropdownItemDirective, - DropdownMenuDirective, - DropdownToggleDirective, - FaIconComponent, - FormCheckComponent, - FormCheckInputDirective, - FormCheckLabelDirective, - FormControlDirective, - FormDirective, - FormErrorDirective, - FormFeedbackComponent, - FormLabelDirective, - FormsModule, - HumanTimeComponent, - InputGroupComponent, - InputGroupTextDirective, - IntervalInputComponent, - LabelLinkComponent, - MacrosComponent, - MultiSelectComponent, - NavComponent, - NavItemComponent, - NgForOf, - NgIf, - NgSelectModule, - PermissionDirective, - PriorityComponent, - ReactiveFormsModule, - RequiredIconComponent, - SelectComponent, - TemplateDiffBtnComponent, - TemplateDiffComponent, - TranslocoDirective, - TranslocoPipe, - TrueFalseDirective, - XsButtonDirective, - RouterLink, - ObjectUuidComponent, - NgClass, - FakeSelectComponent, - UiBlockerComponent, - FormLoaderComponent, - AsyncPipe -], + AlertComponent, + BackButtonDirective, + CardBodyComponent, + CardComponent, + CardFooterComponent, + CardHeaderComponent, + CardTitleDirective, + CheckAttemptsInputComponent, + DropdownComponent, + DropdownItemDirective, + DropdownMenuDirective, + DropdownToggleDirective, + FaIconComponent, + FormCheckComponent, + FormCheckInputDirective, + FormCheckLabelDirective, + FormControlDirective, + FormDirective, + FormErrorDirective, + FormFeedbackComponent, + FormLabelDirective, + FormsModule, + HumanTimeComponent, + InputGroupComponent, + InputGroupTextDirective, + IntervalInputComponent, + LabelLinkComponent, + MacrosComponent, + MultiSelectComponent, + NavComponent, + NavItemComponent, + NgForOf, + NgIf, + NgSelectModule, + PermissionDirective, + PriorityComponent, + ReactiveFormsModule, + RequiredIconComponent, + SelectComponent, + TemplateDiffBtnComponent, + TemplateDiffComponent, + TranslocoDirective, + TranslocoPipe, + TrueFalseDirective, + XsButtonDirective, + RouterLink, + ObjectUuidComponent, + NgClass, + FakeSelectComponent, + UiBlockerComponent, + FormLoaderComponent, + AsyncPipe + ], templateUrl: './hosts-edit.component.html', styleUrl: './hosts-edit.component.css', changeDetection: ChangeDetectionStrategy.OnPush @@ -155,6 +155,9 @@ export class HostsEditComponent implements OnInit, OnDestroy { disableInheritance: false }; + public hostnameCheckedForDuplicates: string = ''; + + public hosttemplates: SelectKeyValue[] = []; public hostgroups: SelectKeyValue[] = []; public timeperiods: SelectKeyValue[] = []; @@ -410,6 +413,8 @@ export class HostsEditComponent implements OnInit, OnDestroy { this.subscriptions.add(this.HostsService.checkForDuplicateHostname(this.post.name, [this.id]) .subscribe((result) => { this.data.isHostnameInUse = result; + // Ensure that the host name in the warning box will not change until the user changes the name again + this.hostnameCheckedForDuplicates = this.post.name; this.cdr.markForCheck(); }) ); diff --git a/src/app/pages/hosts/hosts-index/hosts-index.component.html b/src/app/pages/hosts/hosts-index/hosts-index.component.html index bcc46d0f1..d64588917 100644 --- a/src/app/pages/hosts/hosts-index/hosts-index.component.html +++ b/src/app/pages/hosts/hosts-index/hosts-index.component.html @@ -1088,7 +1088,6 @@
- } - - @@ -1182,17 +1179,16 @@
{{ t('Copy') }} - -
  • - - - - {{ t('Delete') }} - - - + @if (host.Host.allow_edit && ( PermissionsService.hasPermissionObservable(['hosts', 'delete'])|async )) { +
  • + + + + {{ t('Delete') }} + + + } @@ -1254,7 +1250,7 @@
    - + {{ t('Add to host group') }} diff --git a/src/app/pages/hosts/hosts-index/hosts-index.component.ts b/src/app/pages/hosts/hosts-index/hosts-index.component.ts index 44892f19d..a4facddae 100644 --- a/src/app/pages/hosts/hosts-index/hosts-index.component.ts +++ b/src/app/pages/hosts/hosts-index/hosts-index.component.ts @@ -317,7 +317,7 @@ export class HostsIndexComponent implements OnInit, OnDestroy, IndexPage { this.filter['Hosts.address'] = address; } - let address_regex = params['address_regex'] ; + let address_regex = params['address_regex']; if (address_regex === 'true') { this.filter['Hosts.address_regex'] = true; } @@ -596,7 +596,7 @@ export class HostsIndexComponent implements OnInit, OnDestroy, IndexPage { let ids = this.SelectionServiceService.getSelectedItems().map(item => item.Host.id).join(','); if (ids) { this.router.navigate(['/', 'hosts', 'copy', ids]); - }else { + } else { const message = this.TranslocoService.translate('No items selected!'); this.notyService.genericError(message); return; diff --git a/src/app/pages/hosts/hosts.interface.ts b/src/app/pages/hosts/hosts.interface.ts index 6fd0b1c16..7d4fd7a3b 100644 --- a/src/app/pages/hosts/hosts.interface.ts +++ b/src/app/pages/hosts/hosts.interface.ts @@ -778,6 +778,7 @@ export interface HostBrowserResult { }, acknowledgement?: AcknowledgementObject, downtime?: DowntimeObject, + plannedDowntimes: DowntimeObject[], checkCommand: CheckCommandCake2, checkPeriod: TimeperiodEnity, notifyPeriod: TimeperiodEnity, diff --git a/src/app/pages/servicegroups/servicegroups-append/servicegroups-append.component.html b/src/app/pages/servicegroups/servicegroups-append/servicegroups-append.component.html index 2499474c6..583ba31dd 100644 --- a/src/app/pages/servicegroups/servicegroups-append/servicegroups-append.component.html +++ b/src/app/pages/servicegroups/servicegroups-append/servicegroups-append.component.html @@ -54,8 +54,12 @@
    [(ngModel)]="post.Servicegroup.id" optionValue="key" optionLabel="value" - [options]="servicegroups"> + [options]="servicegroups" + [searchCallback]="loadServicegroups" + oitcFormError [errors]="errors" errorField="servicegroup_id"> +
    @@ -82,4 +86,3 @@
    - diff --git a/src/app/pages/servicegroups/servicegroups-append/servicegroups-append.component.ts b/src/app/pages/servicegroups/servicegroups-append/servicegroups-append.component.ts index ba6680a40..2b1e9ba2a 100644 --- a/src/app/pages/servicegroups/servicegroups-append/servicegroups-append.component.ts +++ b/src/app/pages/servicegroups/servicegroups-append/servicegroups-append.component.ts @@ -2,18 +2,17 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, OnDestro import { BackButtonDirective } from '../../../directives/back-button.directive'; import { HistoryService } from '../../../history.service'; import { - AlertComponent, - CardBodyComponent, - CardComponent, - CardFooterComponent, - CardHeaderComponent, - CardTitleDirective, - FormDirective, - FormLabelDirective, - NavComponent, - NavItemComponent + AlertComponent, + CardBodyComponent, + CardComponent, + CardFooterComponent, + CardHeaderComponent, + CardTitleDirective, + FormDirective, + FormLabelDirective, + NavComponent, + NavItemComponent } from '@coreui/angular'; -import { CoreuiComponent } from '../../../layouts/coreui/coreui.component'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; @@ -27,43 +26,48 @@ import { SelectComponent } from '../../../layouts/primeng/select/select/select.c import { TranslocoDirective, TranslocoService } from '@jsverse/transloco'; import { XsButtonDirective } from '../../../layouts/coreui/xsbutton-directive/xsbutton.directive'; import { ServicegroupsService } from '../servicegroups.service'; -import { ServicegroupAppend, ServicegroupsLoadServicegroupsByStringParams } from '../servicegroups.interface'; +import { ServicegroupAppend } from '../servicegroups.interface'; import { Subscription } from 'rxjs'; import { SelectKeyValue } from '../../../layouts/primeng/select.interface'; import { ActivatedRoute, RouterLink } from '@angular/router'; import { NotyService } from '../../../layouts/coreui/noty.service'; -import { GenericResponseWrapper } from '../../../generic-responses'; +import { GenericIdResponse, GenericValidationError } from '../../../generic-responses'; +import { HostgroupsLoadHostgroupsByStringParams } from '../../hostgroups/hostgroups.interface'; +import { FormErrorDirective } from '../../../layouts/coreui/form-error.directive'; +import { FormFeedbackComponent } from '../../../layouts/coreui/form-feedback/form-feedback.component'; @Component({ selector: 'oitc-servicegroups-append', imports: [ - BackButtonDirective, - CardBodyComponent, - CardComponent, - CardFooterComponent, - CardHeaderComponent, - CardTitleDirective, - FaIconComponent, - FormDirective, - FormLabelDirective, - FormsModule, - NavComponent, - NavItemComponent, - PaginatorModule, - PermissionDirective, - RequiredIconComponent, - SelectComponent, - TranslocoDirective, - XsButtonDirective, - AlertComponent, - RouterLink -], + BackButtonDirective, + CardBodyComponent, + CardComponent, + CardFooterComponent, + CardHeaderComponent, + CardTitleDirective, + FaIconComponent, + FormDirective, + FormLabelDirective, + FormsModule, + NavComponent, + NavItemComponent, + PaginatorModule, + PermissionDirective, + RequiredIconComponent, + SelectComponent, + TranslocoDirective, + XsButtonDirective, + AlertComponent, + RouterLink, + FormErrorDirective, + FormFeedbackComponent + ], templateUrl: './servicegroups-append.component.html', styleUrl: './servicegroups-append.component.css', changeDetection: ChangeDetectionStrategy.OnPush }) export class ServicegroupsAppendComponent implements OnInit, OnDestroy { - private readonly Subscription: Subscription = new Subscription(); + private readonly subscriptions: Subscription = new Subscription(); private readonly ServicegroupsService: ServicegroupsService = inject(ServicegroupsService); private readonly notyService: NotyService = inject(NotyService); private readonly TranslocoService: TranslocoService = inject(TranslocoService); @@ -72,6 +76,7 @@ export class ServicegroupsAppendComponent implements OnInit, OnDestroy { private readonly cdr = inject(ChangeDetectorRef); protected servicegroups: SelectKeyValue[] = []; + public errors: GenericValidationError | null = null; protected post: ServicegroupAppend = { Servicegroup: { services: { @@ -81,36 +86,56 @@ export class ServicegroupsAppendComponent implements OnInit, OnDestroy { } }; - protected submit(): void { - const serviceIds = this.route.snapshot.paramMap.get('serviceids'); - if (serviceIds) { - this.post.Servicegroup.services._ids = serviceIds.split(',').map(Number); - } - - this.Subscription.add(this.ServicegroupsService.appendServices(this.post).subscribe((result: GenericResponseWrapper) => { - - this.cdr.markForCheck(); - - const title = this.TranslocoService.translate('Service group'); - const msg = this.TranslocoService.translate('saved successfully'); - const url = ['servicegroups', 'edit', this.post.Servicegroup.id]; - - this.notyService.genericSuccess(msg, title, url); + public ngOnInit() { + this.loadServicegroups(''); + this.cdr.markForCheck(); + } - this.HistoryService.navigateWithFallback(['/servicegroups/index']); - })); + ngOnDestroy(): void { + this.subscriptions.unsubscribe(); } - public ngOnInit() { - this.Subscription.add(this.ServicegroupsService.loadServicegroupsByString({} as ServicegroupsLoadServicegroupsByStringParams).subscribe((data: SelectKeyValue[]) => { + protected loadServicegroups = (search: string) => { + this.subscriptions.add(this.ServicegroupsService.loadServicegroupsByString({ + 'filter[Containers.name]': search + } as HostgroupsLoadHostgroupsByStringParams).subscribe((data: SelectKeyValue[]) => { this.servicegroups = data; this.cdr.markForCheck(); })); } - ngOnDestroy(): void { - this.Subscription.unsubscribe(); + protected submit(): void { + const serviceIds = this.route.snapshot.paramMap.get('serviceids'); + if (serviceIds) { + this.post.Servicegroup.services._ids = serviceIds.split(',').map(Number); + } + + this.subscriptions.add(this.ServicegroupsService.appendServices(this.post) + .subscribe((result) => { + this.cdr.markForCheck(); + if (result.success) { + const response = result.data as GenericIdResponse; + + const title = this.TranslocoService.translate('Append services to service group'); + const msg = this.TranslocoService.translate(' successfully'); + const url = ['servicegroups', 'edit', response.id]; + + this.notyService.genericSuccess(msg, title, url); + + this.notyService.scrollContentDivToTop(); + this.HistoryService.navigateWithFallback(['/services/index']); + return; + } + + // Error + const errorResponse = result.data as GenericValidationError; + this.notyService.genericError(); + if (result) { + this.errors = errorResponse; + } + }) + ); } } diff --git a/src/app/pages/servicegroups/servicegroups.service.ts b/src/app/pages/servicegroups/servicegroups.service.ts index 78ba438f3..7e99c7f8a 100644 --- a/src/app/pages/servicegroups/servicegroups.service.ts +++ b/src/app/pages/servicegroups/servicegroups.service.ts @@ -23,7 +23,12 @@ import { } from "./servicegroups.interface"; import { HttpClient } from "@angular/common/http"; import { PROXY_PATH } from "../../tokens/proxy-path.token"; -import { GenericIdResponse, GenericResponseWrapper, GenericValidationError } from "../../generic-responses"; +import { + GenericActionErrorResponse, + GenericIdResponse, + GenericResponseWrapper, + GenericValidationError +} from "../../generic-responses"; import { DeleteAllItem } from "../../layouts/coreui/delete-all-modal/delete-all.interface"; import { SelectKeyValue } from '../../layouts/primeng/select.interface'; @@ -228,7 +233,27 @@ export class ServicegroupsService { public appendServices(param: ServicegroupAppend): Observable { const proxyPath: string = this.proxyPath; - return this.http.post(`${proxyPath}/servicegroups/append/.json?angular=true`, param); + return this.http.post(`${proxyPath}/servicegroups/append/.json?angular=true`, param) + .pipe( + map(data => { + // Return true on 200 Ok + return { + success: true, + data: data as GenericIdResponse + }; + }), + catchError((error: any) => { + const err = error.error.message as GenericActionErrorResponse; + return of({ + success: false, + data: { + servicegroup_id: { + err + } + } + }); + }) + ); } public loadServicegroupsByContainerId(containerId: number, selected: any[], resolveContainerIds: boolean = true): Observable { diff --git a/src/app/pages/services/services-add/services-add.component.html b/src/app/pages/services/services-add/services-add.component.html index 79cb7a446..560690ad2 100644 --- a/src/app/pages/services/services-add/services-add.component.html +++ b/src/app/pages/services/services-add/services-add.component.html @@ -131,7 +131,7 @@
    {{ t('Create new service') }}
    {{ t('A service with the name {0} already exists on this host. Duplicate service names could lead to confusion.', { - '0': post.name + '0': servicenameCheckedForDuplicates }) }} diff --git a/src/app/pages/services/services-add/services-add.component.ts b/src/app/pages/services/services-add/services-add.component.ts index b19d5317f..0399e654a 100644 --- a/src/app/pages/services/services-add/services-add.component.ts +++ b/src/app/pages/services/services-add/services-add.component.ts @@ -21,7 +21,6 @@ import { BackButtonDirective } from '../../../directives/back-button.directive'; import { CheckAttemptsInputComponent } from '../../../layouts/coreui/check-attempts-input/check-attempts-input.component'; -import { CoreuiComponent } from '../../../layouts/coreui/coreui.component'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { FormErrorDirective } from '../../../layouts/coreui/form-error.directive'; import { FormFeedbackComponent } from '../../../layouts/coreui/form-feedback/form-feedback.component'; @@ -136,6 +135,8 @@ export class ServicesAddComponent { areContactsInheritedFromServicetemplate: false, }; + public servicenameCheckedForDuplicates: string = ''; + public servicetemplates: SelectKeyValue[] = []; public servicegroups: SelectKeyValue[] = []; public timeperiods: SelectKeyValue[] = []; @@ -380,6 +381,8 @@ export class ServicesAddComponent { public checkForDuplicateServicename() { const existingServicesNames: string[] = Object.values(this.existingServices); this.data.isServicenameInUse = existingServicesNames.includes(this.post.name); + // Ensure that the service name in the warning box will not change until the user changes the name again + this.servicenameCheckedForDuplicates = this.post.name; this.cdr.markForCheck(); } diff --git a/src/app/pages/services/services-browser/services-browser.component.html b/src/app/pages/services/services-browser/services-browser.component.html index b4f2c0dcd..176ba062f 100644 --- a/src/app/pages/services/services-browser/services-browser.component.html +++ b/src/app/pages/services/services-browser/services-browser.component.html @@ -492,6 +492,118 @@
    + + @if (result && result.plannedDowntimes && result.plannedDowntimes.length > 0) { + + + + +
    + + {{ t('Upcoming planned maintenance periods for the service') }} +
    +
    + + + + + + + + + + + + + @for (downtime of result.plannedDowntimes; track $index) { + + + + + + + + } + +
    {{ t('User') }}{{ t('Start time') }}{{ t('End time') }} {{ t('Comment') }}{{ t('Cancel') }}
    {{ downtime.authorName }}{{ downtime.scheduledStartTime }}{{ downtime.scheduledEndTime }}{{ downtime.commentData }} + @if (downtime.allowEdit && downtime.isCancellable) { +
    + +
    + } +
    +
    +
    +
    +
    + } + + + @if (result && result.plannedHostDowntimes && result.plannedHostDowntimes.length > 0) { + + + + +
    + + {{ t('Upcoming planned maintenance periods for the host of this service') }} +
    +
    + + + + + + + + + + + + + @for (downtime of result.plannedHostDowntimes; track $index) { + + + + + + + + } + +
    {{ t('User') }}{{ t('Start time') }}{{ t('End time') }} {{ t('Comment') }}{{ t('Cancel') }}
    {{ downtime.authorName }}{{ downtime.scheduledStartTime }}{{ downtime.scheduledEndTime }}{{ downtime.commentData }} + @if (downtime.allowEdit && downtime.isCancellable) { +
    + +
    + } +
    +
    +
    +
    +
    + } + diff --git a/src/app/pages/services/services-edit/services-edit.component.html b/src/app/pages/services/services-edit/services-edit.component.html index aee827198..778635032 100644 --- a/src/app/pages/services/services-edit/services-edit.component.html +++ b/src/app/pages/services/services-edit/services-edit.component.html @@ -187,7 +187,7 @@
    {{ t('A service with the name {0} already exists on this host. Duplicate service names could lead to confusion.', { - '0': post.name + '0': servicenameCheckedForDuplicates }) }} diff --git a/src/app/pages/services/services-edit/services-edit.component.ts b/src/app/pages/services/services-edit/services-edit.component.ts index 0b1e926c5..b8c59b04b 100644 --- a/src/app/pages/services/services-edit/services-edit.component.ts +++ b/src/app/pages/services/services-edit/services-edit.component.ts @@ -21,7 +21,6 @@ import { BackButtonDirective } from '../../../directives/back-button.directive'; import { CheckAttemptsInputComponent } from '../../../layouts/coreui/check-attempts-input/check-attempts-input.component'; -import { CoreuiComponent } from '../../../layouts/coreui/coreui.component'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { FormErrorDirective } from '../../../layouts/coreui/form-error.directive'; import { FormFeedbackComponent } from '../../../layouts/coreui/form-feedback/form-feedback.component'; @@ -142,6 +141,7 @@ export class ServicesEditComponent { areContactsInheritedFromHost: false, areContactsInheritedFromServicetemplate: false, }; + public servicenameCheckedForDuplicates: string = ''; public servicetemplates: SelectKeyValue[] = []; public servicegroups: SelectKeyValue[] = []; @@ -149,7 +149,7 @@ export class ServicesEditComponent { public checkperiods: SelectKeyValue[] = []; public contacts: SelectKeyValue[] = []; public contactgroups: SelectKeyValue[] = []; - public existingServices: object = {} + public existingServices: Record = {}; public isSlaHost: boolean = false; public errors: GenericValidationError | null = null; @@ -326,8 +326,17 @@ export class ServicesEditComponent { } public checkForDuplicateServicename() { + // Remove current service from existing services to avoid false positive + const serviceId = this.post.id; + if (serviceId && this.existingServices) { + if (this.existingServices.hasOwnProperty(serviceId)) { + delete this.existingServices[serviceId]; + } + } + const existingServicesNames: string[] = Object.values(this.existingServices); this.data.isServicenameInUse = existingServicesNames.includes(this.post.name); + this.servicenameCheckedForDuplicates = this.post.name; this.cdr.markForCheck(); } diff --git a/src/app/pages/services/services-index/services-index.component.html b/src/app/pages/services/services-index/services-index.component.html index 7126b3613..0bbb5a6c6 100644 --- a/src/app/pages/services/services-index/services-index.component.html +++ b/src/app/pages/services/services-index/services-index.component.html @@ -941,7 +941,7 @@
  • - + {{ t('Add to service group') }}
  • diff --git a/src/app/pages/services/services.interface.ts b/src/app/pages/services/services.interface.ts index 89925b985..9e1156bbc 100644 --- a/src/app/pages/services/services.interface.ts +++ b/src/app/pages/services/services.interface.ts @@ -766,7 +766,9 @@ export interface ServiceBrowserResult { servicestatus: ServicestatusObject acknowledgement?: AcknowledgementObject downtime?: DowntimeObject + plannedDowntimes: DowntimeObject[], hostDowntime?: DowntimeObject + plannedHostDowntimes: DowntimeObject[], hostAcknowledgement?: AcknowledgementObject checkCommand: CheckCommandCake2 checkPeriod: TimeperiodEnity diff --git a/src/app/pages/servicetemplategroups/servicetemplategroups-allocate-to-host/servicetemplategroups-allocate-to-host.component.html b/src/app/pages/servicetemplategroups/servicetemplategroups-allocate-to-host/servicetemplategroups-allocate-to-host.component.html index 4560f1d75..87c0fbc7c 100644 --- a/src/app/pages/servicetemplategroups/servicetemplategroups-allocate-to-host/servicetemplategroups-allocate-to-host.component.html +++ b/src/app/pages/servicetemplategroups/servicetemplategroups-allocate-to-host/servicetemplategroups-allocate-to-host.component.html @@ -20,7 +20,7 @@ -
    +
    @@ -55,7 +55,7 @@
    id="servicetemplategroupId" [(ngModel)]="servicetemplategroupId" optionValue="key" - (ngModelChange)="hostIdChanged()" + (onChange)="loadServices()" optionLabel="value" [searchCallback]="loadServicetemplategroups" [options]="servicetemplategroups" @@ -78,7 +78,7 @@
    name="hostId" id="hostId" [(ngModel)]="hostId" - (ngModelChange)="hostIdChanged()" + (onChange)="loadServices()" optionValue="key" optionLabel="value" [searchCallback]="loadHosts" @@ -115,7 +115,7 @@
    {{ t('Service/s to deploy on target host:') }}
    + *ngFor="let hostWithServicesToDeploy of hostsWithServicetemplatesForDeploy; let i = index;">
    - - - diff --git a/src/app/pages/servicetemplategroups/servicetemplategroups-allocate-to-host/servicetemplategroups-allocate-to-host.component.ts b/src/app/pages/servicetemplategroups/servicetemplategroups-allocate-to-host/servicetemplategroups-allocate-to-host.component.ts index 1203fb4b3..a7b41e89f 100644 --- a/src/app/pages/servicetemplategroups/servicetemplategroups-allocate-to-host/servicetemplategroups-allocate-to-host.component.ts +++ b/src/app/pages/servicetemplategroups/servicetemplategroups-allocate-to-host/servicetemplategroups-allocate-to-host.component.ts @@ -1,19 +1,18 @@ import { ChangeDetectionStrategy, ChangeDetectorRef, Component, inject, OnDestroy, OnInit } from '@angular/core'; import { BackButtonDirective } from '../../../directives/back-button.directive'; import { - AlertComponent, - CardBodyComponent, - CardComponent, - CardFooterComponent, - CardHeaderComponent, - CardTitleDirective, - FormCheckInputDirective, - FormDirective, - FormLabelDirective, - NavComponent, - NavItemComponent, - ProgressComponent, - TooltipDirective + AlertComponent, + CardBodyComponent, + CardComponent, + CardFooterComponent, + CardHeaderComponent, + CardTitleDirective, + FormCheckInputDirective, + FormDirective, + FormLabelDirective, + NavComponent, + NavItemComponent, + TooltipDirective } from '@coreui/angular'; import { FaIconComponent } from '@fortawesome/angular-fontawesome'; import { FormErrorDirective } from '../../../layouts/coreui/form-error.directive'; @@ -48,35 +47,34 @@ import { HistoryService } from '../../../history.service'; @Component({ selector: 'oitc-servicetemplategroups-allocate-to-host', imports: [ - BackButtonDirective, - CardBodyComponent, - CardComponent, - CardFooterComponent, - CardHeaderComponent, - CardTitleDirective, - FaIconComponent, - FormCheckInputDirective, - FormDirective, - FormErrorDirective, - FormFeedbackComponent, - FormLabelDirective, - FormsModule, - NavComponent, - NavItemComponent, - NgForOf, - NgIf, - NgSelectModule, - PermissionDirective, - RequiredIconComponent, - TranslocoDirective, - XsButtonDirective, - AlertComponent, - TranslocoPipe, - ProgressComponent, - RouterLink, - SelectComponent, - TooltipDirective -], + BackButtonDirective, + CardBodyComponent, + CardComponent, + CardFooterComponent, + CardHeaderComponent, + CardTitleDirective, + FaIconComponent, + FormCheckInputDirective, + FormDirective, + FormErrorDirective, + FormFeedbackComponent, + FormLabelDirective, + FormsModule, + NavComponent, + NavItemComponent, + NgForOf, + NgIf, + NgSelectModule, + PermissionDirective, + RequiredIconComponent, + TranslocoDirective, + XsButtonDirective, + AlertComponent, + TranslocoPipe, + RouterLink, + SelectComponent, + TooltipDirective + ], templateUrl: './servicetemplategroups-allocate-to-host.component.html', styleUrl: './servicetemplategroups-allocate-to-host.component.css', changeDetection: ChangeDetectionStrategy.OnPush @@ -100,14 +98,11 @@ export class ServicetemplategroupsAllocateToHostComponent implements OnInit, OnD protected hostsWithServicetemplatesForDeploy: AllocateToHostGetServicetemplatesForDeploy[] = []; protected servicetemplategroupId: number = 0; protected hostId: number = 0; - protected percentage: number = 0; - protected isProcessing: boolean = false; public ngOnInit() { this.servicetemplategroupId = Number(this.route.snapshot.paramMap.get('id')); let hostId: number = Number(this.route.snapshot.paramMap.get('hostId')); if (hostId > 0) { - this.hostId = hostId; } this.loadServicetemplategroups(''); @@ -119,7 +114,7 @@ export class ServicetemplategroupsAllocateToHostComponent implements OnInit, OnD } - private loadServices(): void { + public loadServices(): void { if (this.servicetemplategroupId === 0 || this.hostId === 0) { return; } @@ -131,10 +126,6 @@ export class ServicetemplategroupsAllocateToHostComponent implements OnInit, OnD ) } - protected hostIdChanged(): void { - this.loadServices(); - } - protected selectAll(): void { if (typeof this.hostsWithServicetemplatesForDeploy === "undefined") { return; @@ -155,7 +146,7 @@ export class ServicetemplategroupsAllocateToHostComponent implements OnInit, OnD } - protected allocateToHost(): void { + protected submit(): void { let item: AllocateToHostPost = { Host: { @@ -170,19 +161,10 @@ export class ServicetemplategroupsAllocateToHostComponent implements OnInit, OnD item.Servicetemplates._ids.push(this.hostsWithServicetemplatesForDeploy[hostIndex].servicetemplate.id); } } - this.isProcessing = true; - let i = 0; - let count = item.Servicetemplates._ids.length; - this.ServicetemplategroupsService.allocateToHost(this.servicetemplategroupId, item); - - this.subscriptions.add(this.ServicetemplategroupsService.allocateToHost(this.servicetemplategroupId, item) .subscribe((result: GenericResponseWrapper) => { this.cdr.markForCheck(); if (result.success) { - i++; - this.percentage = Math.round(i / count * 100); - this.notyService.genericSuccess(); this.HistoryService.navigateWithFallback(['/servicetemplategroups/index']); return; diff --git a/src/app/pages/servicetemplategroups/servicetemplategroups-allocate-to-hostgroup/servicetemplategroups-allocate-to-hostgroup.component.html b/src/app/pages/servicetemplategroups/servicetemplategroups-allocate-to-hostgroup/servicetemplategroups-allocate-to-hostgroup.component.html index c6d5258f9..812f27d08 100644 --- a/src/app/pages/servicetemplategroups/servicetemplategroups-allocate-to-hostgroup/servicetemplategroups-allocate-to-hostgroup.component.html +++ b/src/app/pages/servicetemplategroups/servicetemplategroups-allocate-to-hostgroup/servicetemplategroups-allocate-to-hostgroup.component.html @@ -20,7 +20,7 @@ - +
    @@ -55,7 +55,7 @@
    id="servicetemplategroupId" [(ngModel)]="servicetemplategroupId" optionValue="key" - (ngModelChange)="hostGroupIdChanged()" + (onChange)="loadServices()" optionLabel="value" [searchCallback]="loadServicetemplategroups" [options]="servicetemplategroups" @@ -78,7 +78,7 @@
    name="hostgroupId" id="hostgroupId" [(ngModel)]="hostgroupId" - (ngModelChange)="hostGroupIdChanged()" + (onChange)="loadServices()" optionValue="key" optionLabel="value" [searchCallback]="loadHostgroups" @@ -114,7 +114,8 @@
    + *ngFor="let hostWithServicesToDeploy of hostsWithServicetemplatesForDeploy; let i = index;" + class="mb-3">