From 36f5bf32c02f4634641a86b970665543b210b251 Mon Sep 17 00:00:00 2001 From: romanost03 Date: Wed, 2 Apr 2025 09:37:55 +0200 Subject: [PATCH 01/33] Add model code for saving the database data properly. New schema-discovery.service.ts with table-selection-dialog component to retrieve database information. --- package-lock.json | 1168 +++++++++++++++++ package.json | 29 +- .../table-selection-dialog.component.html | 38 + .../table-selection-dialog.component.scss | 0 .../table-selection-dialog.component.spec.ts | 27 + .../table-selection-dialog.component.ts | 46 + src/app/models/databaseInfo.model.ts | 20 + src/app/services/schema-discovery.service.ts | 63 + .../views/adapters/adapters.component.html | 3 +- src/app/views/adapters/adapters.component.ts | 8 +- 10 files changed, 1386 insertions(+), 16 deletions(-) create mode 100644 src/app/components/table-selection-dialog/table-selection-dialog.component.html create mode 100644 src/app/components/table-selection-dialog/table-selection-dialog.component.scss create mode 100644 src/app/components/table-selection-dialog/table-selection-dialog.component.spec.ts create mode 100644 src/app/components/table-selection-dialog/table-selection-dialog.component.ts create mode 100644 src/app/models/databaseInfo.model.ts create mode 100644 src/app/services/schema-discovery.service.ts diff --git a/package-lock.json b/package-lock.json index 2a646992..00a48b85 100644 --- a/package-lock.json +++ b/package-lock.json @@ -17,6 +17,7 @@ "@angular/core": "^17.2.3", "@angular/elements": "^17.2.3", "@angular/forms": "^17.2.3", + "@angular/material": "^17.2.1", "@angular/platform-browser": "^17.2.3", "@angular/platform-browser-dynamic": "^17.2.3", "@angular/router": "^17.2.3", @@ -1787,6 +1788,77 @@ "node": "^18.13.0 || >=20.9.0" } }, + "node_modules/@angular/material": { + "version": "17.2.1", + "resolved": "https://registry.npmjs.org/@angular/material/-/material-17.2.1.tgz", + "integrity": "sha512-NLQJkX4XiwIm32dGdNseoc+ARn6JvuB2xMY5XfWTtjJBbQaPk5sIvjH4wsAEeYqDKtZbRCjxGwRz0K1djyaVqQ==", + "license": "MIT", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/auto-init": "15.0.0-canary.7f224ddd4.0", + "@material/banner": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/button": "15.0.0-canary.7f224ddd4.0", + "@material/card": "15.0.0-canary.7f224ddd4.0", + "@material/checkbox": "15.0.0-canary.7f224ddd4.0", + "@material/chips": "15.0.0-canary.7f224ddd4.0", + "@material/circular-progress": "15.0.0-canary.7f224ddd4.0", + "@material/data-table": "15.0.0-canary.7f224ddd4.0", + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/dialog": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/drawer": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/fab": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/floating-label": "15.0.0-canary.7f224ddd4.0", + "@material/form-field": "15.0.0-canary.7f224ddd4.0", + "@material/icon-button": "15.0.0-canary.7f224ddd4.0", + "@material/image-list": "15.0.0-canary.7f224ddd4.0", + "@material/layout-grid": "15.0.0-canary.7f224ddd4.0", + "@material/line-ripple": "15.0.0-canary.7f224ddd4.0", + "@material/linear-progress": "15.0.0-canary.7f224ddd4.0", + "@material/list": "15.0.0-canary.7f224ddd4.0", + "@material/menu": "15.0.0-canary.7f224ddd4.0", + "@material/menu-surface": "15.0.0-canary.7f224ddd4.0", + "@material/notched-outline": "15.0.0-canary.7f224ddd4.0", + "@material/radio": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/segmented-button": "15.0.0-canary.7f224ddd4.0", + "@material/select": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/slider": "15.0.0-canary.7f224ddd4.0", + "@material/snackbar": "15.0.0-canary.7f224ddd4.0", + "@material/switch": "15.0.0-canary.7f224ddd4.0", + "@material/tab": "15.0.0-canary.7f224ddd4.0", + "@material/tab-bar": "15.0.0-canary.7f224ddd4.0", + "@material/tab-indicator": "15.0.0-canary.7f224ddd4.0", + "@material/tab-scroller": "15.0.0-canary.7f224ddd4.0", + "@material/textfield": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tooltip": "15.0.0-canary.7f224ddd4.0", + "@material/top-app-bar": "15.0.0-canary.7f224ddd4.0", + "@material/touch-target": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.3.0" + }, + "peerDependencies": { + "@angular/animations": "^17.0.0 || ^18.0.0", + "@angular/cdk": "17.2.1", + "@angular/common": "^17.0.0 || ^18.0.0", + "@angular/core": "^17.0.0 || ^18.0.0", + "@angular/forms": "^17.0.0 || ^18.0.0", + "@angular/platform-browser": "^17.0.0 || ^18.0.0", + "rxjs": "^6.5.3 || ^7.4.0" + } + }, + "node_modules/@angular/material/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/@angular/platform-browser": { "version": "17.2.3", "resolved": "https://registry.npmjs.org/@angular/platform-browser/-/platform-browser-17.2.3.tgz", @@ -4524,6 +4596,1096 @@ "node": ">= 0.4" } }, + "node_modules/@material/animation": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/animation/-/animation-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-1GSJaPKef+7HRuV+HusVZHps64cmZuOItDbt40tjJVaikcaZvwmHlcTxRIqzcRoCdt5ZKHh3NoO7GB9Khg4Jnw==", + "license": "MIT", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@material/animation/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@material/auto-init": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/auto-init/-/auto-init-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-t7ZGpRJ3ec0QDUO0nJu/SMgLW7qcuG2KqIsEYD1Ej8qhI2xpdR2ydSDQOkVEitXmKoGol1oq4nYSBjTlB65GqA==", + "license": "MIT", + "dependencies": { + "@material/base": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/auto-init/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@material/banner": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/banner/-/banner-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-g9wBUZzYBizyBcBQXTIafnRUUPi7efU9gPJfzeGgkynXiccP/vh5XMmH+PBxl5v+4MlP/d4cZ2NUYoAN7UTqSA==", + "license": "MIT", + "dependencies": { + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/button": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/banner/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@material/base": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/base/-/base-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-I9KQOKXpLfJkP8MqZyr8wZIzdPHrwPjFvGd9zSK91/vPyE4hzHRJc/0njsh9g8Lm9PRYLbifXX+719uTbHxx+A==", + "license": "MIT", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@material/base/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@material/button": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/button/-/button-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-BHB7iyHgRVH+JF16+iscR+Qaic+p7LU1FOLgP8KucRlpF9tTwIxQA6mJwGRi5gUtcG+vyCmzVS+hIQ6DqT/7BA==", + "license": "MIT", + "dependencies": { + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/touch-target": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/button/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@material/card": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/card/-/card-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-kt7y9/IWOtJTr3Z/AoWJT3ZLN7CLlzXhx2udCLP9ootZU2bfGK0lzNwmo80bv/pJfrY9ihQKCtuGTtNxUy+vIw==", + "license": "MIT", + "dependencies": { + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/card/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@material/checkbox": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/checkbox/-/checkbox-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-rURcrL5O1u6hzWR+dNgiQ/n89vk6tdmdP3mZgnxJx61q4I/k1yijKqNJSLrkXH7Rto3bM5NRKMOlgvMvVd7UMQ==", + "license": "MIT", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/touch-target": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/checkbox/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@material/chips": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/chips/-/chips-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-AYAivV3GSk/T/nRIpH27sOHFPaSMrE3L0WYbnb5Wa93FgY8a0fbsFYtSH2QmtwnzXveg+B1zGTt7/xIIcynKdQ==", + "license": "MIT", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/checkbox": "15.0.0-canary.7f224ddd4.0", + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/touch-target": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", + "safevalues": "^0.3.4", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/chips/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@material/circular-progress": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/circular-progress/-/circular-progress-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-DJrqCKb+LuGtjNvKl8XigvyK02y36GRkfhMUYTcJEi3PrOE00bwXtyj7ilhzEVshQiXg6AHGWXtf5UqwNrx3Ow==", + "license": "MIT", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/progress-indicator": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/circular-progress/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@material/data-table": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/data-table/-/data-table-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-/2WZsuBIq9z9RWYF5Jo6b7P6u0fwit+29/mN7rmAZ6akqUR54nXyNfoSNiyydMkzPlZZsep5KrSHododDhBZbA==", + "license": "MIT", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/checkbox": "15.0.0-canary.7f224ddd4.0", + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/icon-button": "15.0.0-canary.7f224ddd4.0", + "@material/linear-progress": "15.0.0-canary.7f224ddd4.0", + "@material/list": "15.0.0-canary.7f224ddd4.0", + "@material/menu": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/select": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/touch-target": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/data-table/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@material/density": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/density/-/density-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-o9EXmGKVpiQ6mHhyV3oDDzc78Ow3E7v8dlaOhgaDSXgmqaE8v5sIlLNa/LKSyUga83/fpGk3QViSGXotpQx0jA==", + "license": "MIT", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@material/density/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@material/dialog": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/dialog/-/dialog-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-u0XpTlv1JqWC/bQ3DavJ1JguofTelLT2wloj59l3/1b60jv42JQ6Am7jU3I8/SIUB1MKaW7dYocXjDWtWJakLA==", + "license": "MIT", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/button": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/icon-button": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/touch-target": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/dialog/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@material/dom": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/dom/-/dom-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-mQ1HT186GPQSkRg5S18i70typ5ZytfjL09R0gJ2Qg5/G+MLCGi7TAjZZSH65tuD/QGOjel4rDdWOTmYbPYV6HA==", + "license": "MIT", + "dependencies": { + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/dom/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@material/drawer": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/drawer/-/drawer-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-qyO0W0KBftfH8dlLR0gVAgv7ZHNvU8ae11Ao6zJif/YxcvK4+gph1z8AO4H410YmC2kZiwpSKyxM1iQCCzbb4g==", + "license": "MIT", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/list": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/drawer/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@material/elevation": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/elevation/-/elevation-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-tV6s4/pUBECedaI36Yj18KmRCk1vfue/JP/5yYRlFNnLMRVISePbZaKkn/BHXVf+26I3W879+XqIGlDVdmOoMA==", + "license": "MIT", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/elevation/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@material/fab": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/fab/-/fab-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-4h76QrzfZTcPdd+awDPZ4Q0YdSqsXQnS540TPtyXUJ/5G99V6VwGpjMPIxAsW0y+pmI9UkLL/srrMaJec+7r4Q==", + "license": "MIT", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/touch-target": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/fab/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@material/feature-targeting": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/feature-targeting/-/feature-targeting-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-SAjtxYh6YlKZriU83diDEQ7jNSP2MnxKsER0TvFeyG1vX/DWsUyYDOIJTOEa9K1N+fgJEBkNK8hY55QhQaspew==", + "license": "MIT", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@material/feature-targeting/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@material/floating-label": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/floating-label/-/floating-label-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-0KMo5ijjYaEHPiZ2pCVIcbaTS2LycvH9zEhEMKwPPGssBCX7iz5ffYQFk7e5yrQand1r3jnQQgYfHAwtykArnQ==", + "license": "MIT", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/floating-label/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@material/focus-ring": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/focus-ring/-/focus-ring-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-Jmg1nltq4J6S6A10EGMZnvufrvU3YTi+8R8ZD9lkSbun0Fm2TVdICQt/Auyi6An9zP66oQN6c31eqO6KfIPsDg==", + "license": "MIT", + "dependencies": { + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0" + } + }, + "node_modules/@material/form-field": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/form-field/-/form-field-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-fEPWgDQEPJ6WF7hNnIStxucHR9LE4DoDSMqCsGWS2Yu+NLZYLuCEecgR0UqQsl1EQdNRaFh8VH93KuxGd2hiPg==", + "license": "MIT", + "dependencies": { + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/form-field/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@material/icon-button": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/icon-button/-/icon-button-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-DcK7IL4ICY/DW+48YQZZs9g0U1kRaW0Wb0BxhvppDMYziHo/CTpFdle4gjyuTyRxPOdHQz5a97ru48Z9O4muTw==", + "license": "MIT", + "dependencies": { + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/touch-target": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/icon-button/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@material/image-list": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/image-list/-/image-list-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-voMjG2p80XbjL1B2lmF65zO5gEgJOVKClLdqh4wbYzYfwY/SR9c8eLvlYG7DLdFaFBl/7gGxD8TvvZ329HUFPw==", + "license": "MIT", + "dependencies": { + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/image-list/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@material/layout-grid": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/layout-grid/-/layout-grid-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-veDABLxMn2RmvfnUO2RUmC1OFfWr4cU+MrxKPoDD2hl3l3eDYv5fxws6r5T1JoSyXoaN+oEZpheS0+M9Ure8Pg==", + "license": "MIT", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@material/layout-grid/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@material/line-ripple": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/line-ripple/-/line-ripple-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-f60hVJhIU6I3/17Tqqzch1emUKEcfVVgHVqADbU14JD+oEIz429ZX9ksZ3VChoU3+eejFl+jVdZMLE/LrAuwpg==", + "license": "MIT", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/line-ripple/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@material/linear-progress": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/linear-progress/-/linear-progress-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-pRDEwPQielDiC9Sc5XhCXrGxP8wWOnAO8sQlMebfBYHYqy5hhiIzibezS8CSaW4MFQFyXmCmpmqWlbqGYRmiyg==", + "license": "MIT", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/progress-indicator": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/linear-progress/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@material/list": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/list/-/list-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-Is0NV91sJlXF5pOebYAtWLF4wU2MJDbYqztML/zQNENkQxDOvEXu3nWNb3YScMIYJJXvARO0Liur5K4yPagS1Q==", + "license": "MIT", + "dependencies": { + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/list/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@material/menu": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/menu/-/menu-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-D11QU1dXqLbh5X1zKlEhS3QWh0b5BPNXlafc5MXfkdJHhOiieb7LC9hMJhbrHtj24FadJ7evaFW/T2ugJbJNnQ==", + "license": "MIT", + "dependencies": { + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/list": "15.0.0-canary.7f224ddd4.0", + "@material/menu-surface": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/menu-surface": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/menu-surface/-/menu-surface-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-7RZHvw0gbwppaAJ/Oh5SWmfAKJ62aw1IMB3+3MRwsb5PLoV666wInYa+zJfE4i7qBeOn904xqT2Nko5hY0ssrg==", + "license": "MIT", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/menu-surface/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@material/menu/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@material/notched-outline": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/notched-outline/-/notched-outline-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-Yg2usuKB2DKlKIBISbie9BFsOVuffF71xjbxPbybvqemxqUBd+bD5/t6H1fLE+F8/NCu5JMigho4ewUU+0RCiw==", + "license": "MIT", + "dependencies": { + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/floating-label": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/notched-outline/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@material/progress-indicator": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/progress-indicator/-/progress-indicator-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-UPbDjE5CqT+SqTs0mNFG6uFEw7wBlgYmh+noSkQ6ty/EURm8lF125dmi4dv4kW0+octonMXqkGtAoZwLIHKf/w==", + "license": "MIT", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/@material/progress-indicator/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@material/radio": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/radio/-/radio-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-wR1X0Sr0KmQLu6+YOFKAI84G3L6psqd7Kys5kfb8WKBM36zxO5HQXC5nJm/Y0rdn22ixzsIz2GBo0MNU4V4k1A==", + "license": "MIT", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/touch-target": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/radio/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@material/ripple": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/ripple/-/ripple-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-JqOsWM1f4aGdotP0rh1vZlPZTg6lZgh39FIYHFMfOwfhR+LAikUJ+37ciqZuewgzXB6iiRO6a8aUH6HR5SJYPg==", + "license": "MIT", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/ripple/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@material/rtl": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/rtl/-/rtl-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-UVf14qAtmPiaaZjuJtmN36HETyoKWmsZM/qn1L5ciR2URb8O035dFWnz4ZWFMmAYBno/L7JiZaCkPurv2ZNrGA==", + "license": "MIT", + "dependencies": { + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/rtl/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@material/segmented-button": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/segmented-button/-/segmented-button-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-LCnVRUSAhELTKI/9hSvyvIvQIpPpqF29BV+O9yM4WoNNmNWqTulvuiv7grHZl6Z+kJuxSg4BGbsPxxb9dXozPg==", + "license": "MIT", + "dependencies": { + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/touch-target": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/segmented-button/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@material/select": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/select/-/select-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-WioZtQEXRpglum0cMSzSqocnhsGRr+ZIhvKb3FlaNrTaK8H3Y4QA7rVjv3emRtrLOOjaT6/RiIaUMTo9AGzWQQ==", + "license": "MIT", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/floating-label": "15.0.0-canary.7f224ddd4.0", + "@material/line-ripple": "15.0.0-canary.7f224ddd4.0", + "@material/list": "15.0.0-canary.7f224ddd4.0", + "@material/menu": "15.0.0-canary.7f224ddd4.0", + "@material/menu-surface": "15.0.0-canary.7f224ddd4.0", + "@material/notched-outline": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/select/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@material/shape": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/shape/-/shape-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-8z8l1W3+cymObunJoRhwFPKZ+FyECfJ4MJykNiaZq7XJFZkV6xNmqAVrrbQj93FtLsECn9g4PjjIomguVn/OEw==", + "license": "MIT", + "dependencies": { + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/shape/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@material/slider": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/slider/-/slider-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-QU/WSaSWlLKQRqOhJrPgm29wqvvzRusMqwAcrCh1JTrCl+xwJ43q5WLDfjYhubeKtrEEgGu9tekkAiYfMG7EBw==", + "license": "MIT", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/slider/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@material/snackbar": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/snackbar/-/snackbar-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-sm7EbVKddaXpT/aXAYBdPoN0k8yeg9+dprgBUkrdqGzWJAeCkxb4fv2B3He88YiCtvkTz2KLY4CThPQBSEsMFQ==", + "license": "MIT", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/button": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/icon-button": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/snackbar/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@material/switch": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/switch/-/switch-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-lEDJfRvkVyyeHWIBfoxYjJVl+WlEAE2kZ/+6OqB1FW0OV8ftTODZGhHRSzjVBA1/p4FPuhAtKtoK9jTpa4AZjA==", + "license": "MIT", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "safevalues": "^0.3.4", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/switch/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@material/tab": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/tab/-/tab-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-E1xGACImyCLurhnizyOTCgOiVezce4HlBFAI6YhJo/AyVwjN2Dtas4ZLQMvvWWqpyhITNkeYdOchwCC1mrz3AQ==", + "license": "MIT", + "dependencies": { + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/focus-ring": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/tab-indicator": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/tab-bar": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/tab-bar/-/tab-bar-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-p1Asb2NzrcECvAQU3b2SYrpyJGyJLQWR+nXTYzDKE8WOpLIRCXap2audNqD7fvN/A20UJ1J8U01ptrvCkwJ4eA==", + "license": "MIT", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/tab": "15.0.0-canary.7f224ddd4.0", + "@material/tab-indicator": "15.0.0-canary.7f224ddd4.0", + "@material/tab-scroller": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/tab-bar/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@material/tab-indicator": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/tab-indicator/-/tab-indicator-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-h9Td3MPqbs33spcPS7ecByRHraYgU4tNCZpZzZXw31RypjKvISDv/PS5wcA4RmWqNGih78T7xg4QIGsZg4Pk4w==", + "license": "MIT", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/tab-indicator/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@material/tab-scroller": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/tab-scroller/-/tab-scroller-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-LFeYNjQpdXecwECd8UaqHYbhscDCwhGln5Yh+3ctvcEgvmDPNjhKn/DL3sWprWvG8NAhP6sHMrsGhQFVdCWtTg==", + "license": "MIT", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/tab": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/tab-scroller/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@material/tab/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@material/textfield": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/textfield/-/textfield-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-AExmFvgE5nNF0UA4l2cSzPghtxSUQeeoyRjFLHLy+oAaE4eKZFrSy0zEpqPeWPQpEMDZk+6Y+6T3cOFYBeSvsw==", + "license": "MIT", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/density": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/floating-label": "15.0.0-canary.7f224ddd4.0", + "@material/line-ripple": "15.0.0-canary.7f224ddd4.0", + "@material/notched-outline": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/textfield/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@material/theme": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/theme/-/theme-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-hs45hJoE9yVnoVOcsN1jklyOa51U4lzWsEnQEuJTPOk2+0HqCQ0yv/q0InpSnm2i69fNSyZC60+8HADZGF8ugQ==", + "license": "MIT", + "dependencies": { + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/theme/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@material/tokens": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/tokens/-/tokens-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-r9TDoicmcT7FhUXC4eYMFnt9TZsz0G8T3wXvkKncLppYvZ517gPyD/1+yhuGfGOxAzxTrM66S/oEc1fFE2q4hw==", + "license": "MIT", + "dependencies": { + "@material/elevation": "15.0.0-canary.7f224ddd4.0" + } + }, + "node_modules/@material/tooltip": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/tooltip/-/tooltip-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-8qNk3pmPLTnam3XYC1sZuplQXW9xLn4Z4MI3D+U17Q7pfNZfoOugGr+d2cLA9yWAEjVJYB0mj8Yu86+udo4N9w==", + "license": "MIT", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/button": "15.0.0-canary.7f224ddd4.0", + "@material/dom": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/tokens": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", + "safevalues": "^0.3.4", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/tooltip/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@material/top-app-bar": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/top-app-bar/-/top-app-bar-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-SARR5/ClYT4CLe9qAXakbr0i0cMY0V3V4pe3ElIJPfL2Z2c4wGR1mTR8m2LxU1MfGKK8aRoUdtfKaxWejp+eNA==", + "license": "MIT", + "dependencies": { + "@material/animation": "15.0.0-canary.7f224ddd4.0", + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/elevation": "15.0.0-canary.7f224ddd4.0", + "@material/ripple": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/shape": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "@material/typography": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/top-app-bar/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@material/touch-target": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/touch-target/-/touch-target-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-BJo/wFKHPYLGsRaIpd7vsQwKr02LtO2e89Psv0on/p0OephlNIgeB9dD9W+bQmaeZsZ6liKSKRl6wJWDiK71PA==", + "license": "MIT", + "dependencies": { + "@material/base": "15.0.0-canary.7f224ddd4.0", + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/rtl": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/touch-target/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/@material/typography": { + "version": "15.0.0-canary.7f224ddd4.0", + "resolved": "https://registry.npmjs.org/@material/typography/-/typography-15.0.0-canary.7f224ddd4.0.tgz", + "integrity": "sha512-kBaZeCGD50iq1DeRRH5OM5Jl7Gdk+/NOfKArkY4ksBZvJiStJ7ACAhpvb8MEGm4s3jvDInQFLsDq3hL+SA79sQ==", + "license": "MIT", + "dependencies": { + "@material/feature-targeting": "15.0.0-canary.7f224ddd4.0", + "@material/theme": "15.0.0-canary.7f224ddd4.0", + "tslib": "^2.1.0" + } + }, + "node_modules/@material/typography/node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, "node_modules/@ngtools/webpack": { "version": "17.2.2", "resolved": "https://registry.npmjs.org/@ngtools/webpack/-/webpack-17.2.2.tgz", @@ -14925,6 +16087,12 @@ "version": "2.1.2", "license": "MIT" }, + "node_modules/safevalues": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/safevalues/-/safevalues-0.3.4.tgz", + "integrity": "sha512-LRneZZRXNgjzwG4bDQdOTSbze3fHm1EAKN/8bePxnlEZiBmkYEDggaHbuvHI9/hoqHbGfsEA7tWS9GhYHZBBsw==", + "license": "Apache-2.0" + }, "node_modules/sass": { "version": "1.64.1", "resolved": "https://registry.npmjs.org/sass/-/sass-1.64.1.tgz", diff --git a/package.json b/package.json index dfc71054..50d301a3 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "@angular/core": "^17.2.3", "@angular/elements": "^17.2.3", "@angular/forms": "^17.2.3", + "@angular/material": "^17.2.1", "@angular/platform-browser": "^17.2.3", "@angular/platform-browser-dynamic": "^17.2.3", "@angular/router": "^17.2.3", @@ -59,6 +60,16 @@ "ngx-markdown": "^17.1.1", "ngx-plyr": "^4.0.1", "prismjs": "1.29.0", + "rete": "^2.0.3", + "rete-angular-plugin": "^2.1.1", + "rete-area-plugin": "^2.0.4", + "rete-auto-arrange-plugin": "^2.0.1", + "rete-connection-path-plugin": "^2.0.3", + "rete-connection-plugin": "^2.0.1", + "rete-context-menu-plugin": "^2.0.3", + "rete-engine": "^2.0.1", + "rete-readonly-plugin": "^2.0.1", + "rete-render-utils": "^2.0.2", "rxjs": "^7.8.1", "rxjs-compat": "^6.5.5", "sass": "^1.26.3", @@ -67,17 +78,7 @@ "ts-helpers": "^1.1.2", "ts-md5": "^1.2.7", "uuid": "^9.0.0", - "zone.js": "~0.14.4", - "rete": "^2.0.3", - "rete-angular-plugin": "^2.1.1", - "rete-connection-plugin": "^2.0.1", - "rete-auto-arrange-plugin": "^2.0.1", - "rete-area-plugin": "^2.0.4", - "rete-render-utils": "^2.0.2", - "rete-readonly-plugin": "^2.0.1", - "rete-connection-path-plugin": "^2.0.3", - "rete-context-menu-plugin": "^2.0.3", - "rete-engine": "^2.0.1" + "zone.js": "~0.14.4" }, "devDependencies": { "@angular-builders/custom-webpack": "^17.0.1", @@ -86,6 +87,7 @@ "@angular/compiler-cli": "^17.2.3", "@angular/language-service": "^17.2.3", "@ngtools/webpack": "^17.2.2", + "@types/d3-shape": "^3.1.6", "@types/hammerjs": "^2.0.36", "@types/jasmine": "~3.6.0", "@types/jasminewd2": "^2.0.8", @@ -108,11 +110,10 @@ "tslint": "~6.1.0", "typescript": "^5.3.3", "webpack": "^5.54.0", - "webpack-bundle-analyzer": "^4.9.0", - "@types/d3-shape": "^3.1.6" + "webpack-bundle-analyzer": "^4.9.0" }, "engines": { "node": ">= 16.3.0", "npm": ">= 5.6.0" } -} +} \ No newline at end of file diff --git a/src/app/components/table-selection-dialog/table-selection-dialog.component.html b/src/app/components/table-selection-dialog/table-selection-dialog.component.html new file mode 100644 index 00000000..584c532e --- /dev/null +++ b/src/app/components/table-selection-dialog/table-selection-dialog.component.html @@ -0,0 +1,38 @@ +

Tabellen auswählen

+ + +
+

📦 {{ db.name }}

+
+

📁 {{ schema.name }}

+ +
    +
  • + + +
      +
    • + +
    • +
    +
  • i +
+
+
+
+ +

✅ Ausgewählte Tabellen:

+ + + + + + diff --git a/src/app/components/table-selection-dialog/table-selection-dialog.component.scss b/src/app/components/table-selection-dialog/table-selection-dialog.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/components/table-selection-dialog/table-selection-dialog.component.spec.ts b/src/app/components/table-selection-dialog/table-selection-dialog.component.spec.ts new file mode 100644 index 00000000..5558c4a5 --- /dev/null +++ b/src/app/components/table-selection-dialog/table-selection-dialog.component.spec.ts @@ -0,0 +1,27 @@ +import { ComponentFixture, TestBed } from '@angular/core/testing'; + +import { TableSelectionDialogComponent } from './table-selection-dialog.component'; + +// @ts-ignore +describe('TableSelectionDialogComponent', () => { + let component: TableSelectionDialogComponent; + let fixture: ComponentFixture; + + // @ts-ignore + beforeEach(async () => { + await TestBed.configureTestingModule({ + imports: [TableSelectionDialogComponent] + }) + .compileComponents(); + + fixture = TestBed.createComponent(TableSelectionDialogComponent); + component = fixture.componentInstance; + fixture.detectChanges(); + }); + + // @ts-ignore + it('should create', () => { + // @ts-ignore + expect(component).toBeTruthy(); + }); +}); diff --git a/src/app/components/table-selection-dialog/table-selection-dialog.component.ts b/src/app/components/table-selection-dialog/table-selection-dialog.component.ts new file mode 100644 index 00000000..b669ed74 --- /dev/null +++ b/src/app/components/table-selection-dialog/table-selection-dialog.component.ts @@ -0,0 +1,46 @@ +import {Component, Inject} from '@angular/core'; +import {MAT_DIALOG_DATA, MatDialogActions, MatDialogContent, MatDialogRef} from '@angular/material/dialog'; +import {DatabaseInfo, TableInfo} from '../../models/databaseInfo.model'; +import {FormsModule} from '@angular/forms'; +import {NgForOf} from '@angular/common'; +import {MatButton} from '@angular/material/button'; + +@Component({ + selector: 'app-table-selection-dialog', + imports: [ + MatDialogContent, + FormsModule, + MatDialogActions, + NgForOf, + MatButton, + MatButton + ], + templateUrl: './table-selection-dialog.component.html', + standalone: true, + styleUrl: './table-selection-dialog.component.scss' +}) +export class TableSelectionDialogComponent { + constructor( + @Inject(MAT_DIALOG_DATA) public data: DatabaseInfo[], + private dialogRef: MatDialogRef + ) {} + + close(): void { + this.dialogRef.close(); + } + + + getSelectedTables(): string[] { + const selected: string[] = []; + for (const db of this.data) { + for (const schema of db.schemas) { + for (const table of schema.tables) { + if (table.selected) { + selected.push(`${db.name}.${schema.name}.${table.name}`); + } + } + } + } + return selected; + } +} diff --git a/src/app/models/databaseInfo.model.ts b/src/app/models/databaseInfo.model.ts new file mode 100644 index 00000000..2ef16528 --- /dev/null +++ b/src/app/models/databaseInfo.model.ts @@ -0,0 +1,20 @@ +export interface DatabaseInfo { + name: string; + schemas: SchemaInfo[]; +} + +export interface TableInfo { + name: string; + attributes: AttributeInfo[]; + selected?: boolean; +} + +export interface SchemaInfo { + name: string; + tables: TableInfo[]; +} + +export interface AttributeInfo { + name: String; + type: String; +} diff --git a/src/app/services/schema-discovery.service.ts b/src/app/services/schema-discovery.service.ts new file mode 100644 index 00000000..9afc7836 --- /dev/null +++ b/src/app/services/schema-discovery.service.ts @@ -0,0 +1,63 @@ +import {Component, Inject, Injectable} from '@angular/core'; +import {HttpClient, HttpParams} from '@angular/common/http'; +import {CommonModule} from '@angular/common'; +import {HttpClientModule} from '@angular/common/http'; +import { RouterOutlet } from '@angular/router'; +import {DatabaseInfo, TableInfo} from '../models/databaseInfo.model'; +import {FormsModule} from '@angular/forms'; +import {TableSelectionDialogComponent} from '../components/table-selection-dialog/table-selection-dialog.component'; +import {MatDialog} from '@angular/material/dialog'; + +@Injectable({ + providedIn: 'root' +}) +export class SchemaDiscoveryService { + title = 'schemaui'; + message = ''; + databaseList: DatabaseInfo[] = []; + + constructor(private http: HttpClient, private dialog: MatDialog) { + } + + makeRequest() { + this.message = 'Button geklickt !!!'; + } + + + sendRequest(): void { + this.http.post('http://127.0.0.1:7659/confirm', {}) + .subscribe({ + next: (response) => { + this.databaseList = response; + console.log('Halter Datei: ', this.databaseList); + for (const db of response) { + this.message += `Datenbank: ${db.name}\n`; + for (const schema of db.schemas) { + this.message += `Schema: ${schema}\n`; + } + } + alert('Nachricht angekommen.'); + }, + error: (err) => { + console.log('Fehler beim Aufrufen der Daten:', err); + alert('Nachricht nicht angekommen !!!'); + } + }); + } + + openTableDialog(): void { + this.http.post('http://127.0.0.1:7659/confirm', {}) + .subscribe({ + next: (data) => { + this.dialog.open(TableSelectionDialogComponent, { + width: '700px', + data: data + }); + }, + error: (err) => { + console.error('Fehler beim Abrufen der Datenbankstruktur:', err); + alert('Nachricht nicht angekommen !!!'); + } + }); + } +} diff --git a/src/app/views/adapters/adapters.component.html b/src/app/views/adapters/adapters.component.html index c122415e..181fb17b 100644 --- a/src/app/views/adapters/adapters.component.html +++ b/src/app/views/adapters/adapters.component.html @@ -35,7 +35,8 @@

Adapters

{{ adapter.adapterName }} {{ adapter.description }} - + + diff --git a/src/app/views/adapters/adapters.component.ts b/src/app/views/adapters/adapters.component.ts index 4678edc7..54f9eedf 100644 --- a/src/app/views/adapters/adapters.component.ts +++ b/src/app/views/adapters/adapters.component.ts @@ -32,6 +32,7 @@ import { DeployMode } from '../../models/catalog.model'; import {LeftSidebarService} from '../../components/left-sidebar/left-sidebar.service'; +import {SchemaDiscoveryService} from '../../services/schema-discovery.service'; @Component({ selector: 'app-adapters', @@ -48,7 +49,8 @@ export class AdaptersComponent implements OnInit, OnDestroy { private readonly _catalog = inject(CatalogService); private readonly _left = inject(LeftSidebarService); - constructor(private injector: Injector) { + constructor(private injector: Injector, + private schemaDiscoveryService: SchemaDiscoveryService) { this.availableAdapters = computed(() => { return this._catalog.getAdapterTemplates().filter(a => a.adapterType === this.getMatchingAdapterType()); }); @@ -514,6 +516,10 @@ export class AdaptersComponent implements OnInit, OnDestroy { } } + openTableDialog(): void { + this.schemaDiscoveryService.openTableDialog(); + } + private validateControl(form: UntypedFormControl, key: string) { if ((key === 'port' || key === 'instanceId') && this.activeMode() === DeployMode.DOCKER) { if (this.editingAvailableAdapterForm.valid) { From 6bbb47dff728d048bf5063bd303d6ab5cef0967d Mon Sep 17 00:00:00 2001 From: romanost03 Date: Wed, 2 Apr 2025 11:43:47 +0200 Subject: [PATCH 02/33] Change code for table-selection-dialog such that it opens a new window instead of a popup. --- src/app/app-routing.module.ts | 8 +++ .../table-selection-dialog.component.html | 18 ++---- .../table-selection-dialog.component.ts | 64 ++++++++++--------- src/app/services/schema-discovery.service.ts | 8 +-- .../views/adapters/adapters.component.html | 2 +- 5 files changed, 53 insertions(+), 47 deletions(-) diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 9d471aad..6f4c4116 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -4,6 +4,7 @@ import {DefaultLayoutComponent} from './containers/default-layout'; import {P404Component} from './views/error/404.component'; import {P500Component} from './views/error/500.component'; import {LoginComponent} from './views/login/login.component'; +import {TableSelectionDialogComponent} from './components/table-selection-dialog/table-selection-dialog.component'; export const routes: Routes = [ @@ -28,6 +29,13 @@ export const routes: Routes = [ title: 'Login Page' } }, + { + path: 'table-selection', + component: TableSelectionDialogComponent, + data: { + title: 'Table Selection' + } + }, { path: '', pathMatch: 'full', diff --git a/src/app/components/table-selection-dialog/table-selection-dialog.component.html b/src/app/components/table-selection-dialog/table-selection-dialog.component.html index 584c532e..de14ed98 100644 --- a/src/app/components/table-selection-dialog/table-selection-dialog.component.html +++ b/src/app/components/table-selection-dialog/table-selection-dialog.component.html @@ -1,6 +1,6 @@ -

Tabellen auswählen

+

Tabellen auswählen

+ -

📦 {{ db.name }}

@@ -9,23 +9,19 @@

📁 {{ schema.name }}

    • -
    -
  • i +
-

✅ Ausgewählte Tabellen:

    @@ -33,6 +29,6 @@

    ✅ Ausgewählte Tabellen:

- - - +
+ +
diff --git a/src/app/components/table-selection-dialog/table-selection-dialog.component.ts b/src/app/components/table-selection-dialog/table-selection-dialog.component.ts index b669ed74..ebc51204 100644 --- a/src/app/components/table-selection-dialog/table-selection-dialog.component.ts +++ b/src/app/components/table-selection-dialog/table-selection-dialog.component.ts @@ -1,46 +1,48 @@ import {Component, Inject} from '@angular/core'; -import {MAT_DIALOG_DATA, MatDialogActions, MatDialogContent, MatDialogRef} from '@angular/material/dialog'; import {DatabaseInfo, TableInfo} from '../../models/databaseInfo.model'; import {FormsModule} from '@angular/forms'; import {NgForOf} from '@angular/common'; import {MatButton} from '@angular/material/button'; @Component({ - selector: 'app-table-selection-dialog', - imports: [ - MatDialogContent, - FormsModule, - MatDialogActions, - NgForOf, - MatButton, - MatButton - ], - templateUrl: './table-selection-dialog.component.html', - standalone: true, - styleUrl: './table-selection-dialog.component.scss' + selector: 'app-table-selection-dialog', + imports: [ + FormsModule, + NgForOf + ], + templateUrl: './table-selection-dialog.component.html', + standalone: true, + styleUrl: './table-selection-dialog.component.scss' }) export class TableSelectionDialogComponent { - constructor( - @Inject(MAT_DIALOG_DATA) public data: DatabaseInfo[], - private dialogRef: MatDialogRef - ) {} + constructor() { + } + + data: DatabaseInfo[] = []; + + ngOnInit() { + const raw = localStorage.getItem('databaseInfo'); + if (raw) { + this.data = JSON.parse(raw); - close(): void { - this.dialogRef.close(); - } + } + } + close(): void { + window.close(); + } - getSelectedTables(): string[] { - const selected: string[] = []; - for (const db of this.data) { - for (const schema of db.schemas) { - for (const table of schema.tables) { - if (table.selected) { - selected.push(`${db.name}.${schema.name}.${table.name}`); - } + getSelectedTables(): string[] { + const selected: string[] = []; + for (const db of this.data) { + for (const schema of db.schemas) { + for (const table of schema.tables) { + if (table.selected) { + selected.push(`${db.name}.${schema.name}.${table.name}`); + } + } + } } - } + return selected; } - return selected; - } } diff --git a/src/app/services/schema-discovery.service.ts b/src/app/services/schema-discovery.service.ts index 9afc7836..a5cbd5d1 100644 --- a/src/app/services/schema-discovery.service.ts +++ b/src/app/services/schema-discovery.service.ts @@ -49,10 +49,10 @@ export class SchemaDiscoveryService { this.http.post('http://127.0.0.1:7659/confirm', {}) .subscribe({ next: (data) => { - this.dialog.open(TableSelectionDialogComponent, { - width: '700px', - data: data - }); + alert('Nachricht angekommen.'); + console.log('Dateien: ', data); + localStorage.setItem('databaseInfo', JSON.stringify(data)); + window.open('/#/table-selection', 'popup', 'width=1000, height=700'); }, error: (err) => { console.error('Fehler beim Abrufen der Datenbankstruktur:', err); diff --git a/src/app/views/adapters/adapters.component.html b/src/app/views/adapters/adapters.component.html index 181fb17b..fd9ff1bd 100644 --- a/src/app/views/adapters/adapters.component.html +++ b/src/app/views/adapters/adapters.component.html @@ -36,7 +36,7 @@

Adapters

{{ adapter.description }} - + From 67ec616b69228a7b947c5df65ed3fd10136ec22d Mon Sep 17 00:00:00 2001 From: romanost03 Date: Sun, 13 Apr 2025 18:26:34 +0200 Subject: [PATCH 03/33] Code change such that a small preview is given in the table-selection-dialog.component.html and columns are selectable instead of tables. --- .../table-selection-dialog.component.html | 47 +++++++++++-------- .../table-selection-dialog.component.ts | 41 +++++++++++----- src/app/models/databaseInfo.model.ts | 3 +- .../views/adapters/adapters.component.html | 5 +- 4 files changed, 62 insertions(+), 34 deletions(-) diff --git a/src/app/components/table-selection-dialog/table-selection-dialog.component.html b/src/app/components/table-selection-dialog/table-selection-dialog.component.html index de14ed98..bd435058 100644 --- a/src/app/components/table-selection-dialog/table-selection-dialog.component.html +++ b/src/app/components/table-selection-dialog/table-selection-dialog.component.html @@ -1,34 +1,41 @@

Tabellen auswählen

-
+

📦 {{ db.name }}

-

📁 {{ schema.name }}

- -
    -
  • - - -
      -
    • - 🔹 {{ attr.name }} : {{ attr.type }} -
    • -
    -
  • -
+

📁 {{ schema.name }}

+ +
    +
  • + + +
      +
    • + +
        +
      • + → {{ val }} +
      • +
      + +
    • +
    +
-
+

✅ Ausgewählte Tabellen:

    -
  • {{ table }}
  • +
  • {{ attr }}
- +
diff --git a/src/app/components/table-selection-dialog/table-selection-dialog.component.ts b/src/app/components/table-selection-dialog/table-selection-dialog.component.ts index ebc51204..3600b334 100644 --- a/src/app/components/table-selection-dialog/table-selection-dialog.component.ts +++ b/src/app/components/table-selection-dialog/table-selection-dialog.component.ts @@ -1,14 +1,14 @@ import {Component, Inject} from '@angular/core'; import {DatabaseInfo, TableInfo} from '../../models/databaseInfo.model'; import {FormsModule} from '@angular/forms'; -import {NgForOf} from '@angular/common'; -import {MatButton} from '@angular/material/button'; +import {NgForOf, NgIf} from '@angular/common'; @Component({ selector: 'app-table-selection-dialog', imports: [ FormsModule, - NgForOf + NgForOf, + NgIf ], templateUrl: './table-selection-dialog.component.html', standalone: true, @@ -20,29 +20,48 @@ export class TableSelectionDialogComponent { data: DatabaseInfo[] = []; - ngOnInit() { - const raw = localStorage.getItem('databaseInfo'); + + close(): void { + window.close(); + } + + ngOnInit(): void { + const raw: string = localStorage.getItem('databaseInfo'); if (raw) { this.data = JSON.parse(raw); + // 🔁 sampleValues → values mappen + for (const db of this.data) { + for (const schema of db.schemas) { + for (const table of schema.tables) { + for (const attr of table.attributes) { + if (attr.sampleValues) { + attr.sampleValues = attr.sampleValues; + } + } + } + } + } } } - close(): void { - window.close(); - } - getSelectedTables(): string[] { + getSelectedAttributes(): string[] { const selected: string[] = []; + for (const db of this.data) { for (const schema of db.schemas) { for (const table of schema.tables) { - if (table.selected) { - selected.push(`${db.name}.${schema.name}.${table.name}`); + for (const attr of table.attributes) { + if (attr.selected) { + selected.push(`${db.name}.${schema.name}.${table.name}.${attr.name}`); + } } } } } + return selected; } + } diff --git a/src/app/models/databaseInfo.model.ts b/src/app/models/databaseInfo.model.ts index 2ef16528..f2e6182e 100644 --- a/src/app/models/databaseInfo.model.ts +++ b/src/app/models/databaseInfo.model.ts @@ -6,7 +6,6 @@ export interface DatabaseInfo { export interface TableInfo { name: string; attributes: AttributeInfo[]; - selected?: boolean; } export interface SchemaInfo { @@ -17,4 +16,6 @@ export interface SchemaInfo { export interface AttributeInfo { name: String; type: String; + sampleValues?: string[]; + selected?: boolean; } diff --git a/src/app/views/adapters/adapters.component.html b/src/app/views/adapters/adapters.component.html index fd9ff1bd..ef3f33b9 100644 --- a/src/app/views/adapters/adapters.component.html +++ b/src/app/views/adapters/adapters.component.html @@ -35,8 +35,8 @@

Adapters

{{ adapter.adapterName }} {{ adapter.description }} - - + + @@ -274,6 +274,7 @@

{{ activeMode() ? "Settings" : "Deployment Mode" }}

[disabled]="!editingAdapterForm?.valid"> Save + diff --git a/src/app/components/table-selection-dialog/table-selection-dialog.component.ts b/src/app/components/table-selection-dialog/table-selection-dialog.component.ts index 3600b334..d3931c36 100644 --- a/src/app/components/table-selection-dialog/table-selection-dialog.component.ts +++ b/src/app/components/table-selection-dialog/table-selection-dialog.component.ts @@ -1,7 +1,9 @@ -import {Component, Inject} from '@angular/core'; +import {Component, inject, Inject} from '@angular/core'; import {DatabaseInfo, TableInfo} from '../../models/databaseInfo.model'; import {FormsModule} from '@angular/forms'; import {NgForOf, NgIf} from '@angular/common'; +import {CrudService} from '../../services/crud.service'; +import {PreviewRequest} from '../../models/ui-request.model'; @Component({ selector: 'app-table-selection-dialog', @@ -15,6 +17,7 @@ import {NgForOf, NgIf} from '@angular/common'; styleUrl: './table-selection-dialog.component.scss' }) export class TableSelectionDialogComponent { + public readonly _crud = inject(CrudService); constructor() { } @@ -26,7 +29,7 @@ export class TableSelectionDialogComponent { } ngOnInit(): void { - const raw: string = localStorage.getItem('databaseInfo'); + /*const raw: string = localStorage.getItem('databaseInfo'); if (raw) { this.data = JSON.parse(raw); @@ -42,11 +45,11 @@ export class TableSelectionDialogComponent { } } } - } + }*/ } - getSelectedAttributes(): string[] { + /*getSelectedAttributes(): string[] { const selected: string[] = []; for (const db of this.data) { @@ -62,6 +65,6 @@ export class TableSelectionDialogComponent { } return selected; - } + }*/ } diff --git a/src/app/models/ui-request.model.ts b/src/app/models/ui-request.model.ts index 1c0717c2..6fac894f 100644 --- a/src/app/models/ui-request.model.ts +++ b/src/app/models/ui-request.model.ts @@ -210,6 +210,21 @@ export class EditCollectionRequest { } } +/** + * Request for dealing with the data preview while deploying an adapter. + */ +export class PreviewRequest extends RequestModel{ + type = 'PreviewRequest'; + source: string; + payload: string; + + constructor(source: string = '', payload: string = '') { + super(); + this.source = source; + this.payload = payload; + } +} + /** * Request to drop or create a constraint of a table */ diff --git a/src/app/services/crud.service.ts b/src/app/services/crud.service.ts index 7c5365a8..1261361a 100644 --- a/src/app/services/crud.service.ts +++ b/src/app/services/crud.service.ts @@ -5,7 +5,25 @@ import {EntityMeta, IndexModel, ModifyPartitionRequest, PartitionFunctionModel, RelationalResult } from '../components/data-view/models/result-set.model'; import {webSocket} from 'rxjs/webSocket'; -import {ColumnRequest, ConstraintRequest, DataModel, DeleteRequest, EditCollectionRequest, EditTableRequest, EntityRequest, ExploreTable, GraphRequest, MaterializedRequest, Method, MonitoringRequest, Namespace, PolyAlgRequest, QueryRequest, StatisticRequest} from '../models/ui-request.model'; +import { + ColumnRequest, + ConstraintRequest, + DataModel, + DeleteRequest, + EditCollectionRequest, + EditTableRequest, + EntityRequest, + ExploreTable, + GraphRequest, + MaterializedRequest, + Method, + MonitoringRequest, + Namespace, + PolyAlgRequest, + PreviewRequest, + QueryRequest, + StatisticRequest +} from '../models/ui-request.model'; import {AutoDockerResult, AutoDockerStatus, CreateDockerResponse, DockerInstanceInfo, DockerSettings, @@ -257,6 +275,14 @@ export class CrudService { return this._http.post(`${this.httpUrl}/dropConstraint`, request, this.httpOptions); } + /** + * Get the preview for an adapter. + */ + previewTable(request: PreviewRequest) { + return this._http.post(`${this.httpUrl}/previewTable`, request, this.httpOptions); + alert('Methode wird aufgerufen'); + } + /** * Add a primary key to a table */ diff --git a/src/app/services/schema-discovery.service.ts b/src/app/services/schema-discovery.service.ts index a5cbd5d1..f49b04a6 100644 --- a/src/app/services/schema-discovery.service.ts +++ b/src/app/services/schema-discovery.service.ts @@ -7,6 +7,8 @@ import {DatabaseInfo, TableInfo} from '../models/databaseInfo.model'; import {FormsModule} from '@angular/forms'; import {TableSelectionDialogComponent} from '../components/table-selection-dialog/table-selection-dialog.component'; import {MatDialog} from '@angular/material/dialog'; +import {PreviewRequest} from '../models/ui-request.model'; +import {WebSocket} from './webSocket'; @Injectable({ providedIn: 'root' @@ -23,29 +25,7 @@ export class SchemaDiscoveryService { this.message = 'Button geklickt !!!'; } - - sendRequest(): void { - this.http.post('http://127.0.0.1:7659/confirm', {}) - .subscribe({ - next: (response) => { - this.databaseList = response; - console.log('Halter Datei: ', this.databaseList); - for (const db of response) { - this.message += `Datenbank: ${db.name}\n`; - for (const schema of db.schemas) { - this.message += `Schema: ${schema}\n`; - } - } - alert('Nachricht angekommen.'); - }, - error: (err) => { - console.log('Fehler beim Aufrufen der Daten:', err); - alert('Nachricht nicht angekommen !!!'); - } - }); - } - - openTableDialog(): void { + openTableDialogs(): void { this.http.post('http://127.0.0.1:7659/confirm', {}) .subscribe({ next: (data) => { @@ -60,4 +40,8 @@ export class SchemaDiscoveryService { } }); } + + openTableDialog(): void { + + } } diff --git a/src/app/views/adapters/adapters.component.html b/src/app/views/adapters/adapters.component.html index ef3f33b9..c48af6ee 100644 --- a/src/app/views/adapters/adapters.component.html +++ b/src/app/views/adapters/adapters.component.html @@ -35,7 +35,7 @@

Adapters

{{ adapter.adapterName }} {{ adapter.description }} - + diff --git a/src/app/views/adapters/adapters.component.ts b/src/app/views/adapters/adapters.component.ts index 54f9eedf..2281a794 100644 --- a/src/app/views/adapters/adapters.component.ts +++ b/src/app/views/adapters/adapters.component.ts @@ -24,6 +24,7 @@ import { Validators } from '@angular/forms'; import {PathAccessRequest, RelationalResult} from '../../components/data-view/models/result-set.model'; +import {PreviewRequest} from '../../models/ui-request.model'; import {Subscription} from 'rxjs'; import {CatalogService} from '../../services/catalog.service'; import { @@ -517,7 +518,20 @@ export class AdaptersComponent implements OnInit, OnDestroy { } openTableDialog(): void { - this.schemaDiscoveryService.openTableDialog(); + // this.schemaDiscoveryService.openTableDialog(); + // this._crud.previewTable(new PreviewRequest()); + this._crud.previewTable(new PreviewRequest()).subscribe( { + next: (result: RelationalResult) => { + if (!result.error) { + this._toast.success('Previewed "' + result.query + '"', result.query); + } else { + this._toast.exception(result); + } + }, error: err => { + this._toast.error('Could not preview table', 'server error'); + console.log(err); + } + }); } private validateControl(form: UntypedFormControl, key: string) { From ca1d054d0b364892e16a0c4af9780c0499fc20fd Mon Sep 17 00:00:00 2001 From: romanost03 Date: Wed, 23 Apr 2025 11:33:04 +0200 Subject: [PATCH 05/33] Hardcoded PostgreSQL entity request for schema discovery --- .../data-view/models/result-set.model.ts | 9 ++++++ src/app/models/ui-request.model.ts | 22 +++++++------- src/app/views/adapters/adapters.component.ts | 29 ++++++++++++++----- 3 files changed, 42 insertions(+), 18 deletions(-) diff --git a/src/app/components/data-view/models/result-set.model.ts b/src/app/components/data-view/models/result-set.model.ts index 1de3f0d5..5cbc8909 100644 --- a/src/app/components/data-view/models/result-set.model.ts +++ b/src/app/components/data-view/models/result-set.model.ts @@ -80,6 +80,15 @@ export class DashboardSet { } +/** + * model for handling preview data + */ +export class PreviewResult { + metadata: any; + preview: any[]; +} + + /** * model for statistics coming from the server */ diff --git a/src/app/models/ui-request.model.ts b/src/app/models/ui-request.model.ts index 6fac894f..5b51551a 100644 --- a/src/app/models/ui-request.model.ts +++ b/src/app/models/ui-request.model.ts @@ -211,17 +211,19 @@ export class EditCollectionRequest { } /** - * Request for dealing with the data preview while deploying an adapter. + * Request for dealing with the data preview before deploying an adapter. */ -export class PreviewRequest extends RequestModel{ - type = 'PreviewRequest'; - source: string; - payload: string; - - constructor(source: string = '', payload: string = '') { - super(); - this.source = source; - this.payload = payload; +export class PreviewRequest { + adapterName: string; + adapterType: string; + settings: { [key: string]: string }; + limit: number; + + constructor(adapterName: string, adapterType: string, settings: { [key: string]: string }, limit: number) { + this.adapterName = adapterName; + this.adapterType = adapterType; + this.settings = settings; + this.limit = limit; } } diff --git a/src/app/views/adapters/adapters.component.ts b/src/app/views/adapters/adapters.component.ts index 2281a794..7b221057 100644 --- a/src/app/views/adapters/adapters.component.ts +++ b/src/app/views/adapters/adapters.component.ts @@ -23,7 +23,7 @@ import { ValidatorFn, Validators } from '@angular/forms'; -import {PathAccessRequest, RelationalResult} from '../../components/data-view/models/result-set.model'; +import {PathAccessRequest, PreviewResult, RelationalResult} from '../../components/data-view/models/result-set.model'; import {PreviewRequest} from '../../models/ui-request.model'; import {Subscription} from 'rxjs'; import {CatalogService} from '../../services/catalog.service'; @@ -518,15 +518,28 @@ export class AdaptersComponent implements OnInit, OnDestroy { } openTableDialog(): void { + alert('Open table dialog'); // this.schemaDiscoveryService.openTableDialog(); // this._crud.previewTable(new PreviewRequest()); - this._crud.previewTable(new PreviewRequest()).subscribe( { - next: (result: RelationalResult) => { - if (!result.error) { - this._toast.success('Previewed "' + result.query + '"', result.query); - } else { - this._toast.exception(result); - } + this._crud.previewTable(new PreviewRequest( + 'PostgreSQL', + 'SOURCE', + { + host: 'localhost', + port: '5432', + database: 'postgres', + username: 'postgres', + password: 'password', + tables: 'public.testtable', + maxConnections: '1', + transactionIsolation: 'SERIALIZABLE' + }, + 10 + )).subscribe( { + next: (result: PreviewResult) => { + console.log('Metadata', result.metadata); + console.log('Preview Rows', result.preview); + this._toast.success('Preview erfolgreich geladen'); }, error: err => { this._toast.error('Could not preview table', 'server error'); console.log(err); From 920de41232c4175e94123008a229eb5bcaae7fc7 Mon Sep 17 00:00:00 2001 From: romanost03 Date: Fri, 25 Apr 2025 17:34:43 +0200 Subject: [PATCH 06/33] Hardcoded PostgreSQL entity request is now processed properly and correctly displayed on the window. --- .../data-view/models/result-set.model.ts | 9 ++- .../table-selection-dialog.component.html | 5 +- .../table-selection-dialog.component.ts | 79 +++++++++++++------ src/app/views/adapters/adapters.component.ts | 30 +++++-- 4 files changed, 89 insertions(+), 34 deletions(-) diff --git a/src/app/components/data-view/models/result-set.model.ts b/src/app/components/data-view/models/result-set.model.ts index 5cbc8909..7e787cc3 100644 --- a/src/app/components/data-view/models/result-set.model.ts +++ b/src/app/components/data-view/models/result-set.model.ts @@ -84,7 +84,7 @@ export class DashboardSet { * model for handling preview data */ export class PreviewResult { - metadata: any; + metadata: string; preview: any[]; } @@ -393,6 +393,13 @@ export class PartitionFunctionColumn { options: string[]; } +export interface Node { + type: string; + name: string; + children: Node[]; + properties: { [key: string]: any }; +} + /** * What kind of type to render in the UI, e.g. a number input, a select menu etc. */ diff --git a/src/app/components/table-selection-dialog/table-selection-dialog.component.html b/src/app/components/table-selection-dialog/table-selection-dialog.component.html index 01a49eb4..269bd29c 100644 --- a/src/app/components/table-selection-dialog/table-selection-dialog.component.html +++ b/src/app/components/table-selection-dialog/table-selection-dialog.component.html @@ -23,7 +23,9 @@

📁 {{ schema.name }}

→ {{ val }} - +
    +
  • {{ meta }}
  • +
@@ -32,4 +34,5 @@

📁 {{ schema.name }}

+
diff --git a/src/app/components/table-selection-dialog/table-selection-dialog.component.ts b/src/app/components/table-selection-dialog/table-selection-dialog.component.ts index d3931c36..aab9bb63 100644 --- a/src/app/components/table-selection-dialog/table-selection-dialog.component.ts +++ b/src/app/components/table-selection-dialog/table-selection-dialog.component.ts @@ -1,5 +1,5 @@ import {Component, inject, Inject} from '@angular/core'; -import {DatabaseInfo, TableInfo} from '../../models/databaseInfo.model'; +import {DatabaseInfo, TableInfo, SchemaInfo} from '../../models/databaseInfo.model'; import {FormsModule} from '@angular/forms'; import {NgForOf, NgIf} from '@angular/common'; import {CrudService} from '../../services/crud.service'; @@ -17,39 +17,63 @@ import {PreviewRequest} from '../../models/ui-request.model'; styleUrl: './table-selection-dialog.component.scss' }) export class TableSelectionDialogComponent { - public readonly _crud = inject(CrudService); - constructor() { - } data: DatabaseInfo[] = []; - + selectedMetadata: string[] = []; close(): void { window.close(); } ngOnInit(): void { - /*const raw: string = localStorage.getItem('databaseInfo'); - if (raw) { - this.data = JSON.parse(raw); - - // 🔁 sampleValues → values mappen - for (const db of this.data) { - for (const schema of db.schemas) { - for (const table of schema.tables) { - for (const attr of table.attributes) { - if (attr.sampleValues) { - attr.sampleValues = attr.sampleValues; - } - } - } + const metaRaw = localStorage.getItem('metaRoot'); + const previewRaw = localStorage.getItem('preview'); + console.log(metaRaw); + console.log(previewRaw); + + if (!metaRaw || !previewRaw) { + console.error('No meta or preview data found'); + return; + } + + const rootNode = JSON.parse(metaRaw); + const preview = previewRaw ? JSON.parse(previewRaw) : {}; + + this.data = this.buildDatabaseInfo(rootNode, preview); + } + + private buildDatabaseInfo(root: any, preview: any): DatabaseInfo[] { + const db: DatabaseInfo = {name: root.name, schemas: []}; + for (const schemaNode of root.children ?? []) { + + const schema: SchemaInfo = {name: schemaNode.name, tables: []}; + + for (const tableNode of schemaNode.children ?? []) { + + const tableKey = `${schemaNode.name}.${tableNode.name}`; + const sampleRows = preview[tableKey] ?? []; + + const table: TableInfo = {name: tableNode.name, attributes: []}; + + for (const colNode of tableNode.children ?? []) { + const colName = colNode.name; + + table.attributes.push({ + name: colName, + type: colNode.properties?.type ?? '', + selected: false, + sampleValues: sampleRows.slice(0, 5).map((r: any) => r[colName]) + }); } + schema.tables.push(table); } - }*/ + db.schemas.push(schema); + } + this.data = [db]; + return [db]; } - - /*getSelectedAttributes(): string[] { + getSelectedAttributeMetadata(): string[] { const selected: string[] = []; for (const db of this.data) { @@ -57,7 +81,9 @@ export class TableSelectionDialogComponent { for (const table of schema.tables) { for (const attr of table.attributes) { if (attr.selected) { - selected.push(`${db.name}.${schema.name}.${table.name}.${attr.name}`); + selected.push( + `${db.name}.${schema.name}.${table.name}.${attr.name} : ${attr.type}` + ); } } } @@ -65,6 +91,11 @@ export class TableSelectionDialogComponent { } return selected; - }*/ + } + + showSelectedMetadata(): void { + this.selectedMetadata = this.getSelectedAttributeMetadata(); + } } + diff --git a/src/app/views/adapters/adapters.component.ts b/src/app/views/adapters/adapters.component.ts index 7b221057..40addd3c 100644 --- a/src/app/views/adapters/adapters.component.ts +++ b/src/app/views/adapters/adapters.component.ts @@ -23,7 +23,12 @@ import { ValidatorFn, Validators } from '@angular/forms'; -import {PathAccessRequest, PreviewResult, RelationalResult} from '../../components/data-view/models/result-set.model'; +import { + PathAccessRequest, + PreviewResult, + RelationalResult, + Node +} from '../../components/data-view/models/result-set.model'; import {PreviewRequest} from '../../models/ui-request.model'; import {Subscription} from 'rxjs'; import {CatalogService} from '../../services/catalog.service'; @@ -288,7 +293,7 @@ export class AdaptersComponent implements OnInit, OnDestroy { for (let file of files) { fileNames.push(file.name); this.files.set(file.name, file); - setting.template.fileNames.push(file.name) + setting.template.fileNames.push(file.name); } } @@ -353,7 +358,7 @@ export class AdaptersComponent implements OnInit, OnDestroy { setting.current = null; } - if (setting.template.type.toLowerCase() === "directory") { + if (setting.template.type.toLowerCase() === 'directory') { const fileNames = []; for (let fileName of setting.template.fileNames) { @@ -410,7 +415,7 @@ export class AdaptersComponent implements OnInit, OnDestroy { } private startDeploying(deploy: AdapterModel, formdata: FormData) { - console.log(deploy) + console.log(deploy); this.deploying = true; this._crud.createAdapter(deploy, formdata).subscribe({ next: (result: RelationalResult) => { @@ -535,11 +540,20 @@ export class AdaptersComponent implements OnInit, OnDestroy { transactionIsolation: 'SERIALIZABLE' }, 10 - )).subscribe( { + )).subscribe({ next: (result: PreviewResult) => { - console.log('Metadata', result.metadata); - console.log('Preview Rows', result.preview); - this._toast.success('Preview erfolgreich geladen'); + try { + localStorage.setItem('metaRoot', result.metadata); + localStorage.setItem('preview', JSON.stringify(result.preview)); + + window.open('/#/table-selection', + 'popup', + 'width=1000,height=700'); + + this._toast.success('Preview erfolgreich geladen'); + } catch (e) { + console.error('Fail to parse data: ', e); + } }, error: err => { this._toast.error('Could not preview table', 'server error'); console.log(err); From acbb8c8e8458343efcb01421d3266f30041b3cdc Mon Sep 17 00:00:00 2001 From: romanost03 Date: Wed, 30 Apr 2025 17:54:38 +0200 Subject: [PATCH 07/33] Selected metadata sends back to the Backend now --- .../table-selection-dialog.component.html | 1 + .../table-selection-dialog.component.ts | 12 ++++++++++++ src/app/services/crud.service.ts | 5 +++++ src/app/views/adapters/adapters.component.ts | 2 +- 4 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/app/components/table-selection-dialog/table-selection-dialog.component.html b/src/app/components/table-selection-dialog/table-selection-dialog.component.html index 269bd29c..5b210f8d 100644 --- a/src/app/components/table-selection-dialog/table-selection-dialog.component.html +++ b/src/app/components/table-selection-dialog/table-selection-dialog.component.html @@ -35,4 +35,5 @@

📁 {{ schema.name }}

+
diff --git a/src/app/components/table-selection-dialog/table-selection-dialog.component.ts b/src/app/components/table-selection-dialog/table-selection-dialog.component.ts index aab9bb63..1f1e7cc2 100644 --- a/src/app/components/table-selection-dialog/table-selection-dialog.component.ts +++ b/src/app/components/table-selection-dialog/table-selection-dialog.component.ts @@ -18,6 +18,8 @@ import {PreviewRequest} from '../../models/ui-request.model'; }) export class TableSelectionDialogComponent { + private readonly _crud = inject(CrudService); + data: DatabaseInfo[] = []; selectedMetadata: string[] = []; @@ -97,5 +99,15 @@ export class TableSelectionDialogComponent { this.selectedMetadata = this.getSelectedAttributeMetadata(); } + sendMetadataInfos(): void { + this._crud.sendSelectedMetadata(this.selectedMetadata).subscribe({ + next: res => console.log('Ergoflreich gesendet', res), + error: err => { + console.log(err); + } + }); + alert(' Daten erfolgreich gesendet.'); + } + } diff --git a/src/app/services/crud.service.ts b/src/app/services/crud.service.ts index 1261361a..34bc475e 100644 --- a/src/app/services/crud.service.ts +++ b/src/app/services/crud.service.ts @@ -529,6 +529,11 @@ export class CrudService { return this._http.post(`${this.httpUrl}/createAdapter`, formdata); } + sendSelectedMetadata(request: string[]) { + console.log(request); + return this._http.post(`${this.httpUrl}/sendSelectedMetadata`, request, this.httpOptions); + } + pathAccess(req: PathAccessRequest) { return this._http.post(`${this.httpUrl}/pathAccess`, req); diff --git a/src/app/views/adapters/adapters.component.ts b/src/app/views/adapters/adapters.component.ts index 40addd3c..36d96bb3 100644 --- a/src/app/views/adapters/adapters.component.ts +++ b/src/app/views/adapters/adapters.component.ts @@ -535,7 +535,7 @@ export class AdaptersComponent implements OnInit, OnDestroy { database: 'postgres', username: 'postgres', password: 'password', - tables: 'public.testtable', + tables: 'abcde.testing', maxConnections: '1', transactionIsolation: 'SERIALIZABLE' }, From 1541600a9834bd7b02abb4b10ce69f657fe6926e Mon Sep 17 00:00:00 2001 From: romanost03 Date: Sun, 4 May 2025 15:50:58 +0200 Subject: [PATCH 08/33] Adapter deployment and entity request is not hardcodet anymore, so the adapter is deployed now with the entered settings and choosed metadata. --- .../table-selection-dialog.component.ts | 70 ++++++++++++-- src/app/services/crud.service.ts | 3 + src/app/views/adapters/adapter.model.ts | 4 +- .../views/adapters/adapters.component.html | 4 +- src/app/views/adapters/adapters.component.ts | 91 +++++++++++++++++-- 5 files changed, 153 insertions(+), 19 deletions(-) diff --git a/src/app/components/table-selection-dialog/table-selection-dialog.component.ts b/src/app/components/table-selection-dialog/table-selection-dialog.component.ts index 1f1e7cc2..9d156066 100644 --- a/src/app/components/table-selection-dialog/table-selection-dialog.component.ts +++ b/src/app/components/table-selection-dialog/table-selection-dialog.component.ts @@ -1,9 +1,10 @@ -import {Component, inject, Inject} from '@angular/core'; -import {DatabaseInfo, TableInfo, SchemaInfo} from '../../models/databaseInfo.model'; +import {Component, inject} from '@angular/core'; +import {DatabaseInfo, SchemaInfo, TableInfo} from '../../models/databaseInfo.model'; import {FormsModule} from '@angular/forms'; import {NgForOf, NgIf} from '@angular/common'; import {CrudService} from '../../services/crud.service'; -import {PreviewRequest} from '../../models/ui-request.model'; +import {AdapterModel, AdapterType, PolyMap} from '../../views/adapters/adapter.model'; +import {DeployMode} from '../../models/catalog.model'; @Component({ selector: 'app-table-selection-dialog', @@ -22,26 +23,60 @@ export class TableSelectionDialogComponent { data: DatabaseInfo[] = []; selectedMetadata: string[] = []; + adapter: AdapterModel; close(): void { window.close(); } ngOnInit(): void { + const rawSettings = localStorage.getItem('adapterSettings'); const metaRaw = localStorage.getItem('metaRoot'); const previewRaw = localStorage.getItem('preview'); console.log(metaRaw); console.log(previewRaw); + const settingsObj = rawSettings ? JSON.parse(rawSettings) as Record : {}; + const adapterSettings = new PolyMap(); + for (const [k, v] of Object.entries(settingsObj)) { + adapterSettings.set(k, v as string); + } + if (!metaRaw || !previewRaw) { console.error('No meta or preview data found'); return; } - const rootNode = JSON.parse(metaRaw); - const preview = previewRaw ? JSON.parse(previewRaw) : {}; + let rootNode: any = JSON.parse(metaRaw); + if (typeof rootNode === 'string') { + rootNode = JSON.parse(rootNode); + } + + let preview: any = JSON.parse(previewRaw); + if (typeof preview === 'string') { + preview = JSON.parse(preview); + } + this.data = this.buildDatabaseInfo(rootNode, preview); + + const infoRaw = localStorage.getItem('adapterInfo'); + const info = infoRaw ? JSON.parse(infoRaw) as Partial<{ + uniqueName: string; + adapterName: string; + type: AdapterType; + mode: DeployMode; + persistent: boolean; + }> : {}; + + this.adapter = new AdapterModel( + info.uniqueName ?? adapterSettings.get('uniqueName') ?? 'adapter_' + Date.now(), + info.adapterName ?? 'UNKNOWN', + adapterSettings, + info.persistent ?? true, + info.type ?? AdapterType.SOURCE, + info.mode ?? DeployMode.REMOTE + ); } private buildDatabaseInfo(root: any, preview: any): DatabaseInfo[] { @@ -100,14 +135,29 @@ export class TableSelectionDialogComponent { } sendMetadataInfos(): void { - this._crud.sendSelectedMetadata(this.selectedMetadata).subscribe({ - next: res => console.log('Ergoflreich gesendet', res), - error: err => { - console.log(err); + + + (this.adapter as any).metadata = this.selectedMetadata; + + const formdata = new FormData(); + formdata.set('body', JSON.stringify(this.adapter)); + + formdata.forEach((value, key) => { + console.log(`${key}: ${value}`); + }); + + this._crud.createAdapter(this.adapter, formdata).subscribe({ + next: (res) => { + console.log('Adapter + Metadaten erfolgreich gesendet', res); + alert('Daten erfolgreich gesendet.'); + }, + error: (err) => { + console.error(err); + alert('Fehler beim Senden!'); } }); - alert(' Daten erfolgreich gesendet.'); } + } diff --git a/src/app/services/crud.service.ts b/src/app/services/crud.service.ts index 34bc475e..6dc37022 100644 --- a/src/app/services/crud.service.ts +++ b/src/app/services/crud.service.ts @@ -526,6 +526,9 @@ export class CrudService { createAdapter(adapter: AdapterModel, formdata: FormData) { formdata.set('body', JSON.stringify(adapter)); + formdata.forEach((value, key) => { + console.log(`${key}: ${value}`); + }); return this._http.post(`${this.httpUrl}/createAdapter`, formdata); } diff --git a/src/app/views/adapters/adapter.model.ts b/src/app/views/adapters/adapter.model.ts index 04e126c5..624f5f72 100644 --- a/src/app/views/adapters/adapter.model.ts +++ b/src/app/views/adapters/adapter.model.ts @@ -8,14 +8,16 @@ export class AdapterModel extends IdEntity { readonly type: AdapterType; readonly mode: DeployMode; indexMethods: IndexMethodModel[]; + readonly metadata?: string[]; - constructor(uniqueName: string, adapterName: string, settings: Map, persistent: boolean, type: AdapterType, deployMode: DeployMode) { + constructor(uniqueName: string, adapterName: string, settings: Map, persistent: boolean, type: AdapterType, deployMode: DeployMode, metadata?: string[]) { super(-1, uniqueName); this.adapterName = adapterName; this.settings = settings; this.persistent = persistent; this.type = type; this.mode = deployMode; + this.metadata = metadata; } } diff --git a/src/app/views/adapters/adapters.component.html b/src/app/views/adapters/adapters.component.html index c48af6ee..f0d893ba 100644 --- a/src/app/views/adapters/adapters.component.html +++ b/src/app/views/adapters/adapters.component.html @@ -35,7 +35,7 @@

Adapters

{{ adapter.adapterName }} {{ adapter.description }} - + @@ -276,7 +276,7 @@

{{ activeMode() ? "Settings" : "Deployment Mode" }}

+ + + - -
- - - +
+

{{ tablePreviewName }}

+ + + + + + + + + + + + +
{{ key }}
{{ row[key] }}
+
diff --git a/src/app/components/table-selection-dialog/table-selection-dialog.component.ts b/src/app/components/table-selection-dialog/table-selection-dialog.component.ts index 9d156066..feeed5b8 100644 --- a/src/app/components/table-selection-dialog/table-selection-dialog.component.ts +++ b/src/app/components/table-selection-dialog/table-selection-dialog.component.ts @@ -25,6 +25,11 @@ export class TableSelectionDialogComponent { selectedMetadata: string[] = []; adapter: AdapterModel; + tablePreviewName = ''; + tablePreview: any[] = []; + previewKeys: string[] = []; + + close(): void { window.close(); } @@ -57,6 +62,15 @@ export class TableSelectionDialogComponent { preview = JSON.parse(preview); } + const [name, rawRows] = Object.entries(preview)[0]; + const rows = rawRows as any[]; + + this.tablePreviewName = name; + this.tablePreview = rows; + this.previewKeys = rows.length > 0 ? Object.keys(rows[0]) : []; + + + this.data = this.buildDatabaseInfo(rootNode, preview); From 29fe5b8ada0781640ae76a3b61130e1fc99ab86c Mon Sep 17 00:00:00 2001 From: romanost03 Date: Fri, 9 May 2025 17:21:04 +0200 Subject: [PATCH 10/33] table-selection-dialog.component.html shows all previews instead of only one. --- .../table-selection-dialog.component.html | 37 ++++++++++++------- .../table-selection-dialog.component.ts | 14 ++++++- 2 files changed, 35 insertions(+), 16 deletions(-) diff --git a/src/app/components/table-selection-dialog/table-selection-dialog.component.html b/src/app/components/table-selection-dialog/table-selection-dialog.component.html index acd2883a..5eb30fe0 100644 --- a/src/app/components/table-selection-dialog/table-selection-dialog.component.html +++ b/src/app/components/table-selection-dialog/table-selection-dialog.component.html @@ -32,20 +32,29 @@

📁 {{ schema.name }}

-
-

{{ tablePreviewName }}

+
+ +
+
+

{{ name }}

+ + + + + + + + + + + + +
{{ key }}
+ {{ row[key] }} +
+
+
- - - - - - - - - - - -
{{ key }}
{{ row[key] }}
+
diff --git a/src/app/components/table-selection-dialog/table-selection-dialog.component.ts b/src/app/components/table-selection-dialog/table-selection-dialog.component.ts index feeed5b8..d0d26998 100644 --- a/src/app/components/table-selection-dialog/table-selection-dialog.component.ts +++ b/src/app/components/table-selection-dialog/table-selection-dialog.component.ts @@ -29,6 +29,8 @@ export class TableSelectionDialogComponent { tablePreview: any[] = []; previewKeys: string[] = []; + tablePreviewAll: { [tableName: string]: any[] } = {}; + close(): void { window.close(); @@ -62,6 +64,7 @@ export class TableSelectionDialogComponent { preview = JSON.parse(preview); } + this.tablePreviewAll = preview; const [name, rawRows] = Object.entries(preview)[0]; const rows = rawRows as any[]; @@ -70,8 +73,6 @@ export class TableSelectionDialogComponent { this.previewKeys = rows.length > 0 ? Object.keys(rows[0]) : []; - - this.data = this.buildDatabaseInfo(rootNode, preview); const infoRaw = localStorage.getItem('adapterInfo'); @@ -148,6 +149,15 @@ export class TableSelectionDialogComponent { this.selectedMetadata = this.getSelectedAttributeMetadata(); } + getKeys(rows: any[]): string[] { + return rows.length > 0 ? Object.keys(rows[0]) : []; + } + + getTableNames(): string[] { + return Object.keys(this.tablePreviewAll); + } + + sendMetadataInfos(): void { From 17ecf21b88e293bbcc74c6107c4a2af8b96187fa Mon Sep 17 00:00:00 2001 From: romanost03 Date: Sun, 18 May 2025 21:03:42 +0200 Subject: [PATCH 11/33] New added preview request such that form-data is nwo send with (for excel adapter by now). --- src/app/services/crud.service.ts | 9 +++++++++ src/app/views/adapters/adapters.component.ts | 21 +++++++++++++++++--- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/app/services/crud.service.ts b/src/app/services/crud.service.ts index 6dc37022..26cb2fa0 100644 --- a/src/app/services/crud.service.ts +++ b/src/app/services/crud.service.ts @@ -283,6 +283,15 @@ export class CrudService { alert('Methode wird aufgerufen'); } + /** + * Get the preview for an adapter with forms + */ + previewTableForm(request: PreviewRequest, formdata: FormData) { + formdata.set('body', JSON.stringify(request)); + return this._http.post(`${this.httpUrl}/previewTable`, formdata); + alert('Methode wird aufgerufen'); + } + /** * Add a primary key to a table */ diff --git a/src/app/views/adapters/adapters.component.ts b/src/app/views/adapters/adapters.component.ts index 58ff6901..ed8b33b0 100644 --- a/src/app/views/adapters/adapters.component.ts +++ b/src/app/views/adapters/adapters.component.ts @@ -229,8 +229,22 @@ export class AdaptersComponent implements OnInit, OnDestroy { const fd = new FormData(); for (const [k, v] of Object.entries(this.editingAvailableAdapterForm.controls)) { - if (!this.getAdapterSetting(k)) { continue; } - deploy.settings.set(k, (v as AbstractControl).value); + const setting = this.getAdapterSetting(k); + if (!setting) { + continue; + } + + let value = (v as AbstractControl).value; + + if (setting.template.type.toLowerCase() === 'directory') { + const first = setting.template.fileNames?.[0]; + if (first) { + value = JSON.stringify(first); + fd.append(first, this.files.get(first)); + } + } + + deploy.settings.set(k, value); } fd.set('body', JSON.stringify(deploy)); @@ -241,8 +255,9 @@ export class AdaptersComponent implements OnInit, OnDestroy { 10 ); - this._crud.previewTable(previewReq).subscribe({ + this._crud.previewTableForm(previewReq, fd).subscribe({ next: (preview: PreviewResult) => { + console.log('Preview geladen', preview); const settingsObj = Object.fromEntries( From 8b47a097032c90e414a3324d0cc8ca4feada3f4d Mon Sep 17 00:00:00 2001 From: romanost03 Date: Mon, 2 Jun 2025 10:47:47 +0200 Subject: [PATCH 12/33] Newly created UI for preview request with new receiving model in preview-selection.component and metadata-tree.component --- src/app/app-routing.module.ts | 7 + .../data-view/models/result-set.model.ts | 6 +- .../metadata-tree.component.html | 20 ++ .../metadata-tree.component.scss | 0 .../metadata-tree/metadata-tree.component.ts | 24 +++ .../preview-selection.component.html | 56 +++++ .../preview-selection.component.scss | 72 +++++++ .../preview-selection.component.ts | 200 ++++++++++++++++++ .../table-selection-dialog.component.ts | 1 + src/app/models/ui-request.model.ts | 4 +- src/app/services/crud.service.ts | 10 +- src/app/views/adapters/adapters.component.ts | 68 ++---- 12 files changed, 410 insertions(+), 58 deletions(-) create mode 100644 src/app/components/preview-selection/metadata-tree/metadata-tree.component.html create mode 100644 src/app/components/preview-selection/metadata-tree/metadata-tree.component.scss create mode 100644 src/app/components/preview-selection/metadata-tree/metadata-tree.component.ts create mode 100644 src/app/components/preview-selection/preview-selection.component.html create mode 100644 src/app/components/preview-selection/preview-selection.component.scss create mode 100644 src/app/components/preview-selection/preview-selection.component.ts diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 6f4c4116..663cbaf6 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -36,6 +36,13 @@ export const routes: Routes = [ title: 'Table Selection' } }, + { + path: 'preview-selection', + loadComponent: () => import('./components/preview-selection/preview-selection.component').then(m => m.PreviewSelectionComponent), + data: { + title: 'Preview Selection' + } + }, { path: '', pathMatch: 'full', diff --git a/src/app/components/data-view/models/result-set.model.ts b/src/app/components/data-view/models/result-set.model.ts index 7e787cc3..f90b75d5 100644 --- a/src/app/components/data-view/models/result-set.model.ts +++ b/src/app/components/data-view/models/result-set.model.ts @@ -393,11 +393,11 @@ export class PartitionFunctionColumn { options: string[]; } -export interface Node { +export interface AbstractNode { type: string; name: string; - children: Node[]; - properties: { [key: string]: any }; + children?: AbstractNode[]; + properties?: { [key: string]: any }; } /** diff --git a/src/app/components/preview-selection/metadata-tree/metadata-tree.component.html b/src/app/components/preview-selection/metadata-tree/metadata-tree.component.html new file mode 100644 index 00000000..2fb62309 --- /dev/null +++ b/src/app/components/preview-selection/metadata-tree/metadata-tree.component.html @@ -0,0 +1,20 @@ +
    +
  • + {{ node.name }} + + + + {{ node.name }} + + + + + + +
  • +
diff --git a/src/app/components/preview-selection/metadata-tree/metadata-tree.component.scss b/src/app/components/preview-selection/metadata-tree/metadata-tree.component.scss new file mode 100644 index 00000000..e69de29b diff --git a/src/app/components/preview-selection/metadata-tree/metadata-tree.component.ts b/src/app/components/preview-selection/metadata-tree/metadata-tree.component.ts new file mode 100644 index 00000000..ad399c12 --- /dev/null +++ b/src/app/components/preview-selection/metadata-tree/metadata-tree.component.ts @@ -0,0 +1,24 @@ +import {Component, Input, Output, EventEmitter} from '@angular/core'; +import {CommonModule} from '@angular/common'; +import {AbstractNode} from '../../data-view/models/result-set.model'; + +@Component({ + selector: 'app-metadata-tree', + standalone: true, + imports: [ + CommonModule + ], + templateUrl: './metadata-tree.component.html', + styleUrl: './metadata-tree.component.scss' +}) + +export class MetadataTreeComponent { + @Input() node!: AbstractNode; + @Input() path = ''; + @Output() columnToggle = new EventEmitter<{ fullKey: string, checked: boolean }>(); + + toggleColumn(fullKey: string, checked: boolean) { + this.columnToggle.emit({fullKey, checked}); + } + +} diff --git a/src/app/components/preview-selection/preview-selection.component.html b/src/app/components/preview-selection/preview-selection.component.html new file mode 100644 index 00000000..a79ac245 --- /dev/null +++ b/src/app/components/preview-selection/preview-selection.component.html @@ -0,0 +1,56 @@ +
+ + + + +

Preview Selection

+
+
+
+
+ + + +
+
+

{{ tableName }}

+ + + + + + + + + + + + + +
+ {{ col }} +
+ {{ row[col] }} +
+ +
+
+ +
+

Metadata

+ + + +
+
+ +
+ +
+
+ diff --git a/src/app/components/preview-selection/preview-selection.component.scss b/src/app/components/preview-selection/preview-selection.component.scss new file mode 100644 index 00000000..5e7998a3 --- /dev/null +++ b/src/app/components/preview-selection/preview-selection.component.scss @@ -0,0 +1,72 @@ +.preview-wrapper { + position: relative; + min-height: 200px; +} + +.btn-circle { + width: 40px; + height: 40px; + line-height: 40px; + text-align: center; + padding: 0; + border-radius: 50%; + cursor: pointer; + display: inline-flex; + align-items: center; + justify-content: center; +} + +.topRight { + position: absolute; + top: 0.5rem; + right: 1rem; + z-index: 2; +} + +.page-title { + margin-left: 30px; + margin-top: 30px; +} + +.btn-circle { + margin-top: 30px; +} + +.preview-container { + display: flex; + overflow-x: auto; + gap: 24px; + padding: 1rem; +} + +.table-wrapper { + min-width: 300px; + flex-shrink: 0; + + table { + border-collapse: collapse; + width: 100%; + + th, td { + border: 1px solid #ccc; + padding: 4px 8px; + } + + th { + background: #f5f5f5; + } + } +} + +.dataPreview { + float: right; + margin-right: 30px; + overflow: hidden; +} + +.dataMeta { + margin-left: 30px; + float: left; + overflow: hidden; +} + diff --git a/src/app/components/preview-selection/preview-selection.component.ts b/src/app/components/preview-selection/preview-selection.component.ts new file mode 100644 index 00000000..24b32874 --- /dev/null +++ b/src/app/components/preview-selection/preview-selection.component.ts @@ -0,0 +1,200 @@ +import {Component, inject} from '@angular/core'; +import {ButtonCloseDirective, ButtonDirective, ColComponent, RowComponent} from '@coreui/angular'; +import {RouterLink} from '@angular/router'; +import {AbstractNode} from '../data-view/models/result-set.model'; +import {CommonModule} from '@angular/common'; +import {MetadataTreeComponent} from './metadata-tree/metadata-tree.component'; +import {AdapterModel, AdapterType, PolyMap} from '../../views/adapters/adapter.model'; +import {DeployMode} from '../../models/catalog.model'; +import {CrudService} from '../../services/crud.service'; + +@Component({ + selector: 'app-preview-selection', + standalone: true, + imports: [ + ButtonDirective, + RowComponent, + ColComponent, + CommonModule, + MetadataTreeComponent + ], + templateUrl: './preview-selection.component.html', + styleUrl: './preview-selection.component.scss' +}) +export class PreviewSelectionComponent { + + private readonly _crud = inject(CrudService); + + formData: FormData = new FormData(); + pendingFiles!: Map; + adapter: AdapterModel; + + + metadata: AbstractNode = null; + preview: Record = {}; + + + selected: Set = new Set(); + ready = false; + + + ngOnInit() { + + const opener = (window.opener as any); + this.formData = opener?.pendingFormData; + this.pendingFiles = opener.pendingFiles as Map; + + + let metaRaw = localStorage.getItem('metaRoot'); + metaRaw = JSON.parse(metaRaw); + this.metadata = this.deserializeNode(metaRaw); + + const previewRaw = localStorage.getItem('preview'); + this.preview = previewRaw ? JSON.parse(previewRaw) : {}; + + console.log(this.metadata); + + + const rawSettings = localStorage.getItem('adapterSettings'); + const settingsObj = rawSettings ? JSON.parse(rawSettings) as Record : {}; + const adapterSettings = new PolyMap(); + for (const [k, v] of Object.entries(settingsObj)) { + adapterSettings.set(k, v as string); + } + + + const infoRaw = localStorage.getItem('adapterInfo'); + const info = infoRaw ? JSON.parse(infoRaw) as Partial<{ + uniqueName: string; + adapterName: string; + type: AdapterType; + mode: DeployMode; + persistent: boolean; + }> : {}; + + this.adapter = new AdapterModel( + info.uniqueName ?? adapterSettings.get('uniqueName') ?? 'adapter_' + Date.now(), + info.adapterName ?? 'UNKNOWN', + adapterSettings, + info.persistent ?? true, + info.type ?? AdapterType.SOURCE, + info.mode ?? DeployMode.REMOTE + ); + + + this.ready = true; + + } + + close(): void { + window.close(); + } + + deserializeNode(obj: any): Node { + const node = new Node(obj.type, obj.name); + + if (obj.properties) { + for (const [key, value] of Object.entries(obj.properties)) { + node.addProperty(key, value); + } + } + + if (obj.children && Array.isArray(obj.children)) { + for (const child of obj.children) { + node.addChild(this.deserializeNode(child)); + } + } + + return node; + } + + getPreviewKeys(): string[] { + return Object.keys(this.preview); + } + + getPreviewColumns(tableName: string): string[] { + const rows = this.preview[tableName]; + return rows && rows.length ? Object.keys(rows[0]) : []; + } + + onColumnToggle(e: { fullKey: string; checked: boolean }) { + e.checked ? this.selected.add(e.fullKey) : this.selected.delete(e.fullKey); + console.log(this.selected); + } + + /*sendMetadata() { + (this.adapter as any).metadata = Array.from(this.selected); + + + this._crud.createAdapter(this.adapter, this.formData).subscribe({ + next: (res) => { + console.log('Adapter + Metadaten erfolgreich gesendet', res); + alert('Daten erfolgreich gesendet.'); + }, + error: (err) => { + console.error(err); + alert('Fehler beim Senden!'); + } + }); + }*/ + sendMetadata(): void { + + const newFormData = new FormData(); + const files: Map = (window.opener as any).pendingFiles; + + + for (const [field, file] of this.pendingFiles) { + if (!newFormData.has(field)) { + newFormData.append(field, file); + } + } + + + (this.adapter as any).metadata = Array.from(this.selected); + + const firstFileName = files.keys().next().value as string; + + if (this.adapter.settings.has('directory')) { + const fileNames = [firstFileName]; + this.adapter.settings.set('directory', JSON.stringify(fileNames)); + } + + + newFormData.forEach((value, key) => { + console.log(`${key}: ${value}`); + }); + + this._crud.createAdapter(this.adapter, newFormData).subscribe({ + next: res => { + alert('Adapter + Metadaten erfolgreich gesendet'); + window.close(); + }, + error: err => { + console.error(err); + alert('Fehler beim Senden!'); + } + }); + + } + +} + +export class Node implements AbstractNode { + type: string; + name: string; + children: AbstractNode[] = []; + properties: { [key: string]: any } = {}; + + constructor(type: string, name: string) { + this.type = type; + this.name = name; + } + + addChild(node: AbstractNode): void { + this.children.push(node); + } + + addProperty(key: string, value: any): void { + this.properties[key] = value; + } +} diff --git a/src/app/components/table-selection-dialog/table-selection-dialog.component.ts b/src/app/components/table-selection-dialog/table-selection-dialog.component.ts index d0d26998..13f13834 100644 --- a/src/app/components/table-selection-dialog/table-selection-dialog.component.ts +++ b/src/app/components/table-selection-dialog/table-selection-dialog.component.ts @@ -147,6 +147,7 @@ export class TableSelectionDialogComponent { showSelectedMetadata(): void { this.selectedMetadata = this.getSelectedAttributeMetadata(); + console.log(this.selectedMetadata); } getKeys(rows: any[]): string[] { diff --git a/src/app/models/ui-request.model.ts b/src/app/models/ui-request.model.ts index 5b51551a..9a006343 100644 --- a/src/app/models/ui-request.model.ts +++ b/src/app/models/ui-request.model.ts @@ -218,12 +218,14 @@ export class PreviewRequest { adapterType: string; settings: { [key: string]: string }; limit: number; + uniqueName?: string; - constructor(adapterName: string, adapterType: string, settings: { [key: string]: string }, limit: number) { + constructor(adapterName: string, adapterType: string, settings: { [key: string]: string }, limit: number, uniqueName: string) { this.adapterName = adapterName; this.adapterType = adapterType; this.settings = settings; this.limit = limit; + this.uniqueName = uniqueName; } } diff --git a/src/app/services/crud.service.ts b/src/app/services/crud.service.ts index 26cb2fa0..1e2d45f8 100644 --- a/src/app/services/crud.service.ts +++ b/src/app/services/crud.service.ts @@ -46,7 +46,7 @@ import {PlanType} from '../models/information-page.model'; providedIn: 'root' }) export class CrudService { - private readonly _http = inject(HttpClient); + readonly _http = inject(HttpClient); private readonly _settings = inject(WebuiSettingsService); private enabledPlugins: [string] = null; @@ -61,7 +61,7 @@ export class CrudService { public connected = false; private reconnected = new EventEmitter(); - private httpUrl = this._settings.getConnection('crud.rest'); + httpUrl = this._settings.getConnection('crud.rest'); private httpOptions = {headers: new HttpHeaders({'Content-Type': 'application/json'})}; private httpOptionsPart = {headers: new HttpHeaders({'Content-Type': 'multipart/form-data'})}; private socket; @@ -288,6 +288,10 @@ export class CrudService { */ previewTableForm(request: PreviewRequest, formdata: FormData) { formdata.set('body', JSON.stringify(request)); + console.log('Daten, die als Preview geschickt werden'); + formdata.forEach((value, key) => { + console.log(`${key}: ${value}`); + }); return this._http.post(`${this.httpUrl}/previewTable`, formdata); alert('Methode wird aufgerufen'); } @@ -535,8 +539,10 @@ export class CrudService { createAdapter(adapter: AdapterModel, formdata: FormData) { formdata.set('body', JSON.stringify(adapter)); + console.log('Adaptersettings vor der Creation'); formdata.forEach((value, key) => { console.log(`${key}: ${value}`); + }); return this._http.post(`${this.httpUrl}/createAdapter`, formdata); } diff --git a/src/app/views/adapters/adapters.component.ts b/src/app/views/adapters/adapters.component.ts index ed8b33b0..4460810e 100644 --- a/src/app/views/adapters/adapters.component.ts +++ b/src/app/views/adapters/adapters.component.ts @@ -27,7 +27,7 @@ import { PathAccessRequest, PreviewResult, RelationalResult, - Node + AbstractNode } from '../../components/data-view/models/result-set.model'; import {PreviewRequest} from '../../models/ui-request.model'; import {Subscription} from 'rxjs'; @@ -112,8 +112,8 @@ export class AdaptersComponent implements OnInit, OnDestroy { private readonly files = new Map(); - private pendingDeploy!: AdapterModel; - private pendingFormData!: FormData; + pendingDeploy!: AdapterModel; + pendingFormData!: FormData; metadataSelection = false; @@ -121,7 +121,7 @@ export class AdaptersComponent implements OnInit, OnDestroy { return (a, b) => { return a.position - b.position; }; - } + }; ngOnInit() { @@ -252,7 +252,8 @@ export class AdaptersComponent implements OnInit, OnDestroy { deploy.adapterName, deploy.type, Object.fromEntries(deploy.settings), - 10 + 10, + deploy.name ); this._crud.previewTableForm(previewReq, fd).subscribe({ @@ -268,11 +269,11 @@ export class AdaptersComponent implements OnInit, OnDestroy { localStorage.setItem('adapterSettings', JSON.stringify(settingsObj)); localStorage.setItem('adapterInfo', JSON.stringify({ - uniqueName : deploy.name, + uniqueName: deploy.name, adapterName: deploy.adapterName, - type : deploy.type, - mode : deploy.mode, - persistent : deploy.persistent + type: deploy.type, + mode: deploy.mode, + persistent: deploy.persistent })); @@ -283,17 +284,18 @@ export class AdaptersComponent implements OnInit, OnDestroy { : JSON.stringify(preview.metadata) ); - localStorage.setItem('preview', JSON.stringify(preview.preview)); + localStorage.setItem('preview', JSON.stringify(preview.preview)); console.log(JSON.stringify(preview.metadata)); console.log(JSON.stringify(preview.preview)); - this.pendingDeploy = deploy; - this.pendingFormData = fd; + (window as any).pendingDeploy = deploy; + (window as any).pendingFormData = fd; + (window as any).pendingFiles = this.files; this.metadataSelection = true; this.modalActive = false; - window.open('/#/table-selection', + const win = window.open('/#/preview-selection', 'popup', 'width=1000,height=700'); @@ -508,6 +510,7 @@ export class AdaptersComponent implements OnInit, OnDestroy { window.URL.revokeObjectURL(url); }, 0); } + private startDeploying(deploy: AdapterModel, formdata: FormData) { console.log(deploy); this.deploying = true; @@ -616,45 +619,6 @@ export class AdaptersComponent implements OnInit, OnDestroy { } } - openTableDialog(): void { - alert('Open table dialog'); - // this.schemaDiscoveryService.openTableDialog(); - // this._crud.previewTable(new PreviewRequest()); - this._crud.previewTable(new PreviewRequest( - 'PostgreSQL', - 'SOURCE', - { - host: 'localhost', - port: '5432', - database: 'postgres', - username: 'postgres', - password: 'password', - tables: 'public.testtable', - maxConnections: '25', - transactionIsolation: 'SERIALIZABLE' - }, - 10 - )).subscribe({ - next: (result: PreviewResult) => { - try { - localStorage.setItem('metaRoot', result.metadata); - localStorage.setItem('preview', JSON.stringify(result.preview)); - - window.open('/#/table-selection', - 'popup', - 'width=1000,height=700'); - - this._toast.success('Preview erfolgreich geladen'); - } catch (e) { - console.error('Fail to parse data: ', e); - } - }, error: err => { - this._toast.error('Could not preview table', 'server error'); - console.log(err); - } - }); - } - private validateControl(form: UntypedFormControl, key: string) { if ((key === 'port' || key === 'instanceId') && this.activeMode() === DeployMode.DOCKER) { if (this.editingAvailableAdapterForm.valid) { From 05e6a1cf18d2ddef770a64ff0f75107d3781e018 Mon Sep 17 00:00:00 2001 From: romanost03 Date: Fri, 13 Jun 2025 18:21:13 +0200 Subject: [PATCH 13/33] Moving preview-selection component into UI-framework and add new http for change requests --- src/app/app-routing.module.ts | 7 -- .../data-view/models/result-set.model.ts | 4 + .../metadata-tree.component.scss | 0 .../metadata-tree/metadata-tree.component.ts | 24 ------ src/app/models/ui-request.model.ts | 18 ++++- src/app/services/catalog.service.ts | 19 ++++- src/app/services/crud.service.ts | 13 ++++ src/app/services/metadata-polling.service.ts | 54 ++++++++++++++ .../services/preview-navigation.service.ts | 42 +++++++++++ src/app/views/adapters/adapter.model.ts | 4 +- .../views/adapters/adapters.component.html | 7 ++ src/app/views/adapters/adapters.component.ts | 74 ++++++++++++++++++- .../metadata-tree.component.html | 11 +++ .../metadata-tree.component.scss | 3 + .../metadata-tree/metadata-tree.component.ts | 52 +++++++++++++ .../preview-selection.component.html | 2 +- .../preview-selection.component.scss | 0 .../preview-selection.component.ts | 49 +++++++++--- src/app/views/views-routing.module.ts | 9 +++ src/app/views/views.module.ts | 6 +- 20 files changed, 345 insertions(+), 53 deletions(-) delete mode 100644 src/app/components/preview-selection/metadata-tree/metadata-tree.component.scss delete mode 100644 src/app/components/preview-selection/metadata-tree/metadata-tree.component.ts create mode 100644 src/app/services/metadata-polling.service.ts create mode 100644 src/app/services/preview-navigation.service.ts rename src/app/{components => views}/preview-selection/metadata-tree/metadata-tree.component.html (74%) create mode 100644 src/app/views/preview-selection/metadata-tree/metadata-tree.component.scss create mode 100644 src/app/views/preview-selection/metadata-tree/metadata-tree.component.ts rename src/app/{components => views}/preview-selection/preview-selection.component.html (93%) rename src/app/{components => views}/preview-selection/preview-selection.component.scss (100%) rename src/app/{components => views}/preview-selection/preview-selection.component.ts (80%) diff --git a/src/app/app-routing.module.ts b/src/app/app-routing.module.ts index 663cbaf6..6f4c4116 100644 --- a/src/app/app-routing.module.ts +++ b/src/app/app-routing.module.ts @@ -36,13 +36,6 @@ export const routes: Routes = [ title: 'Table Selection' } }, - { - path: 'preview-selection', - loadComponent: () => import('./components/preview-selection/preview-selection.component').then(m => m.PreviewSelectionComponent), - data: { - title: 'Preview Selection' - } - }, { path: '', pathMatch: 'full', diff --git a/src/app/components/data-view/models/result-set.model.ts b/src/app/components/data-view/models/result-set.model.ts index f90b75d5..96bec34c 100644 --- a/src/app/components/data-view/models/result-set.model.ts +++ b/src/app/components/data-view/models/result-set.model.ts @@ -398,6 +398,10 @@ export interface AbstractNode { name: string; children?: AbstractNode[]; properties?: { [key: string]: any }; + + addChild(node: AbstractNode): void; + addProperty(key: string, value: any): void; + getProperty(key: string): string | null; } /** diff --git a/src/app/components/preview-selection/metadata-tree/metadata-tree.component.scss b/src/app/components/preview-selection/metadata-tree/metadata-tree.component.scss deleted file mode 100644 index e69de29b..00000000 diff --git a/src/app/components/preview-selection/metadata-tree/metadata-tree.component.ts b/src/app/components/preview-selection/metadata-tree/metadata-tree.component.ts deleted file mode 100644 index ad399c12..00000000 --- a/src/app/components/preview-selection/metadata-tree/metadata-tree.component.ts +++ /dev/null @@ -1,24 +0,0 @@ -import {Component, Input, Output, EventEmitter} from '@angular/core'; -import {CommonModule} from '@angular/common'; -import {AbstractNode} from '../../data-view/models/result-set.model'; - -@Component({ - selector: 'app-metadata-tree', - standalone: true, - imports: [ - CommonModule - ], - templateUrl: './metadata-tree.component.html', - styleUrl: './metadata-tree.component.scss' -}) - -export class MetadataTreeComponent { - @Input() node!: AbstractNode; - @Input() path = ''; - @Output() columnToggle = new EventEmitter<{ fullKey: string, checked: boolean }>(); - - toggleColumn(fullKey: string, checked: boolean) { - this.columnToggle.emit({fullKey, checked}); - } - -} diff --git a/src/app/models/ui-request.model.ts b/src/app/models/ui-request.model.ts index 9a006343..2a10f93d 100644 --- a/src/app/models/ui-request.model.ts +++ b/src/app/models/ui-request.model.ts @@ -220,7 +220,9 @@ export class PreviewRequest { limit: number; uniqueName?: string; - constructor(adapterName: string, adapterType: string, settings: { [key: string]: string }, limit: number, uniqueName: string) { + constructor(adapterName: string, adapterType: string, settings: { + [key: string]: string + }, limit: number, uniqueName: string) { this.adapterName = adapterName; this.adapterType = adapterType; this.settings = settings; @@ -229,6 +231,20 @@ export class PreviewRequest { } } +/** + * Request used for checking for available metadata changes. + */ +export class MetadataChangeRequest { + adapterName: string; + uniqueName: string; + + constructor(adapterName: string, uniqueName: string) { + this.adapterName = adapterName; + this.uniqueName = uniqueName; + } +} + + /** * Request to drop or create a constraint of a table */ diff --git a/src/app/services/catalog.service.ts b/src/app/services/catalog.service.ts index d7799f1b..676019c2 100644 --- a/src/app/services/catalog.service.ts +++ b/src/app/services/catalog.service.ts @@ -154,7 +154,7 @@ export class CatalogService { for (let idEntity of idEntities) { const id = extract(idEntity); if (!map.has(id)) { - map.set(id, []) + map.set(id, []); } map.set(id, [idEntity, ...map.get(id)]); } @@ -289,7 +289,7 @@ export class CatalogService { private getNamespaceIcon(dataModel: DataModel): string { if (!this.assets) { this.updateIfNecessary(); - return ""; + return ''; } switch (dataModel) { case DataModel.DOCUMENT: @@ -370,6 +370,21 @@ export class CatalogService { }); } + updateAdapterFlag(uniqueName: string, delta: Partial): void { + this.adapters.update(oldMap => { + const newMap = new Map(oldMap); + + for (const [id, adapter] of oldMap.entries()) { + if (adapter.name === uniqueName) { + newMap.set(id, { ...adapter, ...delta }); + break; + } + } + + return newMap; + }); + } + getAdapterTemplate(adapterName: string, type: AdapterType): AdapterTemplateModel { return this.adapterTemplates().get(adapterName + '_' + type); } diff --git a/src/app/services/crud.service.ts b/src/app/services/crud.service.ts index 1e2d45f8..29bea912 100644 --- a/src/app/services/crud.service.ts +++ b/src/app/services/crud.service.ts @@ -40,6 +40,7 @@ import {Observable} from 'rxjs'; import {PolyAlgRegistry} from '../components/polyalg/models/polyalg-registry'; import {PlanNode} from '../components/polyalg/models/polyalg-plan.model'; import {PlanType} from '../models/information-page.model'; +import {MetadataStatusResponse} from './metadata-polling.service'; @Injectable({ @@ -296,6 +297,18 @@ export class CrudService { alert('Methode wird aufgerufen'); } + metadataStatus(request: string) { + return this._http.get(`${this.httpUrl}/metadataStatus/${request}`, this.httpOptions); + } + + metadataChanges(request: string) { + return this._http.get(`${this.httpUrl}/metadataChange/${request}`, this.httpOptions); + } + + metadataAck(request: string) { + return this._http.post(`${this.httpUrl}/metadataAck/${request}`, this.httpOptions); + } + /** * Add a primary key to a table */ diff --git a/src/app/services/metadata-polling.service.ts b/src/app/services/metadata-polling.service.ts new file mode 100644 index 00000000..77b8a2a8 --- /dev/null +++ b/src/app/services/metadata-polling.service.ts @@ -0,0 +1,54 @@ +// src/app/services/metadata-polling.service.ts +import {inject, Injectable} from '@angular/core'; +import { CrudService } from './crud.service'; +import {timer, Subscription, of, switchMap} from 'rxjs'; + +import { CatalogService } from './catalog.service'; +import { AdapterModel } from '../views/adapters/adapter.model'; + +export interface MetadataStatusResponse { + changed: boolean; +} + +@Injectable({providedIn: 'root'}) +export class MetadataPollingService { + + private readonly _crud = inject(CrudService); + private readonly _catalog = inject(CatalogService); + + private readonly intervalMs = 30_000; + private timerSub: Subscription | null = null; + + + start(): void { + if (this.timerSub) { + return; + } + + this.timerSub = timer(0, this.intervalMs).pipe( + switchMap(() => of(this._catalog.getSources())) + ).subscribe(sourceList => { + sourceList.forEach(source => this.checkSource(source)); + }); + } + + stop(): void { + this.timerSub?.unsubscribe(); + this.timerSub = null; + } + + + private checkSource(adapter: AdapterModel): void { + this._crud.metadataStatus(adapter.name).subscribe({ + next: (res: Object) => { + const casted = res as MetadataStatusResponse; + + this._catalog.updateAdapterFlag(adapter.name, { + metadataChanged: casted.changed + }); + }, + error: err => console.error('[metadata-poll]', err) + }); + } + +} diff --git a/src/app/services/preview-navigation.service.ts b/src/app/services/preview-navigation.service.ts new file mode 100644 index 00000000..1ab341eb --- /dev/null +++ b/src/app/services/preview-navigation.service.ts @@ -0,0 +1,42 @@ +import {Injectable} from '@angular/core'; +import {AbstractNode} from '../components/data-view/models/result-set.model'; +import {AdapterModel, AdapterType, PolyMap} from '../views/adapters/adapter.model'; +import {DeployMode} from '../models/catalog.model'; + + +export type PreviewMode = 'deploy' | 'change'; + +export interface PreviewContext { + mode: PreviewMode; + adapter: AdapterModel; + metadata: AbstractNode; + preview: Record | any[]; + formData: FormData; + files: Map; + adapterSettings: PolyMap; + adapterInfo: Partial<{ + uniqueName: string; + adapterName: string; + type: AdapterType; + mode: DeployMode; + persistent: boolean; + }>; +} + +@Injectable({providedIn: 'root'}) +export class PreviewNavigationService { + + private _ctx: PreviewContext | null = null; + + setContext(ctx: PreviewContext): void { + this._ctx = ctx; + } + + get context(): PreviewContext | null { + return this._ctx; + } + + clear(): void { + this._ctx = null; + } +} diff --git a/src/app/views/adapters/adapter.model.ts b/src/app/views/adapters/adapter.model.ts index 624f5f72..93e7b3f7 100644 --- a/src/app/views/adapters/adapter.model.ts +++ b/src/app/views/adapters/adapter.model.ts @@ -9,8 +9,9 @@ export class AdapterModel extends IdEntity { readonly mode: DeployMode; indexMethods: IndexMethodModel[]; readonly metadata?: string[]; + metadataChanged?: boolean; - constructor(uniqueName: string, adapterName: string, settings: Map, persistent: boolean, type: AdapterType, deployMode: DeployMode, metadata?: string[]) { + constructor(uniqueName: string, adapterName: string, settings: Map, persistent: boolean, type: AdapterType, deployMode: DeployMode, metadata?: string[], metadataChanged?: boolean) { super(-1, uniqueName); this.adapterName = adapterName; this.settings = settings; @@ -18,6 +19,7 @@ export class AdapterModel extends IdEntity { this.type = type; this.mode = deployMode; this.metadata = metadata; + this.metadataChanged = metadataChanged; } } diff --git a/src/app/views/adapters/adapters.component.html b/src/app/views/adapters/adapters.component.html index f0d893ba..a4b7013d 100644 --- a/src/app/views/adapters/adapters.component.html +++ b/src/app/views/adapters/adapters.component.html @@ -75,6 +75,13 @@

Adapters

+ ❗ + + diff --git a/src/app/components/preview-selection/preview-selection.component.scss b/src/app/views/preview-selection/preview-selection.component.scss similarity index 100% rename from src/app/components/preview-selection/preview-selection.component.scss rename to src/app/views/preview-selection/preview-selection.component.scss diff --git a/src/app/components/preview-selection/preview-selection.component.ts b/src/app/views/preview-selection/preview-selection.component.ts similarity index 80% rename from src/app/components/preview-selection/preview-selection.component.ts rename to src/app/views/preview-selection/preview-selection.component.ts index 24b32874..9070921f 100644 --- a/src/app/components/preview-selection/preview-selection.component.ts +++ b/src/app/views/preview-selection/preview-selection.component.ts @@ -1,12 +1,13 @@ import {Component, inject} from '@angular/core'; import {ButtonCloseDirective, ButtonDirective, ColComponent, RowComponent} from '@coreui/angular'; -import {RouterLink} from '@angular/router'; -import {AbstractNode} from '../data-view/models/result-set.model'; +import {Router, RouterLink} from '@angular/router'; +import {AbstractNode} from '../../components/data-view/models/result-set.model'; import {CommonModule} from '@angular/common'; import {MetadataTreeComponent} from './metadata-tree/metadata-tree.component'; -import {AdapterModel, AdapterType, PolyMap} from '../../views/adapters/adapter.model'; +import {AdapterModel, AdapterType, PolyMap} from '../adapters/adapter.model'; import {DeployMode} from '../../models/catalog.model'; import {CrudService} from '../../services/crud.service'; +import {PreviewNavigationService} from '../../services/preview-navigation.service'; @Component({ selector: 'app-preview-selection', @@ -23,7 +24,11 @@ import {CrudService} from '../../services/crud.service'; }) export class PreviewSelectionComponent { + private readonly _nav = inject(PreviewNavigationService); private readonly _crud = inject(CrudService); + private readonly _router = inject(Router); + + private ctx = null; formData: FormData = new FormData(); pendingFiles!: Map; @@ -31,16 +36,31 @@ export class PreviewSelectionComponent { metadata: AbstractNode = null; - preview: Record = {}; + preview: Record | any[] = {}; selected: Set = new Set(); ready = false; - ngOnInit() { - const opener = (window.opener as any); + this.ctx = this._nav.context; + + + if (!this.ctx) { + console.error('Preview-context not found !'); + return; + } + + + this.formData = this.ctx.formData; + this.pendingFiles = this.ctx.files; + this.metadata = this.ctx.metadata; + this.preview = this.ctx.preview; + this.adapter = this.ctx.adapter; + + + /*const opener = (window.opener as any); this.formData = opener?.pendingFormData; this.pendingFiles = opener.pendingFiles as Map; @@ -79,7 +99,7 @@ export class PreviewSelectionComponent { info.persistent ?? true, info.type ?? AdapterType.SOURCE, info.mode ?? DeployMode.REMOTE - ); + );*/ this.ready = true; @@ -87,7 +107,7 @@ export class PreviewSelectionComponent { } close(): void { - window.close(); + this._router.navigate(['/views/adapters/addSource']); } deserializeNode(obj: any): Node { @@ -105,6 +125,7 @@ export class PreviewSelectionComponent { } } + return node; } @@ -140,7 +161,7 @@ export class PreviewSelectionComponent { sendMetadata(): void { const newFormData = new FormData(); - const files: Map = (window.opener as any).pendingFiles; + const files: Map = this.ctx.files; for (const [field, file] of this.pendingFiles) { @@ -166,12 +187,12 @@ export class PreviewSelectionComponent { this._crud.createAdapter(this.adapter, newFormData).subscribe({ next: res => { - alert('Adapter + Metadaten erfolgreich gesendet'); - window.close(); + alert('Adapter + Metadata sent successfully.'); + this.close(); }, error: err => { console.error(err); - alert('Fehler beim Senden!'); + alert('Fail to send metadata!'); } }); @@ -197,4 +218,8 @@ export class Node implements AbstractNode { addProperty(key: string, value: any): void { this.properties[key] = value; } + + getProperty(key: string): string { + return this.properties[key]; + } } diff --git a/src/app/views/views-routing.module.ts b/src/app/views/views-routing.module.ts index e40afa30..4bf79b83 100644 --- a/src/app/views/views-routing.module.ts +++ b/src/app/views/views-routing.module.ts @@ -13,6 +13,7 @@ import {QueryInterfacesComponent} from './query-interfaces/query-interfaces.comp import {NotebooksComponent} from '../plugins/notebooks/components/notebooks.component'; import {UnsavedChangesGuard} from '../plugins/notebooks/services/unsaved-changes.guard'; import {DockerconfigComponent} from './dockerconfig/dockerconfig.component'; +import {PreviewSelectionComponent} from './preview-selection/preview-selection.component'; const routes: Routes = [ { @@ -168,6 +169,14 @@ const routes: Routes = [ title: 'QueryInterfaces' } }, + { + path: 'preview-selection', + component: PreviewSelectionComponent, + data : { + title: 'Preview Selection', + breadcrumb: 'Preview' + } + }, { path: 'notebooks', children: [ diff --git a/src/app/views/views.module.ts b/src/app/views/views.module.ts index 057b8f2d..c2e3094d 100644 --- a/src/app/views/views.module.ts +++ b/src/app/views/views.module.ts @@ -26,6 +26,7 @@ import {TypeaheadModule} from 'ngx-bootstrap/typeahead'; import {TooltipModule} from 'ngx-bootstrap/tooltip'; import {BsDropdownModule} from 'ngx-bootstrap/dropdown'; import {ModalModule} from 'ngx-bootstrap/modal'; +import {PreviewSelectionComponent} from './preview-selection/preview-selection.component'; import {ProgressbarModule} from 'ngx-bootstrap/progressbar'; import {PopoverModule} from 'ngx-bootstrap/popover'; import {QueryInterfacesComponent} from './query-interfaces/query-interfaces.component'; @@ -158,7 +159,8 @@ import {PolyalgComponent, ScrollToDirective} from './querying/polyalg/polyalg.co ProgressComponent, ProgressBarComponent, CollapseDirective, - ButtonToolbarComponent + ButtonToolbarComponent, + PreviewSelectionComponent ], declarations: [ EditColumnsComponent, @@ -187,7 +189,7 @@ import {PolyalgComponent, ScrollToDirective} from './querying/polyalg/polyalg.co DockerconfigComponent, EditEntityComponent, PolyalgComponent, - ScrollToDirective + ScrollToDirective, ], exports: [] }) From 4c1575e7b173ea653d8d64a83a9ced15223ad637 Mon Sep 17 00:00:00 2001 From: romanost03 Date: Sat, 14 Jun 2025 15:45:54 +0200 Subject: [PATCH 14/33] Add new context for metadata change instead of deploying --- .../views/adapters/adapters.component.html | 2 +- src/app/views/adapters/adapters.component.ts | 26 +++++++++++------ .../metadata-tree.component.html | 2 +- .../metadata-tree.component.scss | 3 ++ .../metadata-tree/metadata-tree.component.ts | 8 ++++++ .../preview-selection.component.html | 3 +- .../preview-selection.component.ts | 28 +++++++++++++++---- 7 files changed, 55 insertions(+), 17 deletions(-) diff --git a/src/app/views/adapters/adapters.component.html b/src/app/views/adapters/adapters.component.html index a4b7013d..5a4719f6 100644 --- a/src/app/views/adapters/adapters.component.html +++ b/src/app/views/adapters/adapters.component.html @@ -80,7 +80,7 @@

Adapters

color="warning" class="mx-2 pulse clickable" cTooltip="Metadata changed - click to review" - (click)="alert('Data is now manipulated !')">❗ + (click)="previewAndChange(adapter.name)">❗ + + diff --git a/src/app/views/preview-selection/preview-selection.component.ts b/src/app/views/preview-selection/preview-selection.component.ts index 9070921f..7a98e31d 100644 --- a/src/app/views/preview-selection/preview-selection.component.ts +++ b/src/app/views/preview-selection/preview-selection.component.ts @@ -29,6 +29,7 @@ export class PreviewSelectionComponent { private readonly _router = inject(Router); private ctx = null; + mode!: null; formData: FormData = new FormData(); pendingFiles!: Map; @@ -37,6 +38,7 @@ export class PreviewSelectionComponent { metadata: AbstractNode = null; preview: Record | any[] = {}; + adapterInfo?: null; selected: Set = new Set(); @@ -52,12 +54,23 @@ export class PreviewSelectionComponent { return; } + this.mode = this.ctx.mode; + console.log(this.mode); + + if (this.mode === 'deploy') { + this.formData = this.ctx.formData; + this.pendingFiles = this.ctx.files; + this.metadata = this.ctx.metadata; + this.preview = this.ctx.preview; + this.adapter = this.ctx.adapter; + } else { + this.metadata = this.ctx.metadata; + this.preview = this.ctx.preview; + this.adapterInfo = this.ctx.adapterInfo; + } + + - this.formData = this.ctx.formData; - this.pendingFiles = this.ctx.files; - this.metadata = this.ctx.metadata; - this.preview = this.ctx.preview; - this.adapter = this.ctx.adapter; /*const opener = (window.opener as any); @@ -158,6 +171,11 @@ export class PreviewSelectionComponent { } }); }*/ + + sendAck(): void { + + } + sendMetadata(): void { const newFormData = new FormData(); From 4af9cf3a14f540fda9f2a98dd56988d942804fc9 Mon Sep 17 00:00:00 2001 From: romanost03 Date: Sun, 15 Jun 2025 17:53:23 +0200 Subject: [PATCH 15/33] Sending changed metadata (ack) correct to backend. --- src/app/services/crud.service.ts | 4 +-- .../metadata-tree.component.html | 1 - .../preview-selection.component.ts | 30 ++++++++++++++----- 3 files changed, 25 insertions(+), 10 deletions(-) diff --git a/src/app/services/crud.service.ts b/src/app/services/crud.service.ts index 29bea912..ae00a999 100644 --- a/src/app/services/crud.service.ts +++ b/src/app/services/crud.service.ts @@ -305,8 +305,8 @@ export class CrudService { return this._http.get(`${this.httpUrl}/metadataChange/${request}`, this.httpOptions); } - metadataAck(request: string) { - return this._http.post(`${this.httpUrl}/metadataAck/${request}`, this.httpOptions); + metadataAck(body: { uniqueName: string, selectedPaths: string[] }) { + return this._http.post(`${this.httpUrl}/metadataAck/${body.uniqueName}`, body, this.httpOptions); } /** diff --git a/src/app/views/preview-selection/metadata-tree/metadata-tree.component.html b/src/app/views/preview-selection/metadata-tree/metadata-tree.component.html index 8a9245bf..3477ca92 100644 --- a/src/app/views/preview-selection/metadata-tree/metadata-tree.component.html +++ b/src/app/views/preview-selection/metadata-tree/metadata-tree.component.html @@ -24,7 +24,6 @@ [node]="child" [path]="node.type === 'column' ? path : (path ? path + '.' + node.name : node.name)" (columnToggle)="columnToggle.emit($event)"> - diff --git a/src/app/views/preview-selection/preview-selection.component.ts b/src/app/views/preview-selection/preview-selection.component.ts index 7a98e31d..ef41c62f 100644 --- a/src/app/views/preview-selection/preview-selection.component.ts +++ b/src/app/views/preview-selection/preview-selection.component.ts @@ -38,7 +38,7 @@ export class PreviewSelectionComponent { metadata: AbstractNode = null; preview: Record | any[] = {}; - adapterInfo?: null; + adapterInfo: string = null; selected: Set = new Set(); @@ -46,7 +46,7 @@ export class PreviewSelectionComponent { ngOnInit() { - this.ctx = this._nav.context; + this.ctx = this._nav.context; if (!this.ctx) { @@ -66,13 +66,10 @@ export class PreviewSelectionComponent { } else { this.metadata = this.ctx.metadata; this.preview = this.ctx.preview; - this.adapterInfo = this.ctx.adapterInfo; + this.adapterInfo = this.ctx.adapterInfo.uniqueName; } - - - /*const opener = (window.opener as any); this.formData = opener?.pendingFormData; this.pendingFiles = opener.pendingFiles as Map; @@ -173,13 +170,32 @@ export class PreviewSelectionComponent { }*/ sendAck(): void { + const selected: string[] = Array.from(this.selected); + + const payload = { + uniqueName: this.adapterInfo, + selectedPaths: selected + }; + + this._crud.metadataAck(payload).subscribe( + { + next: () => { + alert('ACK was send.'); + this.close(); + }, + error: err => { + alert('ACK was not send successfully!'); + } + } + ); + } sendMetadata(): void { const newFormData = new FormData(); - const files: Map = this.ctx.files; + const files: Map = this.ctx.files; for (const [field, file] of this.pendingFiles) { From 6b738264491fafcb5aaf11a3e82ff2878699fd4f Mon Sep 17 00:00:00 2001 From: romanost03 Date: Mon, 16 Jun 2025 21:39:36 +0200 Subject: [PATCH 16/33] Add code for manually reconfigure metadata of a source. --- src/app/services/crud.service.ts | 5 ++ .../services/preview-navigation.service.ts | 2 +- src/app/views/adapters/adapters.component.ts | 51 ++++++++++++++----- .../preview-selection.component.html | 1 + .../preview-selection.component.ts | 2 + 5 files changed, 46 insertions(+), 15 deletions(-) diff --git a/src/app/services/crud.service.ts b/src/app/services/crud.service.ts index ae00a999..2bd4c8aa 100644 --- a/src/app/services/crud.service.ts +++ b/src/app/services/crud.service.ts @@ -306,9 +306,14 @@ export class CrudService { } metadataAck(body: { uniqueName: string, selectedPaths: string[] }) { + console.log(body); return this._http.post(`${this.httpUrl}/metadataAck/${body.uniqueName}`, body, this.httpOptions); } + metadataConfiguration(request: string) { + return this._http.get(`${this.httpUrl}/metadataConfiguration/${request}`, this.httpOptions); + } + /** * Add a primary key to a table */ diff --git a/src/app/services/preview-navigation.service.ts b/src/app/services/preview-navigation.service.ts index 1ab341eb..e4a75683 100644 --- a/src/app/services/preview-navigation.service.ts +++ b/src/app/services/preview-navigation.service.ts @@ -4,7 +4,7 @@ import {AdapterModel, AdapterType, PolyMap} from '../views/adapters/adapter.mode import {DeployMode} from '../models/catalog.model'; -export type PreviewMode = 'deploy' | 'change'; +export type PreviewMode = 'deploy' | 'change' | 'config'; export interface PreviewContext { mode: PreviewMode; diff --git a/src/app/views/adapters/adapters.component.ts b/src/app/views/adapters/adapters.component.ts index 07c32a2a..476f5822 100644 --- a/src/app/views/adapters/adapters.component.ts +++ b/src/app/views/adapters/adapters.component.ts @@ -25,19 +25,15 @@ import { Validators } from '@angular/forms'; import { + AbstractNode, PathAccessRequest, PreviewResult, - RelationalResult, - AbstractNode + RelationalResult } from '../../components/data-view/models/result-set.model'; import {PreviewRequest} from '../../models/ui-request.model'; import {Subscription} from 'rxjs'; import {CatalogService} from '../../services/catalog.service'; -import { - AdapterSettingModel, - AdapterTemplateModel, - DeployMode -} from '../../models/catalog.model'; +import {AdapterSettingModel, AdapterTemplateModel, DeployMode} from '../../models/catalog.model'; import {LeftSidebarService} from '../../components/left-sidebar/left-sidebar.service'; import {SchemaDiscoveryService} from '../../services/schema-discovery.service'; import {PreviewMode, PreviewNavigationService} from '../../services/preview-navigation.service'; @@ -212,15 +208,42 @@ export class AdaptersComponent implements OnInit, OnDestroy { } initAdapterSettingsConfigureModal(adapter: AdapterModel) { - const allSettings = this._catalog.getAdapterTemplate(adapter.adapterName, adapter.type); - - const current = Adapter.from(allSettings, adapter, Task.CHANGE); - this.adapter.set(current); - this.activeMode.set(current.modes[0]); - this.handshaking = false; - this.modalActive = true; + if (adapter.type === AdapterType.SOURCE) { + const uniqueName = adapter.name.toLowerCase(); + this.getMetaConfiguration(uniqueName); + } else { + const allSettings = this._catalog.getAdapterTemplate(adapter.adapterName, adapter.type); + const current = Adapter.from(allSettings, adapter, Task.CHANGE); + this.adapter.set(current); + this.activeMode.set(current.modes[0]); + this.handshaking = false; + this.modalActive = true; + } + } + + private getMetaConfiguration(uniqueName: string) { + this._crud.metadataConfiguration(uniqueName).subscribe({ + next: (preview: PreviewResult) => { + this.openPreview( + 'config', + null, + preview.preview, + typeof preview.metadata === 'string' + ? JSON.parse(preview.metadata) + : preview.metadata, + null, + null, + null, + { + uniqueName: uniqueName + } + ); + } + } + ); } + private openPreview( mode: PreviewMode, adapter: AdapterModel, diff --git a/src/app/views/preview-selection/preview-selection.component.html b/src/app/views/preview-selection/preview-selection.component.html index 5b5a9ee8..481f306b 100644 --- a/src/app/views/preview-selection/preview-selection.component.html +++ b/src/app/views/preview-selection/preview-selection.component.html @@ -52,6 +52,7 @@

Metadata

+
diff --git a/src/app/views/preview-selection/preview-selection.component.ts b/src/app/views/preview-selection/preview-selection.component.ts index ef41c62f..3d64098b 100644 --- a/src/app/views/preview-selection/preview-selection.component.ts +++ b/src/app/views/preview-selection/preview-selection.component.ts @@ -188,7 +188,9 @@ export class PreviewSelectionComponent { } } ); + } + sendConfigChange(): void { } From 19b7c56c146b46cf9d2fdf58f3a94df1ed4a4f77 Mon Sep 17 00:00:00 2001 From: romanost03 Date: Mon, 16 Jun 2025 22:27:02 +0200 Subject: [PATCH 17/33] Code for collecting aliases when deploying new source. --- src/app/views/adapters/adapter.model.ts | 4 +++- .../preview-selection.component.ts | 16 ++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/app/views/adapters/adapter.model.ts b/src/app/views/adapters/adapter.model.ts index 93e7b3f7..4dcbae0b 100644 --- a/src/app/views/adapters/adapter.model.ts +++ b/src/app/views/adapters/adapter.model.ts @@ -10,8 +10,9 @@ export class AdapterModel extends IdEntity { indexMethods: IndexMethodModel[]; readonly metadata?: string[]; metadataChanged?: boolean; + columnAliases?: Map; - constructor(uniqueName: string, adapterName: string, settings: Map, persistent: boolean, type: AdapterType, deployMode: DeployMode, metadata?: string[], metadataChanged?: boolean) { + constructor(uniqueName: string, adapterName: string, settings: Map, persistent: boolean, type: AdapterType, deployMode: DeployMode, metadata?: string[], metadataChanged?: boolean, aliase?: Map) { super(-1, uniqueName); this.adapterName = adapterName; this.settings = settings; @@ -20,6 +21,7 @@ export class AdapterModel extends IdEntity { this.mode = deployMode; this.metadata = metadata; this.metadataChanged = metadataChanged; + this.columnAliases = aliase; } } diff --git a/src/app/views/preview-selection/preview-selection.component.ts b/src/app/views/preview-selection/preview-selection.component.ts index 3d64098b..eb8eafb8 100644 --- a/src/app/views/preview-selection/preview-selection.component.ts +++ b/src/app/views/preview-selection/preview-selection.component.ts @@ -196,6 +196,7 @@ export class PreviewSelectionComponent { sendMetadata(): void { + const aliases: Map = new Map(); const newFormData = new FormData(); const files: Map = this.ctx.files; @@ -209,6 +210,9 @@ export class PreviewSelectionComponent { (this.adapter as any).metadata = Array.from(this.selected); + this.collectAliases(this.metadata, aliases); + (this.adapter as any).columnAliases = aliases; + const firstFileName = files.keys().next().value as string; if (this.adapter.settings.has('directory')) { @@ -234,6 +238,18 @@ export class PreviewSelectionComponent { } + private collectAliases(node: AbstractNode, out: Map, path: string='') { + const currentPath = path ? `${path}.${node.name}` : node.name; + + if (node.type === 'column') { + const alias = node.properties?.['alias']; + if (alias) { + out[currentPath] = alias; + } + } + node.children?.forEach(c => this.collectAliases(c, out, currentPath)); + } + } export class Node implements AbstractNode { From 80769488a26117637c9c94ba931e691263b1912c Mon Sep 17 00:00:00 2001 From: romanost03 Date: Fri, 20 Jun 2025 18:31:44 +0200 Subject: [PATCH 18/33] Collecting metadata automatically when it is ghost-node. --- .../metadata-tree.component.html | 3 ++- .../metadata-tree/metadata-tree.component.ts | 27 +++++++++++++++++++ .../preview-selection.component.html | 3 ++- .../preview-selection.component.ts | 9 ++++++- 4 files changed, 39 insertions(+), 3 deletions(-) diff --git a/src/app/views/preview-selection/metadata-tree/metadata-tree.component.html b/src/app/views/preview-selection/metadata-tree/metadata-tree.component.html index 3477ca92..68d832db 100644 --- a/src/app/views/preview-selection/metadata-tree/metadata-tree.component.html +++ b/src/app/views/preview-selection/metadata-tree/metadata-tree.component.html @@ -23,7 +23,8 @@ *ngFor="let child of node.children" [node]="child" [path]="node.type === 'column' ? path : (path ? path + '.' + node.name : node.name)" - (columnToggle)="columnToggle.emit($event)"> + (columnToggle)="columnToggle.emit($event)" + (autoSelectRemoved)="autoSelectRemoved.emit($event)"> diff --git a/src/app/views/preview-selection/metadata-tree/metadata-tree.component.ts b/src/app/views/preview-selection/metadata-tree/metadata-tree.component.ts index c49281c8..26a13217 100644 --- a/src/app/views/preview-selection/metadata-tree/metadata-tree.component.ts +++ b/src/app/views/preview-selection/metadata-tree/metadata-tree.component.ts @@ -19,6 +19,33 @@ export class MetadataTreeComponent { @Input() node!: Node; @Input() path = ''; @Output() columnToggle = new EventEmitter<{ fullKey: string, checked: boolean }>(); + @Output() autoSelectRemoved = new EventEmitter>(); + + ngOnInit() { + const removed = new Set(); + this.collectRemoved(this.node, [], removed); + console.log('removed →', removed); + if (removed.size) { + this.autoSelectRemoved.emit(removed); + } + } + + private collectRemoved(node: AbstractNode, + pathAcc: string[], + bucket: Set): void { + + const nextPath = [...pathAcc, node.name]; + const isLeaf = !node.children || node.children.length === 0; + + if (isLeaf && node.properties?.['diff'] === 'REMOVED') { + bucket.add(nextPath.join('.')); + } + + node.children?.forEach(child => + this.collectRemoved(child, nextPath, bucket)); + } + + toggleColumn(fullKey: string, checked: boolean) { this.columnToggle.emit({fullKey, checked}); diff --git a/src/app/views/preview-selection/preview-selection.component.html b/src/app/views/preview-selection/preview-selection.component.html index 481f306b..22940e3f 100644 --- a/src/app/views/preview-selection/preview-selection.component.html +++ b/src/app/views/preview-selection/preview-selection.component.html @@ -44,7 +44,8 @@

Metadata

+ (columnToggle)="onColumnToggle($event)" + (autoSelectRemoved)="onAutoSelect($event)"> diff --git a/src/app/views/preview-selection/preview-selection.component.ts b/src/app/views/preview-selection/preview-selection.component.ts index eb8eafb8..e81ca816 100644 --- a/src/app/views/preview-selection/preview-selection.component.ts +++ b/src/app/views/preview-selection/preview-selection.component.ts @@ -153,6 +153,10 @@ export class PreviewSelectionComponent { console.log(this.selected); } + onAutoSelect(paths: string[]): void { + paths.forEach(p => this.selected.add(p)); + } + /*sendMetadata() { (this.adapter as any).metadata = Array.from(this.selected); @@ -171,11 +175,14 @@ export class PreviewSelectionComponent { sendAck(): void { const selected: string[] = Array.from(this.selected); + console.log(selected); const payload = { uniqueName: this.adapterInfo, - selectedPaths: selected + selectedPaths: Array.from(selected) }; + console.log(payload.selectedPaths); + this._crud.metadataAck(payload).subscribe( { From 0d9131c9fca634453855b16d9824896875c0cc95 Mon Sep 17 00:00:00 2001 From: romanost03 Date: Sat, 21 Jun 2025 19:07:39 +0200 Subject: [PATCH 19/33] Adding and removing metadata are now treated separately. --- src/app/services/crud.service.ts | 2 +- .../metadata-tree.component.html | 5 +-- .../metadata-tree/metadata-tree.component.ts | 45 ++++++++++--------- .../preview-selection.component.ts | 39 +++++++++++++--- 4 files changed, 61 insertions(+), 30 deletions(-) diff --git a/src/app/services/crud.service.ts b/src/app/services/crud.service.ts index 2bd4c8aa..8b19a3e6 100644 --- a/src/app/services/crud.service.ts +++ b/src/app/services/crud.service.ts @@ -305,7 +305,7 @@ export class CrudService { return this._http.get(`${this.httpUrl}/metadataChange/${request}`, this.httpOptions); } - metadataAck(body: { uniqueName: string, selectedPaths: string[] }) { + metadataAck(body: { uniqueName: string, addedPaths: string[], removedPaths: string[] }) { console.log(body); return this._http.post(`${this.httpUrl}/metadataAck/${body.uniqueName}`, body, this.httpOptions); } diff --git a/src/app/views/preview-selection/metadata-tree/metadata-tree.component.html b/src/app/views/preview-selection/metadata-tree/metadata-tree.component.html index 68d832db..1fb16da1 100644 --- a/src/app/views/preview-selection/metadata-tree/metadata-tree.component.html +++ b/src/app/views/preview-selection/metadata-tree/metadata-tree.component.html @@ -4,7 +4,7 @@ + (change)="toggleColumn(path + '.' + node.name, cb.checked, node.properties?.diff, node.type)"> {{ node.name }} + (columnToggle)="columnToggle.emit($event)"> diff --git a/src/app/views/preview-selection/metadata-tree/metadata-tree.component.ts b/src/app/views/preview-selection/metadata-tree/metadata-tree.component.ts index 26a13217..85cefcdb 100644 --- a/src/app/views/preview-selection/metadata-tree/metadata-tree.component.ts +++ b/src/app/views/preview-selection/metadata-tree/metadata-tree.component.ts @@ -18,37 +18,39 @@ import { AbstractNode } from '../../../components/data-view/models/result-set.mo export class MetadataTreeComponent { @Input() node!: Node; @Input() path = ''; - @Output() columnToggle = new EventEmitter<{ fullKey: string, checked: boolean }>(); - @Output() autoSelectRemoved = new EventEmitter>(); + @Output() columnToggle = new EventEmitter<{ fullKey: string, checked: boolean, diff?, type? }>(); + @Output() autoSelectRemoved = new EventEmitter(); - ngOnInit() { + ngOnInit(): void { const removed = new Set(); this.collectRemoved(this.node, [], removed); - console.log('removed →', removed); if (removed.size) { - this.autoSelectRemoved.emit(removed); + this.autoSelectRemoved.emit([...removed]); } } - private collectRemoved(node: AbstractNode, - pathAcc: string[], - bucket: Set): void { + private collectRemoved(n: AbstractNode, + path: string[], + out: Set): void { - const nextPath = [...pathAcc, node.name]; - const isLeaf = !node.children || node.children.length === 0; + const next = [...path, n.name]; + const isLeaf = !n.children || n.children.length === 0; + const isGhost = n.type === 'ghost'; - if (isLeaf && node.properties?.['diff'] === 'REMOVED') { - bucket.add(nextPath.join('.')); + if (isGhost) { + out.add(next.join('.')); } - node.children?.forEach(child => - this.collectRemoved(child, nextPath, bucket)); + n.children?.forEach(c => this.collectRemoved(c, next, out)); } - toggleColumn(fullKey: string, checked: boolean) { - this.columnToggle.emit({fullKey, checked}); + + + + toggleColumn(fullKey: string, checked: boolean, diff, type) { + this.columnToggle.emit({fullKey, checked, diff, type}); } getAlias(node: AbstractNode): string { @@ -75,11 +77,14 @@ export class MetadataTreeComponent { } getNodeClass(node: AbstractNode): string { - switch (node.properties?.['diff']) { - case 'ADDED': return 'node-added'; - case 'REMOVED': return 'node-removed'; - default: return ''; + if (node.properties?.['diff'] === 'ADDED') { + return 'node-added'; + } else if (node.properties?.['diff'] === 'REMOVED' || node.type === 'ghost') { + return 'node-removed'; + } else { + return ''; } + } diff --git a/src/app/views/preview-selection/preview-selection.component.ts b/src/app/views/preview-selection/preview-selection.component.ts index e81ca816..31957547 100644 --- a/src/app/views/preview-selection/preview-selection.component.ts +++ b/src/app/views/preview-selection/preview-selection.component.ts @@ -42,6 +42,12 @@ export class PreviewSelectionComponent { selected: Set = new Set(); + + added: Set = new Set(); + removed: Set = new Set(); + + + ready = false; ngOnInit() { @@ -148,13 +154,20 @@ export class PreviewSelectionComponent { return rows && rows.length ? Object.keys(rows[0]) : []; } - onColumnToggle(e: { fullKey: string; checked: boolean }) { - e.checked ? this.selected.add(e.fullKey) : this.selected.delete(e.fullKey); - console.log(this.selected); + onColumnToggle(e: ColumnToggleEvent) { + console.log(e.diff); + if (e.diff === 'ADDED') { + e.checked ? this.added.add(e.fullKey) : this.added.delete(e.fullKey); + console.log(this.added); + } else { + e.checked ? this.selected.add(e.fullKey) : this.selected.delete(e.fullKey); + console.log(this.selected); + } } onAutoSelect(paths: string[]): void { - paths.forEach(p => this.selected.add(p)); + paths.forEach(p => this.removed.add(p)); + console.log(this.removed); } /*sendMetadata() { @@ -177,11 +190,17 @@ export class PreviewSelectionComponent { const selected: string[] = Array.from(this.selected); console.log(selected); + const addedPaths: string[] = Array.from(this.added); + const removedPaths: string[] = Array.from(this.removed); + + const payload = { uniqueName: this.adapterInfo, - selectedPaths: Array.from(selected) + addedPaths: Array.from(addedPaths), + removedPaths: Array.from(removedPaths) }; - console.log(payload.selectedPaths); + console.log(payload.addedPaths); + console.log(payload.removedPaths); this._crud.metadataAck(payload).subscribe( @@ -192,6 +211,7 @@ export class PreviewSelectionComponent { }, error: err => { alert('ACK was not send successfully!'); + this.close(); } } ); @@ -282,3 +302,10 @@ export class Node implements AbstractNode { return this.properties[key]; } } + +interface ColumnToggleEvent { + fullKey: string; + checked: boolean; + diff?: 'ADDED' | 'REMOVED'; + type?: 'ghost' | 'table' | 'column'; +} From 77aaeffb99ea615caa3248a87fa817ef5bbfe86b Mon Sep 17 00:00:00 2001 From: romanost03 Date: Sat, 21 Jun 2025 21:45:14 +0200 Subject: [PATCH 20/33] Metadata for reconfiguration are collected and sent. --- .../data-view/models/result-set.model.ts | 1 + src/app/services/crud.service.ts | 4 +++ src/app/views/adapters/adapters.component.ts | 1 + .../metadata-tree.component.html | 3 +- .../preview-selection.component.ts | 33 +++++++++++++++++-- 5 files changed, 38 insertions(+), 4 deletions(-) diff --git a/src/app/components/data-view/models/result-set.model.ts b/src/app/components/data-view/models/result-set.model.ts index 96bec34c..02ada8af 100644 --- a/src/app/components/data-view/models/result-set.model.ts +++ b/src/app/components/data-view/models/result-set.model.ts @@ -398,6 +398,7 @@ export interface AbstractNode { name: string; children?: AbstractNode[]; properties?: { [key: string]: any }; + isSelected?: boolean; addChild(node: AbstractNode): void; addProperty(key: string, value: any): void; diff --git a/src/app/services/crud.service.ts b/src/app/services/crud.service.ts index 8b19a3e6..df9d8ddb 100644 --- a/src/app/services/crud.service.ts +++ b/src/app/services/crud.service.ts @@ -314,6 +314,10 @@ export class CrudService { return this._http.get(`${this.httpUrl}/metadataConfiguration/${request}`, this.httpOptions); } + setMetaConfiguration(body: {uniqueName: string, selected: string[]}) { + return this._http.post(`${this.httpUrl}/setMetaConfig`, body, this.httpOptions); + } + /** * Add a primary key to a table */ diff --git a/src/app/views/adapters/adapters.component.ts b/src/app/views/adapters/adapters.component.ts index 476f5822..c1a15ae3 100644 --- a/src/app/views/adapters/adapters.component.ts +++ b/src/app/views/adapters/adapters.component.ts @@ -31,6 +31,7 @@ import { RelationalResult } from '../../components/data-view/models/result-set.model'; import {PreviewRequest} from '../../models/ui-request.model'; +import {Node} from '../preview-selection/preview-selection.component'; import {Subscription} from 'rxjs'; import {CatalogService} from '../../services/catalog.service'; import {AdapterSettingModel, AdapterTemplateModel, DeployMode} from '../../models/catalog.model'; diff --git a/src/app/views/preview-selection/metadata-tree/metadata-tree.component.html b/src/app/views/preview-selection/metadata-tree/metadata-tree.component.html index 1fb16da1..fbbe0932 100644 --- a/src/app/views/preview-selection/metadata-tree/metadata-tree.component.html +++ b/src/app/views/preview-selection/metadata-tree/metadata-tree.component.html @@ -3,7 +3,8 @@ {{ node.name }} - {{ node.name }} diff --git a/src/app/views/preview-selection/preview-selection.component.ts b/src/app/views/preview-selection/preview-selection.component.ts index 31957547..165aad2b 100644 --- a/src/app/views/preview-selection/preview-selection.component.ts +++ b/src/app/views/preview-selection/preview-selection.component.ts @@ -73,8 +73,11 @@ export class PreviewSelectionComponent { this.metadata = this.ctx.metadata; this.preview = this.ctx.preview; this.adapterInfo = this.ctx.adapterInfo.uniqueName; + console.log(this.metadata); } + this.collectSelected(this.metadata); + /*const opener = (window.opener as any); this.formData = opener?.pendingFormData; @@ -145,6 +148,14 @@ export class PreviewSelectionComponent { return node; } + collectSelected(node: AbstractNode, path: string[] = []) { + const fullPath = [...path, node.name]; + if (node.isSelected) { + this.selected.add(fullPath.join('.')); + } + node.children?.forEach(c => this.collectSelected(c, fullPath)); + } + getPreviewKeys(): string[] { return Object.keys(this.preview); } @@ -187,9 +198,6 @@ export class PreviewSelectionComponent { }*/ sendAck(): void { - const selected: string[] = Array.from(this.selected); - console.log(selected); - const addedPaths: string[] = Array.from(this.added); const removedPaths: string[] = Array.from(this.removed); @@ -218,7 +226,25 @@ export class PreviewSelectionComponent { } sendConfigChange(): void { + const selected: string[] = Array.from(this.selected); + const payload = { + uniqueName: this.adapterInfo, + selected: selected + }; + + + console.log(selected); + this._crud.setMetaConfiguration(payload).subscribe({ + next: () => { + alert('Config changed successfully!'); + this.close(); + }, + error: err => { + alert('Config changed failed!'); + this.close(); + } + }); } sendMetadata(): void { @@ -284,6 +310,7 @@ export class Node implements AbstractNode { name: string; children: AbstractNode[] = []; properties: { [key: string]: any } = {}; + isSelected: boolean; constructor(type: string, name: string) { this.type = type; From 4da1f1e190ca791849464253503e448da657ece1 Mon Sep 17 00:00:00 2001 From: romanost03 Date: Mon, 23 Jun 2025 20:41:37 +0200 Subject: [PATCH 21/33] Differ between critical metadata changes and warnings of change --- src/app/services/metadata-polling.service.ts | 6 +++--- src/app/views/adapters/adapter.model.ts | 6 +++--- src/app/views/adapters/adapters.component.html | 4 ++-- 3 files changed, 8 insertions(+), 8 deletions(-) diff --git a/src/app/services/metadata-polling.service.ts b/src/app/services/metadata-polling.service.ts index 77b8a2a8..63ec6d87 100644 --- a/src/app/services/metadata-polling.service.ts +++ b/src/app/services/metadata-polling.service.ts @@ -7,7 +7,7 @@ import { CatalogService } from './catalog.service'; import { AdapterModel } from '../views/adapters/adapter.model'; export interface MetadataStatusResponse { - changed: boolean; + changed: 'OK' | 'WARNING' | 'CRITICAL' | null; } @Injectable({providedIn: 'root'}) @@ -42,9 +42,9 @@ export class MetadataPollingService { this._crud.metadataStatus(adapter.name).subscribe({ next: (res: Object) => { const casted = res as MetadataStatusResponse; - + console.log('casted', casted); this._catalog.updateAdapterFlag(adapter.name, { - metadataChanged: casted.changed + metadataStatus: casted.changed }); }, error: err => console.error('[metadata-poll]', err) diff --git a/src/app/views/adapters/adapter.model.ts b/src/app/views/adapters/adapter.model.ts index 4dcbae0b..4ecd21cc 100644 --- a/src/app/views/adapters/adapter.model.ts +++ b/src/app/views/adapters/adapter.model.ts @@ -9,10 +9,10 @@ export class AdapterModel extends IdEntity { readonly mode: DeployMode; indexMethods: IndexMethodModel[]; readonly metadata?: string[]; - metadataChanged?: boolean; + metadataStatus?: 'OK' | 'WARNING' | 'CRITICAL' | null; columnAliases?: Map; - constructor(uniqueName: string, adapterName: string, settings: Map, persistent: boolean, type: AdapterType, deployMode: DeployMode, metadata?: string[], metadataChanged?: boolean, aliase?: Map) { + constructor(uniqueName: string, adapterName: string, settings: Map, persistent: boolean, type: AdapterType, deployMode: DeployMode, metadata?: string[], metadataStatus?: 'OK' | 'WARNING' | 'CRITICAL' | null, aliase?: Map) { super(-1, uniqueName); this.adapterName = adapterName; this.settings = settings; @@ -20,7 +20,7 @@ export class AdapterModel extends IdEntity { this.type = type; this.mode = deployMode; this.metadata = metadata; - this.metadataChanged = metadataChanged; + this.metadataStatus = metadataStatus; this.columnAliases = aliase; } } diff --git a/src/app/views/adapters/adapters.component.html b/src/app/views/adapters/adapters.component.html index 5a4719f6..f4ac155a 100644 --- a/src/app/views/adapters/adapters.component.html +++ b/src/app/views/adapters/adapters.component.html @@ -76,8 +76,8 @@

Adapters

role="status"> ❗ From 8a2598f98c167f7a8dc9e9584910b7b7ca88bfd7 Mon Sep 17 00:00:00 2001 From: romanost03 Date: Mon, 30 Jun 2025 16:52:35 +0200 Subject: [PATCH 22/33] Add code for preview document source and adding logs for every source. --- .../data-view/models/result-set.model.ts | 25 ++++ .../services/preview-navigation.service.ts | 3 +- src/app/views/adapters/adapters.component.ts | 25 ++-- .../views/doc-card/doc-card.component.html | 36 +++++ .../views/doc-card/doc-card.component.scss | 31 +++++ src/app/views/doc-card/doc-card.component.ts | 53 ++++++++ .../metadata-tree.component.html | 21 +-- .../preview-selection.component.html | 126 ++++++++++++------ .../preview-selection.component.scss | 20 +++ .../preview-selection.component.ts | 116 ++++++++++++++-- 10 files changed, 386 insertions(+), 70 deletions(-) create mode 100644 src/app/views/doc-card/doc-card.component.html create mode 100644 src/app/views/doc-card/doc-card.component.scss create mode 100644 src/app/views/doc-card/doc-card.component.ts diff --git a/src/app/components/data-view/models/result-set.model.ts b/src/app/components/data-view/models/result-set.model.ts index 02ada8af..5c661e1c 100644 --- a/src/app/components/data-view/models/result-set.model.ts +++ b/src/app/components/data-view/models/result-set.model.ts @@ -86,6 +86,26 @@ export class DashboardSet { export class PreviewResult { metadata: string; preview: any[]; + history?: ChangeLogEntry[]; +} + +export interface ChangeLogEntry { + adapterName: string; + timestamp: string; + severity: ChangeStatus; + messages: string[]; +} + +export enum ChangeStatus { + CRITICAL = 'CRITICAL', + WARNING = 'WARNING', + OK = 'OK' +} + +export interface DiffResult { + added: string[]; + removed: string[]; + changed: string[]; } @@ -399,9 +419,14 @@ export interface AbstractNode { children?: AbstractNode[]; properties?: { [key: string]: any }; isSelected?: boolean; + jsonPath?: string; + cardCandidate?: boolean; + valueType?: string; addChild(node: AbstractNode): void; + addProperty(key: string, value: any): void; + getProperty(key: string): string | null; } diff --git a/src/app/services/preview-navigation.service.ts b/src/app/services/preview-navigation.service.ts index e4a75683..998548e9 100644 --- a/src/app/services/preview-navigation.service.ts +++ b/src/app/services/preview-navigation.service.ts @@ -1,5 +1,5 @@ import {Injectable} from '@angular/core'; -import {AbstractNode} from '../components/data-view/models/result-set.model'; +import {AbstractNode, ChangeLogEntry} from '../components/data-view/models/result-set.model'; import {AdapterModel, AdapterType, PolyMap} from '../views/adapters/adapter.model'; import {DeployMode} from '../models/catalog.model'; @@ -21,6 +21,7 @@ export interface PreviewContext { mode: DeployMode; persistent: boolean; }>; + changeLog?: ChangeLogEntry[]; } @Injectable({providedIn: 'root'}) diff --git a/src/app/views/adapters/adapters.component.ts b/src/app/views/adapters/adapters.component.ts index c1a15ae3..ee8f315a 100644 --- a/src/app/views/adapters/adapters.component.ts +++ b/src/app/views/adapters/adapters.component.ts @@ -25,13 +25,13 @@ import { Validators } from '@angular/forms'; import { - AbstractNode, + AbstractNode, ChangeLogEntry, PathAccessRequest, PreviewResult, RelationalResult } from '../../components/data-view/models/result-set.model'; import {PreviewRequest} from '../../models/ui-request.model'; -import {Node} from '../preview-selection/preview-selection.component'; +import {Node, reviveTree} from '../preview-selection/preview-selection.component'; import {Subscription} from 'rxjs'; import {CatalogService} from '../../services/catalog.service'; import {AdapterSettingModel, AdapterTemplateModel, DeployMode} from '../../models/catalog.model'; @@ -225,6 +225,7 @@ export class AdaptersComponent implements OnInit, OnDestroy { private getMetaConfiguration(uniqueName: string) { this._crud.metadataConfiguration(uniqueName).subscribe({ next: (preview: PreviewResult) => { + console.log(preview); this.openPreview( 'config', null, @@ -237,7 +238,8 @@ export class AdaptersComponent implements OnInit, OnDestroy { null, { uniqueName: uniqueName - } + }, + preview.history ); } } @@ -259,7 +261,9 @@ export class AdaptersComponent implements OnInit, OnDestroy { type: AdapterType; mode: DeployMode; persistent: boolean; - }>) { + }>, + changeLog?: ChangeLogEntry[] + ) { this.nav.setContext({ mode: mode, adapter, @@ -268,7 +272,8 @@ export class AdaptersComponent implements OnInit, OnDestroy { formData: fd, files, adapterSettings, - adapterInfo + adapterInfo, + changeLog }); this._router.navigate(['/views/preview-selection']); @@ -356,13 +361,17 @@ export class AdaptersComponent implements OnInit, OnDestroy { const win = window.open('/#/preview-selection', 'popup', 'width=1000,height=700');*/ + const metaRoot = reviveTree( + typeof preview.metadata === 'string' + ? JSON.parse(preview.metadata) + : preview.metadata + ); + this.openPreview( 'deploy', deploy, preview.preview, - typeof preview.metadata === 'string' - ? JSON.parse(preview.metadata) - : preview.metadata, + metaRoot, fd, this.files, new PolyMap(Object.entries(settingsObj)), diff --git a/src/app/views/doc-card/doc-card.component.html b/src/app/views/doc-card/doc-card.component.html new file mode 100644 index 00000000..35417726 --- /dev/null +++ b/src/app/views/doc-card/doc-card.component.html @@ -0,0 +1,36 @@ + + + + {{ prettifyKey(node.name) }} + + + + + + + +
+ {{ prettifyKey(row.k) }} : + + + {{ row.v }} + + + + [ … ] + { … } + + +
+ + + + + + +
+
diff --git a/src/app/views/doc-card/doc-card.component.scss b/src/app/views/doc-card/doc-card.component.scss new file mode 100644 index 00000000..780edddf --- /dev/null +++ b/src/app/views/doc-card/doc-card.component.scss @@ -0,0 +1,31 @@ +.kvRow { + line-height: 1.4rem; +} + +.kvRow b { + font-weight: 500; + margin-right: .25rem; +} + +mat-card { + width: 260px; + min-height: 180px; +} + +.cardGrid { + display: grid; + grid-template-columns: repeat(auto-fill, 260px); + gap: 1rem 1.25rem; +} + +/* doc-card.component.scss */ +.nestedCard { + margin-left: .75rem; + margin-top: .25rem; + box-shadow: none; + border-left: 2px solid #e0e0e0; +} + +.kvRow { + margin-bottom: .125rem; +} diff --git a/src/app/views/doc-card/doc-card.component.ts b/src/app/views/doc-card/doc-card.component.ts new file mode 100644 index 00000000..4857572a --- /dev/null +++ b/src/app/views/doc-card/doc-card.component.ts @@ -0,0 +1,53 @@ +import {Component, EventEmitter, Input, Output} from '@angular/core'; +import {MatCheckbox, MatCheckboxModule} from '@angular/material/checkbox'; +import {FormsModule} from '@angular/forms'; +import {MatCard, MatCardContent, MatCardModule, MatCardTitle} from '@angular/material/card'; +import {AbstractNode} from '../../components/data-view/models/result-set.model'; +import {NgIf, NgForOf} from '@angular/common'; + + +@Component({ + selector: 'app-doc-card', + standalone: true, + imports: [ + MatCheckbox, + FormsModule, + MatCardContent, + MatCardTitle, + MatCard, + NgIf, + NgForOf, + MatCardModule, + MatCheckboxModule + ], + templateUrl: './doc-card.component.html', + styleUrl: './doc-card.component.scss' +}) +export class DocCardComponent { + @Input() node!: AbstractNode; + @Input() nested = false; + @Output() toggle = new EventEmitter(); + + + get kvRows(): { k: string; v: any; type: string }[] { + if (!this.node.children?.length) { + return []; + } + + return this.node.children.map(c => ({ + k: c.name, + v: c.properties?.sample, + type: c.type + })); + } + + + get subNodes(): AbstractNode[] { + return this.node.children?.filter(c => c.children?.length) ?? []; + } + + prettifyKey(k: string): string { + return k.replace(/^idx\d+:?/, '').replace(/_/g, ' '); + } +} + diff --git a/src/app/views/preview-selection/metadata-tree/metadata-tree.component.html b/src/app/views/preview-selection/metadata-tree/metadata-tree.component.html index fbbe0932..d9648569 100644 --- a/src/app/views/preview-selection/metadata-tree/metadata-tree.component.html +++ b/src/app/views/preview-selection/metadata-tree/metadata-tree.component.html @@ -1,19 +1,22 @@
  • - {{ node.name }} + {{ node.name }} + + : {{ node.properties.sample }} + + [(ngModel)]="node.isSelected" + (change)="toggleColumn(path + '.' + node.name, cb.checked, node.properties?.diff, node.type)"> {{ node.name }} - + diff --git a/src/app/views/preview-selection/preview-selection.component.html b/src/app/views/preview-selection/preview-selection.component.html index 22940e3f..4a258304 100644 --- a/src/app/views/preview-selection/preview-selection.component.html +++ b/src/app/views/preview-selection/preview-selection.component.html @@ -11,49 +11,97 @@

    Preview Selection

    - -
    -
    -

    {{ tableName }}

    - - - - - - - - - - - - - -
    - {{ col }} -
    - {{ row[col] }} -
    - -
    -
    -
    -

    Metadata

    + - - +
    +
    +

    {{ tableName }}

    + + + + + + + + + + + + + +
    + {{ col }} +
    + {{ row[col] }} +
    +
    + + + + + + + + + + + + + + + + + +
    ZeitpunktSchwereDetails
    {{ entry.timestamp | date:'short' }}{{ entry.severity }} + {{ entry.messages | json }} +
    + +

    Keine Änderungen protokolliert …

    +
    + +
    +

    Metadata

    + + + +
    -
    + + + +
    + + +
    -
    - - - -
    +

    Metadata

    + + +
    +
    +
    + + + +
    + diff --git a/src/app/views/preview-selection/preview-selection.component.scss b/src/app/views/preview-selection/preview-selection.component.scss index 5e7998a3..a3ad996b 100644 --- a/src/app/views/preview-selection/preview-selection.component.scss +++ b/src/app/views/preview-selection/preview-selection.component.scss @@ -70,3 +70,23 @@ overflow: hidden; } +.cardGrid { + display: grid; + grid-template-columns:repeat(auto-fill, minmax(220px, 1fr)); + gap: 1rem; +} + +mat-card { + cursor: pointer; +} + +table.changelog { + width: 100%; + border-collapse: collapse; + + th, td { padding: .4rem .6rem; } + + tr.critical { background: #ffebee; border-left: 4px solid #d32f2f; } + tr.warning { background: #fffde7; border-left: 4px solid #fbc02d; } +} + diff --git a/src/app/views/preview-selection/preview-selection.component.ts b/src/app/views/preview-selection/preview-selection.component.ts index 165aad2b..708ce4ad 100644 --- a/src/app/views/preview-selection/preview-selection.component.ts +++ b/src/app/views/preview-selection/preview-selection.component.ts @@ -1,13 +1,14 @@ import {Component, inject} from '@angular/core'; import {ButtonCloseDirective, ButtonDirective, ColComponent, RowComponent} from '@coreui/angular'; import {Router, RouterLink} from '@angular/router'; -import {AbstractNode} from '../../components/data-view/models/result-set.model'; +import {AbstractNode, ChangeLogEntry, ChangeStatus} from '../../components/data-view/models/result-set.model'; import {CommonModule} from '@angular/common'; import {MetadataTreeComponent} from './metadata-tree/metadata-tree.component'; import {AdapterModel, AdapterType, PolyMap} from '../adapters/adapter.model'; import {DeployMode} from '../../models/catalog.model'; import {CrudService} from '../../services/crud.service'; import {PreviewNavigationService} from '../../services/preview-navigation.service'; +import {DocCardComponent} from '../doc-card/doc-card.component'; @Component({ selector: 'app-preview-selection', @@ -17,7 +18,8 @@ import {PreviewNavigationService} from '../../services/preview-navigation.servic RowComponent, ColComponent, CommonModule, - MetadataTreeComponent + MetadataTreeComponent, + DocCardComponent ], templateUrl: './preview-selection.component.html', styleUrl: './preview-selection.component.scss' @@ -39,13 +41,14 @@ export class PreviewSelectionComponent { metadata: AbstractNode = null; preview: Record | any[] = {}; adapterInfo: string = null; + cards: AbstractNode[] = []; selected: Set = new Set(); added: Set = new Set(); removed: Set = new Set(); - + changeLog: ChangeLogEntry[] = []; ready = false; @@ -60,6 +63,7 @@ export class PreviewSelectionComponent { return; } + this.changeLog = this.ctx.changeLog ?? []; this.mode = this.ctx.mode; console.log(this.mode); @@ -79,6 +83,11 @@ export class PreviewSelectionComponent { this.collectSelected(this.metadata); + if (this.isDocumentAdapter) { + this.cards = []; + this.cards = this.findCards(this.metadata); + } + /*const opener = (window.opener as any); this.formData = opener?.pendingFormData; this.pendingFiles = opener.pendingFiles as Map; @@ -181,6 +190,15 @@ export class PreviewSelectionComponent { console.log(this.removed); } + private collectCards(n: AbstractNode) { + console.log(n); + if (n.cardCandidate === true) { + this.cards.push(n); + } + n.children?.forEach(c => this.collectCards(c)); + } + + /*sendMetadata() { (this.adapter as any).metadata = Array.from(this.selected); @@ -236,17 +254,48 @@ export class PreviewSelectionComponent { console.log(selected); this._crud.setMetaConfiguration(payload).subscribe({ - next: () => { - alert('Config changed successfully!'); - this.close(); - }, - error: err => { - alert('Config changed failed!'); - this.close(); - } + next: () => { + alert('Config changed successfully!'); + this.close(); + }, + error: err => { + alert('Config changed failed!'); + this.close(); + } }); } + get isDocumentAdapter(): boolean { + return this.adapter?.adapterName.toLowerCase() === 'json'; + } + + onCardToggle(node: Node) { + (node as any).isSelected = !(node as any).isSelected; + console.log('Card toggled:', node.name, 'selected?', (node as any).isSelected); + if ((node as any).isSelected) { + this.selected.add(node.name); + } else { + this.selected.delete(node.name); + } + console.log(this.selected); + } + + private findCards(root: AbstractNode): AbstractNode[] { + const out: AbstractNode[] = []; + + function walk(n: AbstractNode) { + if ((n as any).cardCandidate) { // Ebene-0-Objekt + out.push(n as AbstractNode); + return; // nicht tiefer bohren + } + n.children?.forEach(walk); + } + + walk(root); + return out; + } + + sendMetadata(): void { const aliases: Map = new Map(); @@ -291,7 +340,7 @@ export class PreviewSelectionComponent { } - private collectAliases(node: AbstractNode, out: Map, path: string='') { + private collectAliases(node: AbstractNode, out: Map, path: string = '') { const currentPath = path ? `${path}.${node.name}` : node.name; if (node.type === 'column') { @@ -303,6 +352,7 @@ export class PreviewSelectionComponent { node.children?.forEach(c => this.collectAliases(c, out, currentPath)); } + protected readonly ChangeStatus = ChangeStatus; } export class Node implements AbstractNode { @@ -310,7 +360,12 @@ export class Node implements AbstractNode { name: string; children: AbstractNode[] = []; properties: { [key: string]: any } = {}; - isSelected: boolean; + isSelected?: boolean; + + jsonPath?: string; + cardCandidate?: boolean; + valueType?: string; + constructor(type: string, name: string) { this.type = type; @@ -330,6 +385,41 @@ export class Node implements AbstractNode { } } +/*export class DocumentObjectNode extends Node implements AbstractNode { + readonly jsonPath!: string; + readonly cardCandidate!: boolean; +} + +export class DocumentArrayNode extends Node implements AbstractNode { + readonly jsonPath!: string; +} + +export class DocumentValueNode extends Node implements AbstractNode { + readonly jsonPath!: string; + readonly valueType!: string; + readonly sample!: any; +} + +export function nodeFactory(raw: any): Node { + switch (raw.type) { + case 'object': + return Object.assign(new DocumentObjectNode(raw.type, raw.name), raw); + case 'array': + return Object.assign(new DocumentArrayNode(raw.type, raw.name), raw); + case 'value': + return Object.assign(new DocumentValueNode(raw.type, raw.name), raw); + default: + return Object.assign(new Node(raw.type, raw.name), raw); + } +}*/ + +export function reviveTree(raw: any): AbstractNode { + if (raw.children) { + raw.children = raw.children.map(reviveTree); + } + return raw as AbstractNode; +} + interface ColumnToggleEvent { fullKey: string; checked: boolean; From 21cb8916c3c9457924eba89072a8ec3a82a308dc Mon Sep 17 00:00:00 2001 From: romanost03 Date: Mon, 30 Jun 2025 18:50:18 +0200 Subject: [PATCH 23/33] Change logs are displayed only once. --- .../preview-selection.component.html | 51 +++++++++---------- 1 file changed, 25 insertions(+), 26 deletions(-) diff --git a/src/app/views/preview-selection/preview-selection.component.html b/src/app/views/preview-selection/preview-selection.component.html index 4a258304..10f2f9f6 100644 --- a/src/app/views/preview-selection/preview-selection.component.html +++ b/src/app/views/preview-selection/preview-selection.component.html @@ -37,32 +37,6 @@

    {{ tableName }}


    - - - - - - - - - - - - - - - - - -
    ZeitpunktSchwereDetails
    {{ entry.timestamp | date:'short' }}{{ entry.severity }} - {{ entry.messages | json }} -
    - -

    Keine Änderungen protokolliert …

    @@ -76,6 +50,31 @@

    Metadata

    + + + + + + + + + + + + + + + + +
    ZeitpunktSchwereDetails
    {{ entry.timestamp | date:'short' }}{{ entry.severity }} + {{ entry.messages | json }} +
    + +

    Keine Änderungen protokolliert …

    From 7390000b60d048da4feb7e06f569328c5b1b6b26 Mon Sep 17 00:00:00 2001 From: romanost03 Date: Tue, 1 Jul 2025 19:55:59 +0200 Subject: [PATCH 24/33] Change routing when deploying new source. Bugfix for metadata logs: Nothing is displayed when no logs exist. --- .../preview-selection/preview-selection.component.html | 6 ++---- .../views/preview-selection/preview-selection.component.ts | 2 +- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/src/app/views/preview-selection/preview-selection.component.html b/src/app/views/preview-selection/preview-selection.component.html index 10f2f9f6..3c4da740 100644 --- a/src/app/views/preview-selection/preview-selection.component.html +++ b/src/app/views/preview-selection/preview-selection.component.html @@ -53,8 +53,8 @@

    Metadata

    - - + + @@ -73,8 +73,6 @@

    Metadata

    ZeitpunktSchwereTimeStatus Details
    - -

    Keine Änderungen protokolliert …

    diff --git a/src/app/views/preview-selection/preview-selection.component.ts b/src/app/views/preview-selection/preview-selection.component.ts index 708ce4ad..3fb223bf 100644 --- a/src/app/views/preview-selection/preview-selection.component.ts +++ b/src/app/views/preview-selection/preview-selection.component.ts @@ -135,7 +135,7 @@ export class PreviewSelectionComponent { } close(): void { - this._router.navigate(['/views/adapters/addSource']); + this._router.navigate(['/views/adapters']); } deserializeNode(obj: any): Node { From c02b70afc8513ef6e7222af59993f7ab5af7290f Mon Sep 17 00:00:00 2001 From: romanost03 Date: Wed, 16 Jul 2025 20:30:17 +0200 Subject: [PATCH 25/33] Minor UI improvements. --- .../preview-selection.component.html | 183 ++++++++++-------- .../preview-selection.component.scss | 47 ++++- .../preview-selection.component.ts | 40 +++- src/scss/style.scss | 2 +- 4 files changed, 177 insertions(+), 95 deletions(-) diff --git a/src/app/views/preview-selection/preview-selection.component.html b/src/app/views/preview-selection/preview-selection.component.html index 3c4da740..72077056 100644 --- a/src/app/views/preview-selection/preview-selection.component.html +++ b/src/app/views/preview-selection/preview-selection.component.html @@ -2,103 +2,118 @@ - - - -

    Preview Selection

    -
    -
    -
    +

    Preview Selection

    - - - - -
    -
    -

    {{ tableName }}

    - - - - - - - - - - - - - -
    - {{ col }} -
    - {{ row[col] }} -
    -
    -
    - -
    -

    Metadata

    + + +
    + + + + Metadata + + -
    -
    - - - - - - - - + + + + + + + Preview + + + +
    +
    +

    {{ tableName }}

    +
    +
    TimeStatusDetails
    + + + + + + + + + + +
    {{ col }}
    {{ row[col] }}
    +
    + + +
    - - - {{ entry.timestamp | date:'short' }} - {{ entry.severity }} - - {{ entry.messages | json }} - - - - -
    - - -
    - - -
    + + +
    + + + + Documents + + +
    + + +
    +
    +
    -

    Metadata

    - - - - + + + + Metadata + + + + + + +
    +
    + + + + + + + + + + {{ entry.timestamp | date:'short' }} — {{ entry.severity + ':' }} + + + {{ entry.messages[0] || '' }} + + + +
    {{ entry.messages | json }}
    +
    +
    -
    - - -
    - diff --git a/src/app/views/preview-selection/preview-selection.component.scss b/src/app/views/preview-selection/preview-selection.component.scss index a3ad996b..0f44c4b5 100644 --- a/src/app/views/preview-selection/preview-selection.component.scss +++ b/src/app/views/preview-selection/preview-selection.component.scss @@ -80,13 +80,50 @@ mat-card { cursor: pointer; } -table.changelog { + +.wrapper { + display: grid; + grid-template-columns: 1fr 2fr; + gap: 1.5rem; + padding: 1rem; +} + +.changelog { + margin-top: 2rem; width: 100%; - border-collapse: collapse; +} - th, td { padding: .4rem .6rem; } +:host ::ng-deep .log-critical { + background: #ffebee !important; + border-left: 4px solid #d32f2f !important; + font-size: 20px; +} - tr.critical { background: #ffebee; border-left: 4px solid #d32f2f; } - tr.warning { background: #fffde7; border-left: 4px solid #fbc02d; } +:host ::ng-deep .log-warning { + background: #fffde7 !important; + border-left: 4px solid #fbc02d !important; + font-size: 20px; } +:host ::ng-deep .log-ok { + background: #e8f5e9 !important; + border-left: 4px solid #43a047 !important; + font-size: 20px; +} + +.msg-preview, +.log-accordion { + display: inline-block; + max-width: 100%; +} + + +.log-details { + display: block; + white-space: pre-wrap; + word-break: break-all; +} + +.action-buttons { + justify-content: right; +} diff --git a/src/app/views/preview-selection/preview-selection.component.ts b/src/app/views/preview-selection/preview-selection.component.ts index 3fb223bf..a2363b7c 100644 --- a/src/app/views/preview-selection/preview-selection.component.ts +++ b/src/app/views/preview-selection/preview-selection.component.ts @@ -9,6 +9,15 @@ import {DeployMode} from '../../models/catalog.model'; import {CrudService} from '../../services/crud.service'; import {PreviewNavigationService} from '../../services/preview-navigation.service'; import {DocCardComponent} from '../doc-card/doc-card.component'; +import {MatCard, MatCardContent, MatCardHeader} from '@angular/material/card'; +import {MatTab, MatTabGroup} from '@angular/material/tabs'; +import {MatIcon} from '@angular/material/icon'; +import { + MatAccordion, + MatExpansionPanel, + MatExpansionPanelDescription, + MatExpansionPanelTitle +} from '@angular/material/expansion'; @Component({ selector: 'app-preview-selection', @@ -19,7 +28,17 @@ import {DocCardComponent} from '../doc-card/doc-card.component'; ColComponent, CommonModule, MetadataTreeComponent, - DocCardComponent + DocCardComponent, + MatCard, + MatCardHeader, + MatCardContent, + MatTabGroup, + MatIcon, + MatTab, + MatAccordion, + MatExpansionPanel, + MatExpansionPanelTitle, + MatExpansionPanelDescription ], templateUrl: './preview-selection.component.html', styleUrl: './preview-selection.component.scss' @@ -49,12 +68,13 @@ export class PreviewSelectionComponent { added: Set = new Set(); removed: Set = new Set(); changeLog: ChangeLogEntry[] = []; + readonly ChangeStatus = ChangeStatus; + ready = false; ngOnInit() { - this.ctx = this._nav.context; @@ -64,6 +84,7 @@ export class PreviewSelectionComponent { } this.changeLog = this.ctx.changeLog ?? []; + console.log(this.changeLog); this.mode = this.ctx.mode; console.log(this.mode); @@ -232,11 +253,11 @@ export class PreviewSelectionComponent { this._crud.metadataAck(payload).subscribe( { next: () => { - alert('ACK was send.'); + alert('Acknowledgement was sent.'); this.close(); }, error: err => { - alert('ACK was not send successfully!'); + alert('Acknowledgement was sent.'); this.close(); } } @@ -352,7 +373,14 @@ export class PreviewSelectionComponent { node.children?.forEach(c => this.collectAliases(c, out, currentPath)); } - protected readonly ChangeStatus = ChangeStatus; + getSeverityClass(sev: ChangeStatus): string { + switch (sev) { + case ChangeStatus.CRITICAL: return 'log-critical'; + case ChangeStatus.WARNING: return 'log-warning'; + default: return 'log-ok'; + } + } + } export class Node implements AbstractNode { @@ -426,3 +454,5 @@ interface ColumnToggleEvent { diff?: 'ADDED' | 'REMOVED'; type?: 'ghost' | 'table' | 'column'; } + + diff --git a/src/scss/style.scss b/src/scss/style.scss index 6d29fa04..ef4c7387 100644 --- a/src/scss/style.scss +++ b/src/scss/style.scss @@ -104,4 +104,4 @@ .subitems { width: 200px !important; } -} \ No newline at end of file +} From d330261a9893f6620d5aef1c469ecc710869dfc4 Mon Sep 17 00:00:00 2001 From: romanost03 Date: Wed, 30 Jul 2025 20:02:18 +0200 Subject: [PATCH 26/33] Change save button for reconfigure an adapter. --- .../preview-selection.component.html | 10 +++++----- .../preview-selection/preview-selection.component.ts | 11 +++++++++-- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/app/views/preview-selection/preview-selection.component.html b/src/app/views/preview-selection/preview-selection.component.html index 72077056..f546f5a1 100644 --- a/src/app/views/preview-selection/preview-selection.component.html +++ b/src/app/views/preview-selection/preview-selection.component.html @@ -3,6 +3,9 @@

    Preview Selection

    + @@ -108,12 +111,9 @@

    {{ tableName }}

    -
    diff --git a/src/app/views/preview-selection/preview-selection.component.ts b/src/app/views/preview-selection/preview-selection.component.ts index a2363b7c..677691a8 100644 --- a/src/app/views/preview-selection/preview-selection.component.ts +++ b/src/app/views/preview-selection/preview-selection.component.ts @@ -70,8 +70,7 @@ export class PreviewSelectionComponent { changeLog: ChangeLogEntry[] = []; readonly ChangeStatus = ChangeStatus; - - + showSaveButton = false; ready = false; ngOnInit() { @@ -156,6 +155,7 @@ export class PreviewSelectionComponent { } close(): void { + this.showSaveButton = false; this._router.navigate(['/views/adapters']); } @@ -196,6 +196,7 @@ export class PreviewSelectionComponent { } onColumnToggle(e: ColumnToggleEvent) { + this.makeSaveButtonVisible(); console.log(e.diff); if (e.diff === 'ADDED') { e.checked ? this.added.add(e.fullKey) : this.added.delete(e.fullKey); @@ -286,6 +287,12 @@ export class PreviewSelectionComponent { }); } + makeSaveButtonVisible(): void { + if (this.showSaveButton === false) { + this.showSaveButton = true; + } + } + get isDocumentAdapter(): boolean { return this.adapter?.adapterName.toLowerCase() === 'json'; } From e158f445e228717b9798eb253812e086f9ace614 Mon Sep 17 00:00:00 2001 From: romanost03 Date: Tue, 5 Aug 2025 16:27:41 +0200 Subject: [PATCH 27/33] UI improvements and functionality for changing adapter credentials. --- src/app/services/crud.service.ts | 5 + .../views/adapters/adapters.component.html | 23 +-- src/app/views/adapters/adapters.component.ts | 141 ++++++++++++- .../preview-selection.component.html | 189 +++++++----------- .../preview-selection.component.scss | 175 +++++++--------- .../preview-selection.component.ts | 5 +- 6 files changed, 303 insertions(+), 235 deletions(-) diff --git a/src/app/services/crud.service.ts b/src/app/services/crud.service.ts index df9d8ddb..c21848d9 100644 --- a/src/app/services/crud.service.ts +++ b/src/app/services/crud.service.ts @@ -547,6 +547,11 @@ export class CrudService { return this._http.post(`${this.httpUrl}/updateAdapterSettings`, adapter); } + updateAdapterSettingsForm(fd: FormData) { + return this._http.post(`${this.httpUrl}/updateAdapterSettingsForm`, fd); + } + + getSources() { return this._http.get(`${this.httpUrl}/getSources`); } diff --git a/src/app/views/adapters/adapters.component.html b/src/app/views/adapters/adapters.component.html index f4ac155a..0e1c4665 100644 --- a/src/app/views/adapters/adapters.component.html +++ b/src/app/views/adapters/adapters.component.html @@ -82,6 +82,10 @@

    Adapters

    cTooltip="Metadata changed - click to review" (click)="previewAndChange(adapter.name)">❗ + - diff --git a/src/app/views/adapters/adapters.component.ts b/src/app/views/adapters/adapters.component.ts index ee8f315a..22d3b514 100644 --- a/src/app/views/adapters/adapters.component.ts +++ b/src/app/views/adapters/adapters.component.ts @@ -222,6 +222,17 @@ export class AdaptersComponent implements OnInit, OnDestroy { } } + changeAdapterSettings(adapter: AdapterModel) { + const allSettings = this._catalog.getAdapterTemplate(adapter.adapterName, adapter.type); + + const current = Adapter.from(allSettings, adapter, Task.CHANGE); + this.adapter.set(current); + this.activeMode.set(current.modes[0]); + + this.handshaking = false; + this.modalActive = true; + } + private getMetaConfiguration(uniqueName: string) { this._crud.metadataConfiguration(uniqueName).subscribe({ next: (preview: PreviewResult) => { @@ -263,7 +274,7 @@ export class AdaptersComponent implements OnInit, OnDestroy { persistent: boolean; }>, changeLog?: ChangeLogEntry[] - ) { + ) { this.nav.setContext({ mode: mode, adapter, @@ -444,6 +455,134 @@ export class AdaptersComponent implements OnInit, OnDestroy { }); } + /*updateAdapterSettings() { + console.log('Rein da – Settings sammeln'); + + const form = this.editingAdapterForm; + if (!form) { + this._toast.error('No editing adapter form available!'); + return; + } + + const newSettings: Record = {}; + for (const [key, ctrl] of Object.entries(form.controls)) { + if (key === 'uniqueName') { + continue; + } + newSettings[key] = (ctrl as AbstractControl).value ?? ''; + console.log(`[Take] ${key} =`, newSettings[key]); + } + + const adapter = this.adapter(); + + const oldSettings = Object.fromEntries(adapter.settings ?? []); + const merged = {...oldSettings, ...newSettings}; + + Object.entries(newSettings).forEach(([k, v]) => { + const merged = adapter.settings.get(k); + if (merged) { + merged.current = v as string; + } else { + + } + }); + + + const poly = new PolyMap( + Object.entries(merged) as [string, string][] + ); + + const updated = new AdapterModel( + adapter.uniqueName, + adapter.adapterName, + poly, + adapter.persistent, + adapter.type, + adapter.mode + ); + + console.log(updated); + this._crud.updateAdapterSettings(updated).subscribe({ + next: res => { + res.error + ? this._toast.exception(res) + : this._toast.success('Updated adapter settings'); + this.modalActive = false; + }, + error: err => { + this._toast.error('Could not update adapter settings'); + console.error(err); + } + }); + }*/ + + updateAdapterSettings() { + + const adapter = this.adapter(); + const form = this.editingAdapterForm; + const fd = new FormData(); + let hasUpload = false; + + for (const [key, ctrl] of Object.entries(form.controls)) { + if (key === 'uniqueName') { continue; } + + const val = (ctrl as AbstractControl).value ?? ''; + const meta = this.getAdapterSetting(key); + + if (meta?.template?.type?.toLowerCase() === 'directory') { + const first = meta.template.fileNames?.[0]; + if (first) { + hasUpload = true; + adapter.settings[key] = JSON.stringify(first); + fd.append(first, this.files.get(first)); + } + } else { + adapter.settings[key] = val; + } + } + + const newSettings = new AdapterModel( + adapter.uniqueName, + adapter.adapterName, + null, + adapter.persistent, + adapter.type, + adapter.mode + ); + + if (hasUpload) { + fd.set('body', JSON.stringify(newSettings)); + + this._crud.updateAdapterSettingsForm(fd).subscribe({ + next: res => { + res.error + ? this._toast.exception(res) + : this._toast.success('Adapter settings updated'); + this.modalActive = false; + }, + error: err => { + this._toast.error('Could not update adapter settings'); + console.error(err); + } + }); + } else { + this._crud.updateAdapterSettings(newSettings).subscribe({ + next: res => { + res.error + ? this._toast.exception(res) + : this._toast.success('Adapter settings updated'); + this.modalActive = false; + }, + error: err => { + this._toast.error('Could not update adapter settings'); + console.error(err); + } + }); + } + } + + + getDefaultUniqueName(): string { if (this.adapter !== undefined) { const base = this.adapter().adapterName.toLowerCase(); // + "_"; // TODO: re-enable underscores when graph namespaces work with it diff --git a/src/app/views/preview-selection/preview-selection.component.html b/src/app/views/preview-selection/preview-selection.component.html index f546f5a1..bb50db1a 100644 --- a/src/app/views/preview-selection/preview-selection.component.html +++ b/src/app/views/preview-selection/preview-selection.component.html @@ -1,119 +1,84 @@ -
    - -

    Preview Selection

    - -
    - - - - -
    - - - - Metadata - - - - - - +
    + +
    +

    Preview Selection

    +
    + + +
    +
    - - - - Preview - - - -
    -
    -

    {{ tableName }}

    -
    - - - - - - - - - - - -
    {{ col }}
    {{ row[col] }}
    -
    -
    -
    -
    + +
    +
    + + - - -
    - - - - Documents - - -
    - - + +
    + + Preview + + +
    +

    {{ tableName }}

    +
    + + + + + + + + + +
    {{ col }}
    {{ row[col] }}
    - - +
    +
    + +
    +
    - - - - Metadata - - - - - - -
    - -
    -
    + +
    + + + + + {{ entry.timestamp | date:'short' }} — {{ entry.severity }} + + + {{ entry.messages[0] || '' }} + + +
    {{ entry.messages | json }}
    +
    +
    +
    + +
    - - - - - - {{ entry.timestamp | date:'short' }} — {{ entry.severity + ':' }} - - - {{ entry.messages[0] || '' }} - - - -
    {{ entry.messages | json }}
    -
    -
    - - - -
    - - + +
    + + +
    diff --git a/src/app/views/preview-selection/preview-selection.component.scss b/src/app/views/preview-selection/preview-selection.component.scss index 0f44c4b5..64cb2966 100644 --- a/src/app/views/preview-selection/preview-selection.component.scss +++ b/src/app/views/preview-selection/preview-selection.component.scss @@ -1,129 +1,88 @@ -.preview-wrapper { - position: relative; - min-height: 200px; -} +/* Browser-Abstände global nullen, falls nicht schon vorhanden */ +html, body { margin: 0; padding: 0; } -.btn-circle { - width: 40px; - height: 40px; - line-height: 40px; - text-align: center; +/* Hauptcontainer */ +.preview-container { + position: fixed; + top: 64px; + left: 0; + width: 100vw; + height: calc(100vh - 130px); + margin: 0; padding: 0; - border-radius: 50%; - cursor: pointer; - display: inline-flex; - align-items: center; - justify-content: center; -} - -.topRight { - position: absolute; - top: 0.5rem; - right: 1rem; - z-index: 2; -} - -.page-title { - margin-left: 30px; - margin-top: 30px; -} - -.btn-circle { - margin-top: 30px; + display: flex; + flex-direction: column; + overflow: hidden; } -.preview-container { +/* Header */ +.header { display: flex; - overflow-x: auto; - gap: 24px; + justify-content: space-between; + align-items: center; + width: 100%; + background: #fff; padding: 1rem; -} - -.table-wrapper { - min-width: 300px; - flex-shrink: 0; - - table { - border-collapse: collapse; - width: 100%; + box-shadow: 0 1px 4px rgba(0,0,0,.1); + z-index: 2; - th, td { - border: 1px solid #ccc; - padding: 4px 8px; - } + h2 { margin: 0; } - th { - background: #f5f5f5; - } + .btn-circle { + width: 2.5rem; height: 2.5rem; border-radius: 50%; + background: #1976d2; color: #fff; border: none; cursor: pointer; + &:hover { background: #0d47a1; } } } -.dataPreview { - float: right; - margin-right: 30px; - overflow: hidden; -} +/* Wrapper + Hauptcontent */ +.main-wrapper { flex: 1; width: 100%; display: flex; overflow: hidden; } +.main-content { display: flex; width: 100%; height: 100%; overflow: hidden; } -.dataMeta { - margin-left: 30px; - float: left; - overflow: hidden; -} +/* Sidebar */ +.sidebar { + width: 280px; + background: #1565c0; + color: #fff; + padding: .5rem; + overflow-y: auto; + transition: width .3s ease; -.cardGrid { - display: grid; - grid-template-columns:repeat(auto-fill, minmax(220px, 1fr)); - gap: 1rem; -} + &.collapsed { width: 0; padding: 0; overflow: hidden; } -mat-card { - cursor: pointer; -} + ::ng-deep .mat-card-title, + ::ng-deep .mat-card-content, + ::ng-deep .mat-checkbox-label { color: #fff !important; } + .meta-card { background: transparent; box-shadow: none; height: 100%; } +} -.wrapper { - display: grid; - grid-template-columns: 1fr 2fr; - gap: 1.5rem; +/* Preview-Bereich */ +.preview { + flex: 1; + background: #fff; padding: 1rem; + display: flex; + flex-direction: column; + overflow-y: auto; + + .preview-card { margin-bottom: .5rem; box-shadow: none; } + .preview-table-area { flex: 1; overflow-y: auto; } + + .log-toggle { + max-height: 40px; + overflow: hidden; + transition: max-height .3s ease; + &.expanded { max-height: 250px; } + mat-expansion-panel { background: #f9f9f9; } + } } -.changelog { - margin-top: 2rem; +/* Footer-Buttons */ +.actions { width: 100%; -} - -:host ::ng-deep .log-critical { - background: #ffebee !important; - border-left: 4px solid #d32f2f !important; - font-size: 20px; -} - -:host ::ng-deep .log-warning { - background: #fffde7 !important; - border-left: 4px solid #fbc02d !important; - font-size: 20px; -} - -:host ::ng-deep .log-ok { - background: #e8f5e9 !important; - border-left: 4px solid #43a047 !important; - font-size: 20px; -} - -.msg-preview, -.log-accordion { - display: inline-block; - max-width: 100%; -} - - -.log-details { - display: block; - white-space: pre-wrap; - word-break: break-all; -} - -.action-buttons { - justify-content: right; + background: #fff; + padding: .5rem 1rem; + text-align: right; + button { margin-left: .5rem; } } diff --git a/src/app/views/preview-selection/preview-selection.component.ts b/src/app/views/preview-selection/preview-selection.component.ts index 677691a8..18120bcb 100644 --- a/src/app/views/preview-selection/preview-selection.component.ts +++ b/src/app/views/preview-selection/preview-selection.component.ts @@ -9,7 +9,7 @@ import {DeployMode} from '../../models/catalog.model'; import {CrudService} from '../../services/crud.service'; import {PreviewNavigationService} from '../../services/preview-navigation.service'; import {DocCardComponent} from '../doc-card/doc-card.component'; -import {MatCard, MatCardContent, MatCardHeader} from '@angular/material/card'; +import {MatCard, MatCardContent, MatCardHeader, MatCardTitle} from '@angular/material/card'; import {MatTab, MatTabGroup} from '@angular/material/tabs'; import {MatIcon} from '@angular/material/icon'; import { @@ -31,6 +31,7 @@ import { DocCardComponent, MatCard, MatCardHeader, + MatCardTitle, MatCardContent, MatTabGroup, MatIcon, @@ -71,6 +72,8 @@ export class PreviewSelectionComponent { readonly ChangeStatus = ChangeStatus; showSaveButton = false; + showMetadata = true; + showLogs = false; ready = false; ngOnInit() { From b3a95e62c6073c9053ba5d1036d51ddea944efa2 Mon Sep 17 00:00:00 2001 From: romanost03 Date: Wed, 6 Aug 2025 17:29:33 +0200 Subject: [PATCH 28/33] Send correct request for building ephemeral adapter on backend-side. --- src/app/views/adapters/adapters.component.ts | 82 ++++++++++++-------- 1 file changed, 49 insertions(+), 33 deletions(-) diff --git a/src/app/views/adapters/adapters.component.ts b/src/app/views/adapters/adapters.component.ts index 22d3b514..db38c2d4 100644 --- a/src/app/views/adapters/adapters.component.ts +++ b/src/app/views/adapters/adapters.component.ts @@ -517,12 +517,23 @@ export class AdaptersComponent implements OnInit, OnDestroy { }*/ updateAdapterSettings() { + console.log('Rein da – Settings sammeln'); + + const form = this.editingAdapterForm; + if (!form) { + this._toast.error('No editing adapter form available!'); + return; + } - const adapter = this.adapter(); - const form = this.editingAdapterForm; - const fd = new FormData(); + const adapter = this.adapter(); + const fd = new FormData(); let hasUpload = false; + const mergedObj: Record = {}; + adapter.settings.forEach((ms, key) => { + mergedObj[key] = ms.current ?? ''; // MergedSetting → String + }); + for (const [key, ctrl] of Object.entries(form.controls)) { if (key === 'uniqueName') { continue; } @@ -533,54 +544,59 @@ export class AdaptersComponent implements OnInit, OnDestroy { const first = meta.template.fileNames?.[0]; if (first) { hasUpload = true; - adapter.settings[key] = JSON.stringify(first); + mergedObj[key] = JSON.stringify(first); fd.append(first, this.files.get(first)); } } else { - adapter.settings[key] = val; + mergedObj[key] = val; } + console.log('[Take] ${key} =', mergedObj[key]); } - const newSettings = new AdapterModel( + const poly = new PolyMap( + Object.entries(mergedObj) as [string,string][] + ); + + const updated = new AdapterModel( adapter.uniqueName, adapter.adapterName, - null, + poly, adapter.persistent, adapter.type, adapter.mode ); + + + const updated2 = new PreviewRequest( + adapter.adapterName, + adapter.type, + mergedObj, + 10, + adapter.uniqueName + ); + if (hasUpload) { - fd.set('body', JSON.stringify(newSettings)); - - this._crud.updateAdapterSettingsForm(fd).subscribe({ - next: res => { - res.error - ? this._toast.exception(res) - : this._toast.success('Adapter settings updated'); - this.modalActive = false; - }, - error: err => { - this._toast.error('Could not update adapter settings'); - console.error(err); - } - }); + fd.set('body', JSON.stringify(updated2)); + this._crud.updateAdapterSettingsForm(fd).subscribe(this.handleResult); } else { - this._crud.updateAdapterSettings(newSettings).subscribe({ - next: res => { - res.error - ? this._toast.exception(res) - : this._toast.success('Adapter settings updated'); - this.modalActive = false; - }, - error: err => { - this._toast.error('Could not update adapter settings'); - console.error(err); - } - }); + this._crud.updateAdapterSettings(updated).subscribe(this.handleResult); } } + private handleResult = { + next: res => { + res.error + ? this._toast.exception(res) + : this._toast.success('Updated adapter settings'); + this.modalActive = false; + }, + error: err => { + this._toast.error('Could not update adapter settings'); + console.error(err); + } + }; + getDefaultUniqueName(): string { From bf635d2f9ab0eb52cf47667fee623ff690003ac6 Mon Sep 17 00:00:00 2001 From: romanost03 Date: Mon, 11 Aug 2025 15:21:34 +0200 Subject: [PATCH 29/33] Configuration button does not work during a CRITICAL change. --- src/app/views/adapters/adapters.component.html | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/app/views/adapters/adapters.component.html b/src/app/views/adapters/adapters.component.html index 0e1c4665..45824884 100644 --- a/src/app/views/adapters/adapters.component.html +++ b/src/app/views/adapters/adapters.component.html @@ -82,12 +82,13 @@

    Adapters

    cTooltip="Metadata changed - click to review" (click)="previewAndChange(adapter.name)">❗ - - From 80bd4974228bca3b6524174bd838b09f9978dcba Mon Sep 17 00:00:00 2001 From: romanost03 Date: Mon, 11 Aug 2025 15:43:00 +0200 Subject: [PATCH 30/33] Minor UI improvements and code cleaning. --- .../data-view/models/result-set.model.ts | 10 +- .../delete-confirm.component.html | 4 +- src/app/services/metadata-polling.service.ts | 6 +- .../services/preview-navigation.service.ts | 4 +- src/app/views/adapters/adapters.component.ts | 4 +- .../metadata-tree.component.html | 1 + .../metadata-tree.component.scss | 11 +- .../metadata-tree/metadata-tree.component.ts | 15 +- .../preview-selection.component.html | 4 +- .../preview-selection.component.scss | 95 ++++++++---- .../preview-selection.component.ts | 145 +++--------------- 11 files changed, 117 insertions(+), 182 deletions(-) diff --git a/src/app/components/data-view/models/result-set.model.ts b/src/app/components/data-view/models/result-set.model.ts index 5c661e1c..90a15f9c 100644 --- a/src/app/components/data-view/models/result-set.model.ts +++ b/src/app/components/data-view/models/result-set.model.ts @@ -86,10 +86,10 @@ export class DashboardSet { export class PreviewResult { metadata: string; preview: any[]; - history?: ChangeLogEntry[]; + history?: ChangeLogView[]; } -export interface ChangeLogEntry { +export interface ChangeLogView { adapterName: string; timestamp: string; severity: ChangeStatus; @@ -102,12 +102,6 @@ export enum ChangeStatus { OK = 'OK' } -export interface DiffResult { - added: string[]; - removed: string[]; - changed: string[]; -} - /** * model for statistics coming from the server diff --git a/src/app/components/delete-confirm/delete-confirm.component.html b/src/app/components/delete-confirm/delete-confirm.component.html index e02e5c99..c6a49a4e 100644 --- a/src/app/components/delete-confirm/delete-confirm.component.html +++ b/src/app/components/delete-confirm/delete-confirm.component.html @@ -1,4 +1,4 @@ -
    - +
    @@ -62,7 +62,7 @@

    {{ tableName }}

    - {{ entry.timestamp | date:'short' }} — {{ entry.severity }} + {{ entry.timestamp }} — {{ entry.severity }} {{ entry.messages[0] || '' }} diff --git a/src/app/views/preview-selection/preview-selection.component.scss b/src/app/views/preview-selection/preview-selection.component.scss index 64cb2966..664623e0 100644 --- a/src/app/views/preview-selection/preview-selection.component.scss +++ b/src/app/views/preview-selection/preview-selection.component.scss @@ -1,11 +1,12 @@ -/* Browser-Abstände global nullen, falls nicht schon vorhanden */ -html, body { margin: 0; padding: 0; } +html, body { + margin: 0; + padding: 0; +} -/* Hauptcontainer */ .preview-container { position: fixed; - top: 64px; - left: 0; + top: 64px; + left: 0; width: 100vw; height: calc(100vh - 130px); margin: 0; @@ -15,7 +16,6 @@ html, body { margin: 0; padding: 0; } overflow: hidden; } -/* Header */ .header { display: flex; justify-content: space-between; @@ -23,41 +23,69 @@ html, body { margin: 0; padding: 0; } width: 100%; background: #fff; padding: 1rem; - box-shadow: 0 1px 4px rgba(0,0,0,.1); + box-shadow: 0 1px 4px rgba(0, 0, 0, .1); z-index: 2; - h2 { margin: 0; } + h2 { + margin: 0; + } .btn-circle { - width: 2.5rem; height: 2.5rem; border-radius: 50%; - background: #1976d2; color: #fff; border: none; cursor: pointer; - &:hover { background: #0d47a1; } + width: 2.5rem; + height: 2.5rem; + border-radius: 50%; + background: #1976d2; + color: #fff; + border: none; + cursor: pointer; + + &:hover { + background: #0d47a1; + } } } -/* Wrapper + Hauptcontent */ -.main-wrapper { flex: 1; width: 100%; display: flex; overflow: hidden; } -.main-content { display: flex; width: 100%; height: 100%; overflow: hidden; } +.main-wrapper { + flex: 1; + width: 100%; + display: flex; + overflow: hidden; +} + +.main-content { + display: flex; + width: 100%; + height: 100%; + overflow: hidden; +} -/* Sidebar */ .sidebar { width: 280px; - background: #1565c0; + background: #022E3D; color: #fff; padding: .5rem; overflow-y: auto; transition: width .3s ease; - &.collapsed { width: 0; padding: 0; overflow: hidden; } + &.collapsed { + width: 0; + padding: 0; + overflow: hidden; + } ::ng-deep .mat-card-title, ::ng-deep .mat-card-content, - ::ng-deep .mat-checkbox-label { color: #fff !important; } + ::ng-deep .mat-checkbox-label { + color: #fff !important; + } - .meta-card { background: transparent; box-shadow: none; height: 100%; } + .meta-card { + background: transparent; + box-shadow: none; + height: 100%; + } } -/* Preview-Bereich */ .preview { flex: 1; background: #fff; @@ -66,23 +94,38 @@ html, body { margin: 0; padding: 0; } flex-direction: column; overflow-y: auto; - .preview-card { margin-bottom: .5rem; box-shadow: none; } - .preview-table-area { flex: 1; overflow-y: auto; } + .preview-card { + margin-bottom: .5rem; + box-shadow: none; + } + + .preview-table-area { + flex: 1; + overflow-y: auto; + } .log-toggle { max-height: 40px; overflow: hidden; transition: max-height .3s ease; - &.expanded { max-height: 250px; } - mat-expansion-panel { background: #f9f9f9; } + + &.expanded { + max-height: 250px; + } + + mat-expansion-panel { + background: #f9f9f9; + } } } -/* Footer-Buttons */ .actions { width: 100%; background: #fff; padding: .5rem 1rem; text-align: right; - button { margin-left: .5rem; } + + button { + margin-left: .5rem; + } } diff --git a/src/app/views/preview-selection/preview-selection.component.ts b/src/app/views/preview-selection/preview-selection.component.ts index 18120bcb..f3e79230 100644 --- a/src/app/views/preview-selection/preview-selection.component.ts +++ b/src/app/views/preview-selection/preview-selection.component.ts @@ -1,7 +1,7 @@ import {Component, inject} from '@angular/core'; import {ButtonCloseDirective, ButtonDirective, ColComponent, RowComponent} from '@coreui/angular'; import {Router, RouterLink} from '@angular/router'; -import {AbstractNode, ChangeLogEntry, ChangeStatus} from '../../components/data-view/models/result-set.model'; +import {AbstractNode, ChangeLogView, ChangeStatus} from '../../components/data-view/models/result-set.model'; import {CommonModule} from '@angular/common'; import {MetadataTreeComponent} from './metadata-tree/metadata-tree.component'; import {AdapterModel, AdapterType, PolyMap} from '../adapters/adapter.model'; @@ -15,31 +15,27 @@ import {MatIcon} from '@angular/material/icon'; import { MatAccordion, MatExpansionPanel, - MatExpansionPanelDescription, + MatExpansionPanelDescription, MatExpansionPanelHeader, MatExpansionPanelTitle } from '@angular/material/expansion'; +import {ToasterService} from '../../components/toast-exposer/toaster.service'; @Component({ selector: 'app-preview-selection', standalone: true, imports: [ ButtonDirective, - RowComponent, - ColComponent, CommonModule, MetadataTreeComponent, - DocCardComponent, MatCard, MatCardHeader, MatCardTitle, MatCardContent, - MatTabGroup, - MatIcon, - MatTab, MatAccordion, MatExpansionPanel, MatExpansionPanelTitle, - MatExpansionPanelDescription + MatExpansionPanelDescription, + MatExpansionPanelHeader ], templateUrl: './preview-selection.component.html', styleUrl: './preview-selection.component.scss' @@ -49,6 +45,7 @@ export class PreviewSelectionComponent { private readonly _nav = inject(PreviewNavigationService); private readonly _crud = inject(CrudService); private readonly _router = inject(Router); + private readonly _toast = inject(ToasterService); private ctx = null; mode!: null; @@ -68,7 +65,7 @@ export class PreviewSelectionComponent { added: Set = new Set(); removed: Set = new Set(); - changeLog: ChangeLogEntry[] = []; + changeLog: ChangeLogView[] = []; readonly ChangeStatus = ChangeStatus; showSaveButton = false; @@ -79,7 +76,6 @@ export class PreviewSelectionComponent { ngOnInit() { this.ctx = this._nav.context; - if (!this.ctx) { console.error('Preview-context not found !'); return; @@ -111,47 +107,6 @@ export class PreviewSelectionComponent { this.cards = this.findCards(this.metadata); } - /*const opener = (window.opener as any); - this.formData = opener?.pendingFormData; - this.pendingFiles = opener.pendingFiles as Map; - - - let metaRaw = localStorage.getItem('metaRoot'); - metaRaw = JSON.parse(metaRaw); - this.metadata = this.deserializeNode(metaRaw); - - const previewRaw = localStorage.getItem('preview'); - this.preview = previewRaw ? JSON.parse(previewRaw) : {}; - - console.log(this.metadata); - - - const rawSettings = localStorage.getItem('adapterSettings'); - const settingsObj = rawSettings ? JSON.parse(rawSettings) as Record : {}; - const adapterSettings = new PolyMap(); - for (const [k, v] of Object.entries(settingsObj)) { - adapterSettings.set(k, v as string); - } - - - const infoRaw = localStorage.getItem('adapterInfo'); - const info = infoRaw ? JSON.parse(infoRaw) as Partial<{ - uniqueName: string; - adapterName: string; - type: AdapterType; - mode: DeployMode; - persistent: boolean; - }> : {}; - - this.adapter = new AdapterModel( - info.uniqueName ?? adapterSettings.get('uniqueName') ?? 'adapter_' + Date.now(), - info.adapterName ?? 'UNKNOWN', - adapterSettings, - info.persistent ?? true, - info.type ?? AdapterType.SOURCE, - info.mode ?? DeployMode.REMOTE - );*/ - this.ready = true; @@ -162,24 +117,6 @@ export class PreviewSelectionComponent { this._router.navigate(['/views/adapters']); } - deserializeNode(obj: any): Node { - const node = new Node(obj.type, obj.name); - - if (obj.properties) { - for (const [key, value] of Object.entries(obj.properties)) { - node.addProperty(key, value); - } - } - - if (obj.children && Array.isArray(obj.children)) { - for (const child of obj.children) { - node.addChild(this.deserializeNode(child)); - } - } - - - return node; - } collectSelected(node: AbstractNode, path: string[] = []) { const fullPath = [...path, node.name]; @@ -224,22 +161,6 @@ export class PreviewSelectionComponent { } - /*sendMetadata() { - (this.adapter as any).metadata = Array.from(this.selected); - - - this._crud.createAdapter(this.adapter, this.formData).subscribe({ - next: (res) => { - console.log('Adapter + Metadaten erfolgreich gesendet', res); - alert('Daten erfolgreich gesendet.'); - }, - error: (err) => { - console.error(err); - alert('Fehler beim Senden!'); - } - }); - }*/ - sendAck(): void { const addedPaths: string[] = Array.from(this.added); const removedPaths: string[] = Array.from(this.removed); @@ -257,11 +178,11 @@ export class PreviewSelectionComponent { this._crud.metadataAck(payload).subscribe( { next: () => { - alert('Acknowledgement was sent.'); + this._toast.success('Successfully acknowledged!'); this.close(); }, error: err => { - alert('Acknowledgement was sent.'); + this._toast.error('Error during acknowledgement!'); this.close(); } } @@ -280,11 +201,11 @@ export class PreviewSelectionComponent { console.log(selected); this._crud.setMetaConfiguration(payload).subscribe({ next: () => { - alert('Config changed successfully!'); + this._toast.success('Config changed successfully!'); this.close(); }, error: err => { - alert('Config changed failed!'); + this._toast.error('Config changed failed!'); this.close(); } }); @@ -315,9 +236,9 @@ export class PreviewSelectionComponent { const out: AbstractNode[] = []; function walk(n: AbstractNode) { - if ((n as any).cardCandidate) { // Ebene-0-Objekt + if ((n as any).cardCandidate) { out.push(n as AbstractNode); - return; // nicht tiefer bohren + return; } n.children?.forEach(walk); } @@ -360,12 +281,12 @@ export class PreviewSelectionComponent { this._crud.createAdapter(this.adapter, newFormData).subscribe({ next: res => { - alert('Adapter + Metadata sent successfully.'); + this._toast.success('Adapter deployed!'); this.close(); }, error: err => { console.error(err); - alert('Fail to send metadata!'); + this._toast.error('Failed to deploy adapter!'); } }); @@ -385,9 +306,12 @@ export class PreviewSelectionComponent { getSeverityClass(sev: ChangeStatus): string { switch (sev) { - case ChangeStatus.CRITICAL: return 'log-critical'; - case ChangeStatus.WARNING: return 'log-warning'; - default: return 'log-ok'; + case ChangeStatus.CRITICAL: + return 'log-critical'; + case ChangeStatus.WARNING: + return 'log-warning'; + default: + return 'log-ok'; } } @@ -423,33 +347,6 @@ export class Node implements AbstractNode { } } -/*export class DocumentObjectNode extends Node implements AbstractNode { - readonly jsonPath!: string; - readonly cardCandidate!: boolean; -} - -export class DocumentArrayNode extends Node implements AbstractNode { - readonly jsonPath!: string; -} - -export class DocumentValueNode extends Node implements AbstractNode { - readonly jsonPath!: string; - readonly valueType!: string; - readonly sample!: any; -} - -export function nodeFactory(raw: any): Node { - switch (raw.type) { - case 'object': - return Object.assign(new DocumentObjectNode(raw.type, raw.name), raw); - case 'array': - return Object.assign(new DocumentArrayNode(raw.type, raw.name), raw); - case 'value': - return Object.assign(new DocumentValueNode(raw.type, raw.name), raw); - default: - return Object.assign(new Node(raw.type, raw.name), raw); - } -}*/ export function reviveTree(raw: any): AbstractNode { if (raw.children) { From be1d8defdebc11dad0f102420a84dd0b919c36b8 Mon Sep 17 00:00:00 2001 From: romanost03 Date: Mon, 11 Aug 2025 19:26:17 +0200 Subject: [PATCH 31/33] Functionality to add multiple metadata with one click. --- .../metadata-tree.component.html | 33 ++++----- .../metadata-tree/metadata-tree.component.ts | 71 ++++++++++--------- 2 files changed, 50 insertions(+), 54 deletions(-) diff --git a/src/app/views/preview-selection/metadata-tree/metadata-tree.component.html b/src/app/views/preview-selection/metadata-tree/metadata-tree.component.html index 64bb9628..a418923b 100644 --- a/src/app/views/preview-selection/metadata-tree/metadata-tree.component.html +++ b/src/app/views/preview-selection/metadata-tree/metadata-tree.component.html @@ -1,28 +1,23 @@
    • - {{ node.name }} - - : {{ node.properties.sample }} - - + + + {{ node.name }} + + : {{ node.properties.sample }} + + - - {{ node.name }} - - - - - - + type="text" + #alias + placeholder="Alias" + [value]="getAlias(node)" + (blur)="setAlias(node, alias.value)">--> + (); + @Output() columnToggle = new EventEmitter<{ fullKey: string; checked: boolean; diff?; type? }>(); @Output() autoSelectRemoved = new EventEmitter(); ngOnInit(): void { @@ -31,55 +27,60 @@ export class MetadataTreeComponent { private collectRemoved(n: AbstractNode, path: string[], out: Set): void { const next = [...path, n.name]; - const isLeaf = !n.children || n.children.length === 0; - const isGhost = n.type === 'ghost'; - - if (isGhost) { + if ((n as any).type === 'ghost') { out.add(next.join('.')); } - n.children?.forEach(c => this.collectRemoved(c, next, out)); } - - toggleColumn(fullKey: string, checked: boolean, diff, type) { + toggleColumn(fullKey: string, checked: boolean, diff?: any, type?: any) { this.columnToggle.emit({fullKey, checked, diff, type}); } - // TODO Aliase may be shown with the physical name. + onNodeToggle(checked: boolean): void { + const parts = this.path ? this.path.split('.') : []; + this.toggleRecursive(this.node, parts, checked); + } + + private toggleRecursive(n: AbstractNode, pathParts: string[], checked: boolean): void { + (n as any).isSelected = checked; + + const next = [...pathParts, n.name]; + const isLeaf = !n.children || n.children.length === 0; + + if (isLeaf && (n as any).type === 'column') { + const fullKey = next.join('.'); + const diff = (n as any).properties?.['diff']; + this.columnToggle.emit({fullKey, checked, diff, type: 'column'}); + return; + } + n.children?.forEach(c => this.toggleRecursive(c, next, checked)); + } + getAlias(node: AbstractNode): string { - return (node.properties && typeof node.properties === 'object') - ? (node.properties['alias'] ?? '') - : ''; + return (node as any).properties?.['alias'] ?? ''; } setAlias(node: AbstractNode, value: string): void { - const trimmed = value.trim(); - + const trimmed = (value ?? '').trim(); if (!trimmed) { - if (node.properties) { - delete node.properties['alias']; + if ((node as any).properties) { + delete (node as any).properties['alias']; } return; } - - if (!node.properties || typeof node.properties !== 'object') { - node.properties = {}; - } - - node.properties['alias'] = trimmed; + (node as any).properties = (node as any).properties ?? {}; + (node as any).properties['alias'] = trimmed; } getNodeClass(node: AbstractNode): string { - if (node.properties?.['diff'] === 'ADDED') { + const props = (node as any).properties || {}; + if (props['diff'] === 'ADDED') { return 'node-added'; - } else if (node.properties?.['diff'] === 'REMOVED' || node.type === 'ghost') { + } + if (props['diff'] === 'REMOVED' || (node as any).type === 'ghost') { return 'node-removed'; - } else { - return ''; } - + return ''; } - - } From 450be9e15b9a3a04b17aeaeb3e5108c55af78b15 Mon Sep 17 00:00:00 2001 From: romanost03 Date: Tue, 12 Aug 2025 12:52:32 +0200 Subject: [PATCH 32/33] Bugfix: Removed metadata cannot be selected. --- .../metadata-tree.component.html | 23 +++++++++++++++---- .../metadata-tree/metadata-tree.component.ts | 16 +++++++++++++ 2 files changed, 35 insertions(+), 4 deletions(-) diff --git a/src/app/views/preview-selection/metadata-tree/metadata-tree.component.html b/src/app/views/preview-selection/metadata-tree/metadata-tree.component.html index a418923b..364e7ff0 100644 --- a/src/app/views/preview-selection/metadata-tree/metadata-tree.component.html +++ b/src/app/views/preview-selection/metadata-tree/metadata-tree.component.html @@ -1,15 +1,24 @@
      • - + {{ node.name }} : {{ node.properties.sample }} - + {{ node.name }} - +
      + + + + + + diff --git a/src/app/views/preview-selection/metadata-tree/metadata-tree.component.ts b/src/app/views/preview-selection/metadata-tree/metadata-tree.component.ts index b3c5626c..b01be260 100644 --- a/src/app/views/preview-selection/metadata-tree/metadata-tree.component.ts +++ b/src/app/views/preview-selection/metadata-tree/metadata-tree.component.ts @@ -38,11 +38,20 @@ export class MetadataTreeComponent { } onNodeToggle(checked: boolean): void { + if (this.isRemovedOrGhost(this.node)) { + (this.node as any).isSelected = false; + return; + } const parts = this.path ? this.path.split('.') : []; this.toggleRecursive(this.node, parts, checked); } private toggleRecursive(n: AbstractNode, pathParts: string[], checked: boolean): void { + if (this.isRemovedOrGhost(n)) { + (n as any).isSelected = false; + return; + } + (n as any).isSelected = checked; const next = [...pathParts, n.name]; @@ -83,4 +92,11 @@ export class MetadataTreeComponent { } return ''; } + + get isRemovedOrGhost() { + return (node: AbstractNode) => { + const props = (node as any).properties || {}; + return props['diff'] === 'REMOVED' || (node as any).type === 'ghost'; + }; + } } From 43eae632eebbbf4ce11593c92b3b3687eac75059 Mon Sep 17 00:00:00 2001 From: romanost03 Date: Tue, 12 Aug 2025 20:05:31 +0200 Subject: [PATCH 33/33] Code cleaning and minor improvements. --- .../data-view/models/result-set.model.ts | 40 ------ .../table-selection-dialog.component.html | 1 - src/app/models/ui-request.model.ts | 13 -- .../services/preview-navigation.service.ts | 2 +- .../views/adapters/adapters.component.html | 1 - src/app/views/adapters/adapters.component.ts | 135 +++--------------- src/app/views/doc-card/doc-card.component.ts | 2 +- .../metadata-tree/metadata-tree.component.ts | 7 +- .../models/metadataTree.model.ts | 80 +++++++++++ .../preview-selection.component.ts | 12 +- 10 files changed, 112 insertions(+), 181 deletions(-) create mode 100644 src/app/views/preview-selection/models/metadataTree.model.ts diff --git a/src/app/components/data-view/models/result-set.model.ts b/src/app/components/data-view/models/result-set.model.ts index 90a15f9c..1de3f0d5 100644 --- a/src/app/components/data-view/models/result-set.model.ts +++ b/src/app/components/data-view/models/result-set.model.ts @@ -80,29 +80,6 @@ export class DashboardSet { } -/** - * model for handling preview data - */ -export class PreviewResult { - metadata: string; - preview: any[]; - history?: ChangeLogView[]; -} - -export interface ChangeLogView { - adapterName: string; - timestamp: string; - severity: ChangeStatus; - messages: string[]; -} - -export enum ChangeStatus { - CRITICAL = 'CRITICAL', - WARNING = 'WARNING', - OK = 'OK' -} - - /** * model for statistics coming from the server */ @@ -407,23 +384,6 @@ export class PartitionFunctionColumn { options: string[]; } -export interface AbstractNode { - type: string; - name: string; - children?: AbstractNode[]; - properties?: { [key: string]: any }; - isSelected?: boolean; - jsonPath?: string; - cardCandidate?: boolean; - valueType?: string; - - addChild(node: AbstractNode): void; - - addProperty(key: string, value: any): void; - - getProperty(key: string): string | null; -} - /** * What kind of type to render in the UI, e.g. a number input, a select menu etc. */ diff --git a/src/app/components/table-selection-dialog/table-selection-dialog.component.html b/src/app/components/table-selection-dialog/table-selection-dialog.component.html index 5eb30fe0..e6992d72 100644 --- a/src/app/components/table-selection-dialog/table-selection-dialog.component.html +++ b/src/app/components/table-selection-dialog/table-selection-dialog.component.html @@ -1,5 +1,4 @@
      -

      Tabellen auswählen

      diff --git a/src/app/models/ui-request.model.ts b/src/app/models/ui-request.model.ts index 2a10f93d..e429367d 100644 --- a/src/app/models/ui-request.model.ts +++ b/src/app/models/ui-request.model.ts @@ -231,19 +231,6 @@ export class PreviewRequest { } } -/** - * Request used for checking for available metadata changes. - */ -export class MetadataChangeRequest { - adapterName: string; - uniqueName: string; - - constructor(adapterName: string, uniqueName: string) { - this.adapterName = adapterName; - this.uniqueName = uniqueName; - } -} - /** * Request to drop or create a constraint of a table diff --git a/src/app/services/preview-navigation.service.ts b/src/app/services/preview-navigation.service.ts index 0dd72ff3..bdbf659b 100644 --- a/src/app/services/preview-navigation.service.ts +++ b/src/app/services/preview-navigation.service.ts @@ -1,5 +1,5 @@ import {Injectable} from '@angular/core'; -import {AbstractNode, ChangeLogView} from '../components/data-view/models/result-set.model'; +import {AbstractNode, ChangeLogView} from '../views/preview-selection/models/metadataTree.model'; import {AdapterModel, AdapterType, PolyMap} from '../views/adapters/adapter.model'; import {DeployMode} from '../models/catalog.model'; diff --git a/src/app/views/adapters/adapters.component.html b/src/app/views/adapters/adapters.component.html index 45824884..fde9d4d0 100644 --- a/src/app/views/adapters/adapters.component.html +++ b/src/app/views/adapters/adapters.component.html @@ -36,7 +36,6 @@

      Adapters

      {{ adapter.description }} - diff --git a/src/app/views/adapters/adapters.component.ts b/src/app/views/adapters/adapters.component.ts index 658ebadb..f462ad49 100644 --- a/src/app/views/adapters/adapters.component.ts +++ b/src/app/views/adapters/adapters.component.ts @@ -25,11 +25,10 @@ import { Validators } from '@angular/forms'; import { - AbstractNode, ChangeLogView, PathAccessRequest, - PreviewResult, RelationalResult } from '../../components/data-view/models/result-set.model'; +import {AbstractNode, ChangeLogView, PreviewResult} from '../preview-selection/models/metadataTree.model'; import {PreviewRequest} from '../../models/ui-request.model'; import {Node, reviveTree} from '../preview-selection/preview-selection.component'; import {Subscription} from 'rxjs'; @@ -292,6 +291,7 @@ export class AdaptersComponent implements OnInit, OnDestroy { previewAndDeploy(): void { + // Create AdapterModel and use it later for deploy. const deploy = new AdapterModel( this.editingAvailableAdapterForm.controls['uniqueName'].value, this.adapter().adapterName, @@ -300,6 +300,8 @@ export class AdaptersComponent implements OnInit, OnDestroy { this.adapter().type, this.activeMode() ); + + // If settings consists directory, append InpuStream const fd = new FormData(); for (const [k, v] of Object.entries(this.editingAvailableAdapterForm.controls)) { @@ -322,6 +324,7 @@ export class AdaptersComponent implements OnInit, OnDestroy { } fd.set('body', JSON.stringify(deploy)); + // Preview-Request for temporal adapter deploy. const previewReq = new PreviewRequest( deploy.adapterName, deploy.type, @@ -332,46 +335,11 @@ export class AdaptersComponent implements OnInit, OnDestroy { this._crud.previewTableForm(previewReq, fd).subscribe({ next: (preview: PreviewResult) => { - - console.log('Preview geladen', preview); - const settingsObj = Object.fromEntries( Object.entries(this.editingAvailableAdapterForm.controls) .map(([key, ctrl]) => [key, (ctrl as AbstractControl).value]) ); - - /* localStorage.setItem('adapterSettings', JSON.stringify(settingsObj)); - localStorage.setItem('adapterInfo', JSON.stringify({ - uniqueName: deploy.name, - adapterName: deploy.adapterName, - type: deploy.type, - mode: deploy.mode, - persistent: deploy.persistent - })); - - - localStorage.setItem( - 'metaRoot', - typeof preview.metadata === 'string' - ? preview.metadata - : JSON.stringify(preview.metadata) - ); - - localStorage.setItem('preview', JSON.stringify(preview.preview)); - console.log(JSON.stringify(preview.metadata)); - console.log(JSON.stringify(preview.preview)); - - - (window as any).pendingDeploy = deploy; - (window as any).pendingFormData = fd; - (window as any).pendingFiles = this.files; - this.metadataSelection = true; - - this.modalActive = false; - const win = window.open('/#/preview-selection', - 'popup', - 'width=1000,height=700');*/ const metaRoot = reviveTree( typeof preview.metadata === 'string' ? JSON.parse(preview.metadata) @@ -395,11 +363,10 @@ export class AdaptersComponent implements OnInit, OnDestroy { } ); - this._toast.success('Preview erfolgreich geladen'); + this._toast.success('Preview loaded successfully.'); }, error: (err) => { - console.error('Preview fehlgeschlagen', err); - this._toast.error('Preview fehlgeschlagen'); + this._toast.error('Error getting preview!'); } }); } @@ -455,70 +422,8 @@ export class AdaptersComponent implements OnInit, OnDestroy { }); } - /*updateAdapterSettings() { - console.log('Rein da – Settings sammeln'); - - const form = this.editingAdapterForm; - if (!form) { - this._toast.error('No editing adapter form available!'); - return; - } - - const newSettings: Record = {}; - for (const [key, ctrl] of Object.entries(form.controls)) { - if (key === 'uniqueName') { - continue; - } - newSettings[key] = (ctrl as AbstractControl).value ?? ''; - console.log(`[Take] ${key} =`, newSettings[key]); - } - - const adapter = this.adapter(); - - const oldSettings = Object.fromEntries(adapter.settings ?? []); - const merged = {...oldSettings, ...newSettings}; - - Object.entries(newSettings).forEach(([k, v]) => { - const merged = adapter.settings.get(k); - if (merged) { - merged.current = v as string; - } else { - - } - }); - - - const poly = new PolyMap( - Object.entries(merged) as [string, string][] - ); - - const updated = new AdapterModel( - adapter.uniqueName, - adapter.adapterName, - poly, - adapter.persistent, - adapter.type, - adapter.mode - ); - - console.log(updated); - this._crud.updateAdapterSettings(updated).subscribe({ - next: res => { - res.error - ? this._toast.exception(res) - : this._toast.success('Updated adapter settings'); - this.modalActive = false; - }, - error: err => { - this._toast.error('Could not update adapter settings'); - console.error(err); - } - }); - }*/ updateAdapterSettings() { - console.log('Rein da – Settings sammeln'); - const form = this.editingAdapterForm; if (!form) { this._toast.error('No editing adapter form available!'); @@ -526,18 +431,21 @@ export class AdaptersComponent implements OnInit, OnDestroy { } const adapter = this.adapter(); - const fd = new FormData(); - let hasUpload = false; + const fd = new FormData(); + let hasUpload = false; + // Collect new settings. const mergedObj: Record = {}; adapter.settings.forEach((ms, key) => { - mergedObj[key] = ms.current ?? ''; // MergedSetting → String + mergedObj[key] = ms.current ?? ''; }); for (const [key, ctrl] of Object.entries(form.controls)) { - if (key === 'uniqueName') { continue; } + if (key === 'uniqueName') { + continue; + } - const val = (ctrl as AbstractControl).value ?? ''; + const val = (ctrl as AbstractControl).value ?? ''; const meta = this.getAdapterSetting(key); if (meta?.template?.type?.toLowerCase() === 'directory') { @@ -550,13 +458,12 @@ export class AdaptersComponent implements OnInit, OnDestroy { } else { mergedObj[key] = val; } - console.log('[Take] ${key} =', mergedObj[key]); } - const poly = new PolyMap( - Object.entries(mergedObj) as [string,string][] - ); + const poly = new PolyMap(Object.entries(mergedObj) as [string, string][]); + + // Create AdapterModel and PreviewRequest depending on if settings changes or reupload. const updated = new AdapterModel( adapter.uniqueName, adapter.adapterName, @@ -567,8 +474,7 @@ export class AdaptersComponent implements OnInit, OnDestroy { ); - - const updated2 = new PreviewRequest( + const updatedForm = new PreviewRequest( adapter.adapterName, adapter.type, mergedObj, @@ -577,7 +483,7 @@ export class AdaptersComponent implements OnInit, OnDestroy { ); if (hasUpload) { - fd.set('body', JSON.stringify(updated2)); + fd.set('body', JSON.stringify(updatedForm)); this._crud.updateAdapterSettingsForm(fd).subscribe(this.handleResult); } else { this._crud.updateAdapterSettings(updated).subscribe(this.handleResult); @@ -598,7 +504,6 @@ export class AdaptersComponent implements OnInit, OnDestroy { }; - getDefaultUniqueName(): string { if (this.adapter !== undefined) { const base = this.adapter().adapterName.toLowerCase(); // + "_"; // TODO: re-enable underscores when graph namespaces work with it diff --git a/src/app/views/doc-card/doc-card.component.ts b/src/app/views/doc-card/doc-card.component.ts index 4857572a..3bb19876 100644 --- a/src/app/views/doc-card/doc-card.component.ts +++ b/src/app/views/doc-card/doc-card.component.ts @@ -2,7 +2,7 @@ import {Component, EventEmitter, Input, Output} from '@angular/core'; import {MatCheckbox, MatCheckboxModule} from '@angular/material/checkbox'; import {FormsModule} from '@angular/forms'; import {MatCard, MatCardContent, MatCardModule, MatCardTitle} from '@angular/material/card'; -import {AbstractNode} from '../../components/data-view/models/result-set.model'; +import {AbstractNode} from '../preview-selection/models/metadataTree.model'; import {NgIf, NgForOf} from '@angular/common'; diff --git a/src/app/views/preview-selection/metadata-tree/metadata-tree.component.ts b/src/app/views/preview-selection/metadata-tree/metadata-tree.component.ts index b01be260..41a5d63c 100644 --- a/src/app/views/preview-selection/metadata-tree/metadata-tree.component.ts +++ b/src/app/views/preview-selection/metadata-tree/metadata-tree.component.ts @@ -2,7 +2,7 @@ import {Component, Input, Output, EventEmitter} from '@angular/core'; import {CommonModule} from '@angular/common'; import {Node} from '../preview-selection.component'; import {FormsModule} from '@angular/forms'; -import {AbstractNode} from '../../../components/data-view/models/result-set.model'; +import {AbstractNode} from '../models/metadataTree.model'; @Component({ selector: 'app-metadata-tree', @@ -25,6 +25,7 @@ export class MetadataTreeComponent { } } + // Automatically collect removed metadata. private collectRemoved(n: AbstractNode, path: string[], out: Set): void { const next = [...path, n.name]; if ((n as any).type === 'ghost') { @@ -33,6 +34,7 @@ export class MetadataTreeComponent { n.children?.forEach(c => this.collectRemoved(c, next, out)); } + // (Un)selecting metadata. toggleColumn(fullKey: string, checked: boolean, diff?: any, type?: any) { this.columnToggle.emit({fullKey, checked, diff, type}); } @@ -66,6 +68,7 @@ export class MetadataTreeComponent { n.children?.forEach(c => this.toggleRecursive(c, next, checked)); } + // TODO Alias names could be display with the physical name getAlias(node: AbstractNode): string { return (node as any).properties?.['alias'] ?? ''; } @@ -82,6 +85,8 @@ export class MetadataTreeComponent { (node as any).properties['alias'] = trimmed; } + // In case when a metadata tree occurs from the observer. + // Correctly displaying added or removed metadata. getNodeClass(node: AbstractNode): string { const props = (node as any).properties || {}; if (props['diff'] === 'ADDED') { diff --git a/src/app/views/preview-selection/models/metadataTree.model.ts b/src/app/views/preview-selection/models/metadataTree.model.ts new file mode 100644 index 00000000..5239e97e --- /dev/null +++ b/src/app/views/preview-selection/models/metadataTree.model.ts @@ -0,0 +1,80 @@ +/** + * AbstractNode interface with general implementation. + */ + +export class Node implements AbstractNode { + type: string; + name: string; + children: AbstractNode[] = []; + properties: { [key: string]: any } = {}; + isSelected?: boolean; + + jsonPath?: string; + cardCandidate?: boolean; + valueType?: string; + + + constructor(type: string, name: string) { + this.type = type; + this.name = name; + } + + addChild(node: AbstractNode): void { + this.children.push(node); + } + + addProperty(key: string, value: any): void { + this.properties[key] = value; + } + + getProperty(key: string): string { + return this.properties[key]; + } +} + + +export interface ColumnToggleEvent { + fullKey: string; + checked: boolean; + diff?: 'ADDED' | 'REMOVED'; + type?: 'ghost' | 'table' | 'column'; +} + +export interface AbstractNode { + type: string; + name: string; + children?: AbstractNode[]; + properties?: { [key: string]: any }; + isSelected?: boolean; + jsonPath?: string; + cardCandidate?: boolean; + valueType?: string; + + addChild(node: AbstractNode): void; + + addProperty(key: string, value: any): void; + + getProperty(key: string): string | null; +} + +/** + * model for handling preview data + */ +export class PreviewResult { + metadata: string; + preview: any[]; + history?: ChangeLogView[]; +} + +export interface ChangeLogView { + adapterName: string; + timestamp: string; + severity: ChangeStatus; + messages: string[]; +} + +export enum ChangeStatus { + CRITICAL = 'CRITICAL', + WARNING = 'WARNING', + OK = 'OK' +} diff --git a/src/app/views/preview-selection/preview-selection.component.ts b/src/app/views/preview-selection/preview-selection.component.ts index f3e79230..534e2c00 100644 --- a/src/app/views/preview-selection/preview-selection.component.ts +++ b/src/app/views/preview-selection/preview-selection.component.ts @@ -1,7 +1,8 @@ import {Component, inject} from '@angular/core'; import {ButtonCloseDirective, ButtonDirective, ColComponent, RowComponent} from '@coreui/angular'; import {Router, RouterLink} from '@angular/router'; -import {AbstractNode, ChangeLogView, ChangeStatus} from '../../components/data-view/models/result-set.model'; +import {AbstractNode, ChangeLogView, ChangeStatus} from './models/metadataTree.model'; +import {ColumnToggleEvent} from './models/metadataTree.model'; import {CommonModule} from '@angular/common'; import {MetadataTreeComponent} from './metadata-tree/metadata-tree.component'; import {AdapterModel, AdapterType, PolyMap} from '../adapters/adapter.model'; @@ -317,6 +318,8 @@ export class PreviewSelectionComponent { } + + export class Node implements AbstractNode { type: string; name: string; @@ -355,11 +358,4 @@ export function reviveTree(raw: any): AbstractNode { return raw as AbstractNode; } -interface ColumnToggleEvent { - fullKey: string; - checked: boolean; - diff?: 'ADDED' | 'REMOVED'; - type?: 'ghost' | 'table' | 'column'; -} -