From 282e0b40394c89f01c298d574efa76640148ebbe Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 19:07:47 +0000 Subject: [PATCH 1/4] chore(deps-dev): bump the types group with 2 updates Bumps the types group with 2 updates: [@types/jasmine](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/jasmine) and [@types/node](https://github.com/DefinitelyTyped/DefinitelyTyped/tree/HEAD/types/node). Updates `@types/jasmine` from 5.1.12 to 5.1.13 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/jasmine) Updates `@types/node` from 24.10.0 to 24.10.1 - [Release notes](https://github.com/DefinitelyTyped/DefinitelyTyped/releases) - [Commits](https://github.com/DefinitelyTyped/DefinitelyTyped/commits/HEAD/types/node) --- updated-dependencies: - dependency-name: "@types/jasmine" dependency-version: 5.1.13 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: types - dependency-name: "@types/node" dependency-version: 24.10.1 dependency-type: direct:development update-type: version-update:semver-patch dependency-group: types ... Signed-off-by: dependabot[bot] --- package-lock.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index 1c775a5fb9..98f10ab7ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -6524,9 +6524,9 @@ } }, "node_modules/@types/jasmine": { - "version": "5.1.12", - "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-5.1.12.tgz", - "integrity": "sha512-1BzPxNsFDLDfj9InVR3IeY0ZVf4o9XV+4mDqoCfyPkbsA7dYyKAPAb2co6wLFlHcvxPlt1wShm7zQdV7uTfLGA==", + "version": "5.1.13", + "resolved": "https://registry.npmjs.org/@types/jasmine/-/jasmine-5.1.13.tgz", + "integrity": "sha512-MYCcDkruFc92LeYZux5BC0dmqo2jk+M5UIZ4/oFnAPCXN9mCcQhLyj7F3/Za7rocVyt5YRr1MmqJqFlvQ9LVcg==", "dev": true, "license": "MIT" }, @@ -6568,9 +6568,9 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "24.10.0", - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.0.tgz", - "integrity": "sha512-qzQZRBqkFsYyaSWXuEHc2WR9c0a0CXwiE5FWUvn7ZM+vdy1uZLfCunD38UzhuB7YN/J11ndbDBcTmOdxJo9Q7A==", + "version": "24.10.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", "dev": true, "license": "MIT", "dependencies": { From 463ab5372ead5d0995726f045c27acd8b2090e39 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 19:40:02 +0000 Subject: [PATCH 2/4] chore(deps-dev): bump cypress from 15.6.0 to 15.7.0 Bumps [cypress](https://github.com/cypress-io/cypress) from 15.6.0 to 15.7.0. - [Release notes](https://github.com/cypress-io/cypress/releases) - [Changelog](https://github.com/cypress-io/cypress/blob/develop/CHANGELOG.md) - [Commits](https://github.com/cypress-io/cypress/compare/v15.6.0...v15.7.0) --- updated-dependencies: - dependency-name: cypress dependency-version: 15.7.0 dependency-type: direct:development update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- package-lock.json | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/package-lock.json b/package-lock.json index 98f10ab7ef..784be6baed 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9716,9 +9716,9 @@ "license": "MIT" }, "node_modules/cypress": { - "version": "15.6.0", - "resolved": "https://registry.npmjs.org/cypress/-/cypress-15.6.0.tgz", - "integrity": "sha512-Vqo66GG1vpxZ7H1oDX9umfmzA3nF7Wy80QAc3VjwPREO5zTY4d1xfQFNPpOWleQl9vpdmR2z1liliOcYlRX6rQ==", + "version": "15.7.0", + "resolved": "https://registry.npmjs.org/cypress/-/cypress-15.7.0.tgz", + "integrity": "sha512-1C81zKxnQckYm2XGi37rPV4rN0bzUoWhydhKdOyshJn5gJKszEx5as9VLSZI0jp0ye49QxmnbU4TtMpcD+OmGQ==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -9759,7 +9759,6 @@ "process": "^0.11.10", "proxy-from-env": "1.0.0", "request-progress": "^3.0.0", - "semver": "^7.7.1", "supports-color": "^8.1.1", "systeminformation": "5.27.7", "tmp": "~0.2.4", From 545d1ab4cca19a1ec7662836cbd06d0ced171901 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 24 Nov 2025 19:53:15 +0000 Subject: [PATCH 3/4] chore(deps-dev): bump js-yaml from 3.14.1 to 3.14.2 Bumps [js-yaml](https://github.com/nodeca/js-yaml) from 3.14.1 to 3.14.2. - [Changelog](https://github.com/nodeca/js-yaml/blob/master/CHANGELOG.md) - [Commits](https://github.com/nodeca/js-yaml/compare/3.14.1...3.14.2) --- updated-dependencies: - dependency-name: js-yaml dependency-version: 3.14.2 dependency-type: indirect ... Signed-off-by: dependabot[bot] --- package-lock.json | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/package-lock.json b/package-lock.json index 784be6baed..60dfff5be1 100644 --- a/package-lock.json +++ b/package-lock.json @@ -3566,9 +3566,9 @@ } }, "node_modules/@eslint/eslintrc/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "license": "MIT", "dependencies": { @@ -9550,9 +9550,9 @@ "license": "Python-2.0" }, "node_modules/cosmiconfig/node_modules/js-yaml": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", - "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", + "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", "dev": true, "license": "MIT", "dependencies": { @@ -13600,9 +13600,9 @@ "license": "MIT" }, "node_modules/js-yaml": { - "version": "3.14.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", - "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "version": "3.14.2", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", + "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, "license": "MIT", "dependencies": { From 51cdc85207e2dd6c8fbd3b7a0d08dab9563bf1fb Mon Sep 17 00:00:00 2001 From: junjiequan Date: Fri, 5 Dec 2025 14:04:43 +0100 Subject: [PATCH 4/4] first commit --- angular.json | 2 +- .../app-header/app-header.component.html | 5 + .../app-header/app-header.component.scss | 7 + src/app/_layout/layout.module.ts | 2 + .../admin-config-edit.component.html | 33 + .../admin-config-edit.component.scss | 0 .../admin-config-edit.component.ts | 97 +++ .../admin-dashboard.component.html | 33 + .../admin-dashboard.component.scss | 0 .../admin-dashboard.component.ts | 82 ++ .../admin-userlist-view.component.html | 1 + .../admin-userlist-view.component.scss | 0 .../admin-userlist-view.component.ts | 11 + src/app/admin/admin.module.ts | 36 + .../schema/frontend.config.jsonforms.json | 718 ++++++++++++++++++ src/app/app-config.service.ts | 7 +- src/app/app-routing/admin.guard.ts | 13 +- src/app/app-routing/app-routing.module.ts | 10 +- .../admin-routing/admin.feature.module.ts | 8 + .../admin-routing/admin.routing.module.ts | 30 + .../expand-group-renderer.html | 21 + .../expand-group-renderer.scss | 0 .../expand-group-renderer.ts | 40 + .../jsonforms-custom-renderers.module.ts | 11 +- .../actions/admin.action.spec.ts | 0 .../state-management/actions/admin.action.ts | 25 + .../effects/admin.effects.spec.ts | 0 .../state-management/effects/admin.effects.ts | 60 ++ .../reducers/admin.reducer.spec.ts | 0 .../reducers/admin.reducer.ts | 32 + .../selectors/admin.selectors.spec.ts | 0 .../selectors/admin.selectors.ts | 9 + src/app/state-management/state/admin.store.ts | 7 + src/styles.scss | 25 +- 34 files changed, 1310 insertions(+), 15 deletions(-) create mode 100644 src/app/admin/admin-config-edit/admin-config-edit.component.html create mode 100644 src/app/admin/admin-config-edit/admin-config-edit.component.scss create mode 100644 src/app/admin/admin-config-edit/admin-config-edit.component.ts create mode 100644 src/app/admin/admin-dashboard/admin-dashboard.component.html create mode 100644 src/app/admin/admin-dashboard/admin-dashboard.component.scss create mode 100644 src/app/admin/admin-dashboard/admin-dashboard.component.ts create mode 100644 src/app/admin/admin-userlist-view/admin-userlist-view.component.html create mode 100644 src/app/admin/admin-userlist-view/admin-userlist-view.component.scss create mode 100644 src/app/admin/admin-userlist-view/admin-userlist-view.component.ts create mode 100644 src/app/admin/admin.module.ts create mode 100644 src/app/admin/schema/frontend.config.jsonforms.json create mode 100644 src/app/app-routing/lazy/admin-routing/admin.feature.module.ts create mode 100644 src/app/app-routing/lazy/admin-routing/admin.routing.module.ts create mode 100644 src/app/shared/modules/jsonforms-custom-renderers/expand-group-renderer/expand-group-renderer.html create mode 100644 src/app/shared/modules/jsonforms-custom-renderers/expand-group-renderer/expand-group-renderer.scss create mode 100644 src/app/shared/modules/jsonforms-custom-renderers/expand-group-renderer/expand-group-renderer.ts create mode 100644 src/app/state-management/actions/admin.action.spec.ts create mode 100644 src/app/state-management/actions/admin.action.ts create mode 100644 src/app/state-management/effects/admin.effects.spec.ts create mode 100644 src/app/state-management/effects/admin.effects.ts create mode 100644 src/app/state-management/reducers/admin.reducer.spec.ts create mode 100644 src/app/state-management/reducers/admin.reducer.ts create mode 100644 src/app/state-management/selectors/admin.selectors.spec.ts create mode 100644 src/app/state-management/selectors/admin.selectors.ts create mode 100644 src/app/state-management/state/admin.store.ts diff --git a/angular.json b/angular.json index faf13bc3a5..76629f8393 100644 --- a/angular.json +++ b/angular.json @@ -56,7 +56,7 @@ { "type": "initial", "maximumWarning": "500kb", - "maximumError": "4mb" + "maximumError": "6mb" }, { "type": "anyComponentStyle", diff --git a/src/app/_layout/app-header/app-header.component.html b/src/app/_layout/app-header/app-header.component.html index 1123522112..c1b63813e5 100644 --- a/src/app/_layout/app-header/app-header.component.html +++ b/src/app/_layout/app-header/app-header.component.html @@ -80,6 +80,11 @@ + + + + + +
+ + +
+ +
+ +
+ +
+ +
+ + +
+ + +
+
+
diff --git a/src/app/admin/admin-config-edit/admin-config-edit.component.scss b/src/app/admin/admin-config-edit/admin-config-edit.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/admin/admin-config-edit/admin-config-edit.component.ts b/src/app/admin/admin-config-edit/admin-config-edit.component.ts new file mode 100644 index 0000000000..a52390ee41 --- /dev/null +++ b/src/app/admin/admin-config-edit/admin-config-edit.component.ts @@ -0,0 +1,97 @@ +import { Component, OnInit } from "@angular/core"; +import { Store } from "@ngrx/store"; +import { + loadConfiguration, + updateConfiguration, +} from "state-management/actions/admin.action"; +import { selectConfig } from "state-management/selectors/admin.selectors"; +import schema from "../schema/frontend.config.jsonforms.json"; +import { angularMaterialRenderers } from "@jsonforms/angular-material"; +import { + accordionArrayLayoutRendererTester, + AccordionArrayLayoutRendererComponent, +} from "shared/modules/jsonforms-custom-renderers/expand-panel-renderer/accordion-array-layout-renderer.component"; +import { map } from "rxjs"; +import { + expandGroupTester, + ExpandGroupRendererComponent, +} from "shared/modules/jsonforms-custom-renderers/expand-group-renderer/expand-group-renderer"; +import { + ArrayLayoutRendererCustom, + arrayLayoutRendererTester, +} from "shared/modules/jsonforms-custom-renderers/ingestor-renderer/array-renderer"; + +@Component({ + selector: "admin-config-edit", + templateUrl: "./admin-config-edit.component.html", + styleUrls: ["./admin-config-edit.component.scss"], + standalone: false, +}) +export class AdminConfigEditComponent implements OnInit { + config$ = this.store.select(selectConfig); + data$ = this.config$.pipe( + map((cfg) => { + if (!cfg?.data) return null; + const d = structuredClone(cfg.data); + d.labelsLocalization.dataset = this.toArray(d.labelsLocalization.dataset); + d.labelsLocalization.proposal = this.toArray( + d.labelsLocalization.proposal, + ); + return d; + }), + ); + + showJsonPreview = false; + currentData: any = {}; + schema: any = schema.schema || {}; + uiSchema: any = schema.uiSchema || {}; + renderers = [ + ...angularMaterialRenderers, + { + tester: accordionArrayLayoutRendererTester, + renderer: AccordionArrayLayoutRendererComponent, + }, + { + tester: expandGroupTester, + renderer: ExpandGroupRendererComponent, + }, + { + tester: arrayLayoutRendererTester, + renderer: ArrayLayoutRendererCustom, + }, + ]; + constructor(private store: Store) {} + + ngOnInit(): void { + this.store.dispatch(loadConfiguration()); + } + + toArray(obj: any) { + if (!obj) return []; + return Array.isArray(obj) + ? obj + : Object.entries(obj).map(([key, value]) => ({ key, value })); + } + + toObject(arr: any) { + if (!arr) return {}; + if (!Array.isArray(arr)) return arr; + + return Object.fromEntries(arr.map((i) => [i.key, i.value])); + } + + onChange(event: any) { + this.currentData = event; + } + + save() { + const d = structuredClone(this.currentData); + + d.labelsLocalization = { + dataset: this.toObject(d.labelsLocalization.dataset), + proposal: this.toObject(d.labelsLocalization.proposal), + }; + + this.store.dispatch(updateConfiguration({ config: d })); + } +} diff --git a/src/app/admin/admin-dashboard/admin-dashboard.component.html b/src/app/admin/admin-dashboard/admin-dashboard.component.html new file mode 100644 index 0000000000..b82ca79ed4 --- /dev/null +++ b/src/app/admin/admin-dashboard/admin-dashboard.component.html @@ -0,0 +1,33 @@ + + + + + + + diff --git a/src/app/admin/admin-dashboard/admin-dashboard.component.scss b/src/app/admin/admin-dashboard/admin-dashboard.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/admin/admin-dashboard/admin-dashboard.component.ts b/src/app/admin/admin-dashboard/admin-dashboard.component.ts new file mode 100644 index 0000000000..edb64ba9bd --- /dev/null +++ b/src/app/admin/admin-dashboard/admin-dashboard.component.ts @@ -0,0 +1,82 @@ +import { ChangeDetectorRef, Component, OnInit } from "@angular/core"; +import { MatDialog } from "@angular/material/dialog"; +import { ActivatedRoute, IsActiveMatchOptions } from "@angular/router"; +import { UsersService } from "@scicatproject/scicat-sdk-ts-angular"; +import { AppConfigService } from "app-config.service"; +enum TAB { + configuration = "Configuration", + usersList = "Users List", +} +@Component({ + selector: "app-admin-dashboard", + templateUrl: "./admin-dashboard.component.html", + styleUrls: ["./admin-dashboard.component.scss"], + standalone: false, +}) +export class AdminDashboardComponent implements OnInit { + showError = false; + navLinks: { + location: string; + label: string; + icon: string; + enabled: boolean; + }[] = []; + + routerLinkActiveOptions: IsActiveMatchOptions = { + matrixParams: "ignored", + queryParams: "ignored", + fragment: "ignored", + paths: "exact", + }; + + fetchDataActions: { [tab: string]: { action: any; loaded: boolean } } = { + [TAB.configuration]: { action: "", loaded: false }, + [TAB.usersList]: { action: "", loaded: false }, + }; + + constructor( + public appConfigService: AppConfigService, + private cdRef: ChangeDetectorRef, + private route: ActivatedRoute, + private userService: UsersService, + public dialog: MatDialog, + ) {} + + ngOnInit(): void { + this.navLinks = [ + { + location: "./configuration", + label: TAB.configuration, + icon: "menu", + enabled: true, + }, + { + location: "./usersList", + label: TAB.usersList, + icon: "data_object", + enabled: true, + }, + ]; + } + + onTabSelected(tab: string) { + this.fetchDataForTab(tab); + } + fetchDataForTab(tab: string) { + if (tab in this.fetchDataActions) { + switch (tab) { + case TAB.configuration: + break; + case TAB.usersList: + break; + default: { + // const { action, loaded } = this.fetchDataActions[tab]; + // if (!loaded) { + // this.fetchDataActions[tab].loaded = true; + // this.store.dispatch(action(args)); + // } + } + } + } + } +} diff --git a/src/app/admin/admin-userlist-view/admin-userlist-view.component.html b/src/app/admin/admin-userlist-view/admin-userlist-view.component.html new file mode 100644 index 0000000000..034e3d5761 --- /dev/null +++ b/src/app/admin/admin-userlist-view/admin-userlist-view.component.html @@ -0,0 +1 @@ +
To be implemented
diff --git a/src/app/admin/admin-userlist-view/admin-userlist-view.component.scss b/src/app/admin/admin-userlist-view/admin-userlist-view.component.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/admin/admin-userlist-view/admin-userlist-view.component.ts b/src/app/admin/admin-userlist-view/admin-userlist-view.component.ts new file mode 100644 index 0000000000..e246459ee8 --- /dev/null +++ b/src/app/admin/admin-userlist-view/admin-userlist-view.component.ts @@ -0,0 +1,11 @@ +import { Component, OnInit } from "@angular/core"; + +@Component({ + selector: "admin-userlist-view", + templateUrl: "./admin-userlist-view.component.html", + styleUrls: ["./admin-userlist-view.component.scss"], + standalone: false, +}) +export class AdminUserlistViewComponent { + constructor() {} +} diff --git a/src/app/admin/admin.module.ts b/src/app/admin/admin.module.ts new file mode 100644 index 0000000000..7ff2c88272 --- /dev/null +++ b/src/app/admin/admin.module.ts @@ -0,0 +1,36 @@ +import { NgModule } from "@angular/core"; +import { CommonModule } from "@angular/common"; +import { AdminDashboardComponent } from "./admin-dashboard/admin-dashboard.component"; +import { MatDialogModule } from "@angular/material/dialog"; +import { MatIconModule } from "@angular/material/icon"; +import { MatTabsModule } from "@angular/material/tabs"; +import { RouterModule } from "@angular/router"; +import { SharedScicatFrontendModule } from "shared/shared.module"; +import { EffectsModule } from "@ngrx/effects"; +import { AdminEffects } from "state-management/effects/admin.effects"; +import { StoreModule } from "@ngrx/store"; +import { adminReducer } from "state-management/reducers/admin.reducer"; +import { AdminConfigEditComponent } from "./admin-config-edit/admin-config-edit.component"; +import { AdminUserlistViewComponent } from "./admin-userlist-view/admin-userlist-view.component"; +import { NgxJsonViewerModule } from "ngx-json-viewer"; + +@NgModule({ + imports: [ + CommonModule, + RouterModule, + MatTabsModule, + MatIconModule, + MatDialogModule, + NgxJsonViewerModule, + SharedScicatFrontendModule, + EffectsModule.forFeature([AdminEffects]), + StoreModule.forFeature("admin", adminReducer), + ], + declarations: [ + AdminDashboardComponent, + AdminConfigEditComponent, + AdminUserlistViewComponent, + ], + exports: [AdminDashboardComponent], +}) +export class AdminModule {} diff --git a/src/app/admin/schema/frontend.config.jsonforms.json b/src/app/admin/schema/frontend.config.jsonforms.json new file mode 100644 index 0000000000..b3a1d46c3d --- /dev/null +++ b/src/app/admin/schema/frontend.config.jsonforms.json @@ -0,0 +1,718 @@ +{ + "schema": { + "type": "object", + "properties": { + "defaultMainPage": { + "type": "object", + + "properties": { + "nonAuthenticatedUser": { "type": "string" }, + "authenticatedUser": { "type": "string" } + } + }, + + "ingestorComponent": { + "type": "object", + "properties": { + "ingestorEnabled": { "type": "boolean" } + } + }, + + "checkBoxFilterClickTrigger": { "type": "boolean" }, + "accessTokenPrefix": { "type": "string" }, + "addDatasetEnabled": { "type": "boolean" }, + "archiveWorkflowEnabled": { "type": "boolean" }, + "datasetReduceEnabled": { "type": "boolean" }, + "datasetJsonScientificMetadata": { "type": "boolean" }, + "editDatasetEnabled": { "type": "boolean" }, + "editDatasetSampleEnabled": { "type": "boolean" }, + "editMetadataEnabled": { "type": "boolean" }, + "addSampleEnabled": { "type": "boolean" }, + + "externalAuthEndpoint": { "type": "string" }, + "facility": { "type": "string" }, + "siteIcon": { "type": "string" }, + "siteTitle": { "type": "string" }, + "siteSciCatLogo": { "type": "string" }, + + "loginFacilityLabel": { "type": "string" }, + "loginLdapLabel": { "type": "string" }, + "loginLocalLabel": { "type": "string" }, + + "loginFacilityEnabled": { "type": "boolean" }, + "loginLdapEnabled": { "type": "boolean" }, + "loginLocalEnabled": { "type": "boolean" }, + + "fileColorEnabled": { "type": "boolean" }, + "fileDownloadEnabled": { "type": "boolean" }, + + "gettingStarted": {}, + "ingestManual": {}, + + "jobsEnabled": { "type": "boolean" }, + "jsonMetadataEnabled": { "type": "boolean" }, + "jupyterHubUrl": { "type": "string" }, + "landingPage": { "type": "string" }, + "lbBaseURL": { "type": "string" }, + + "logbookEnabled": { "type": "boolean" }, + "loginFormEnabled": { "type": "boolean" }, + + "metadataPreviewEnabled": { "type": "boolean" }, + "metadataStructure": { "type": "string" }, + + "multipleDownloadAction": { "type": "string" }, + "multipleDownloadEnabled": { "type": "boolean" }, + + "oAuth2Endpoints": { + "type": "array", + "items": { + "type": "object", + "properties": { + "authURL": { "type": "string" }, + "displayText": { "type": "string" } + } + } + }, + + "policiesEnabled": { "type": "boolean" }, + "retrieveDestinations": { + "type": "array", + "items": { "type": "string" } + }, + + "riotBaseUrl": { "type": "string" }, + "scienceSearchEnabled": { "type": "boolean" }, + "scienceSearchUnitsEnabled": { "type": "boolean" }, + "searchPublicDataEnabled": { "type": "boolean" }, + "searchSamples": { "type": "boolean" }, + + "sftpHost": { "type": "string" }, + "sourceFolder": { "type": "string" }, + + "maxDirectDownloadSize": { "type": "number" }, + "maxFileSizeWarning": { "type": "string" }, + + "shareEnabled": { "type": "boolean" }, + "shoppingCartEnabled": { "type": "boolean" }, + "shoppingCartOnHeader": { "type": "boolean" }, + + "tableSciDataEnabled": { "type": "boolean" }, + "datasetDetailsShowMissingProposalId": { "type": "boolean" }, + + "notificationInterceptorEnabled": { "type": "boolean" }, + "metadataEditingUnitListDisabled": { "type": "boolean" }, + "hideEmptyMetadataTable": { "type": "boolean" }, + + "datafilesActionsEnabled": { "type": "boolean" }, + + "datafilesActions": { + "type": "array", + "items": { + "type": "object", + "properties": { + "id": { "type": "string" }, + "description": { "type": "string" }, + "order": { "type": "number" }, + "label": { "type": "string" }, + "files": { "type": "string" }, + "mat_icon": { "type": "string" }, + "url": { "type": "string" }, + "target": { "type": "string" }, + "enabled": { "type": "string" }, + "authorization": { "type": "array", "items": { "type": "string" } }, + "type": { "type": "string" }, + "icon": { "type": "string" }, + "payload": { "type": "string" }, + "filename": { "type": "string" } + } + } + }, + + "defaultDatasetsListSettings": { + "type": "object", + "properties": { + "columns": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "order": { "type": "number" }, + "type": { "type": "string" }, + "enabled": { "type": "boolean" } + } + } + }, + "filters": { + "type": "array", + "items": { + "type": "object", + "properties": { + "key": { "type": "string" }, + "label": { "type": "string" }, + "type": { "type": "string" }, + "description": { "type": "string" }, + "enabled": { "type": "boolean" } + } + } + }, + "conditions": { + "type": "array" + } + } + }, + + "defaultProposalsListSettings": { + "type": "object", + "properties": { + "columns": { + "type": "array", + "items": { + "type": "object", + "properties": { + "name": { "type": "string" }, + "enabled": { "type": "boolean" }, + "width": { "type": "number" }, + "type": { "type": "string" }, + "format": { "type": "string" } + } + } + }, + "filters": { + "type": "array", + "items": { + "type": "object", + "properties": { + "key": { "type": "string" }, + "label": { "type": "string" }, + "type": { "type": "string" }, + "description": { "type": "string" }, + "enabled": { "type": "boolean" } + } + } + } + } + }, + "labelsLocalization": { + "type": "object", + "properties": { + "dataset": { + "type": "array", + "items": { + "type": "object", + "properties": { + "key": { "type": "string" }, + "value": { "type": "string" } + }, + "required": ["key", "value"] + } + }, + "proposal": { + "type": "array", + "items": { + "type": "object", + "properties": { + "key": { "type": "string" }, + "value": { "type": "string" } + }, + "required": ["key", "value"] + } + } + } + }, + "dateFormat": { "type": "string" }, + "datasetDetailComponent": { + "type": "object", + "properties": { + "enableCustomizedComponent": { "type": "boolean" }, + + "customization": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { "type": "string" }, + "label": { "type": "string" }, + "order": { "type": "number" }, + "row": { "type": "number" }, + "col": { "type": "number" }, + "viewMode": { "type": "string" }, + + "options": { + "type": "object", + "properties": { + "limit": { "type": "number" }, + "size": { "type": "string" } + } + }, + + "fields": { + "type": "array", + "items": { + "type": "object", + "properties": { + "element": { "type": "string" }, + "source": { "type": "string" }, + "order": { "type": "number" } + } + } + } + } + } + } + } + }, + "mainMenu": { + "type": "object", + "properties": { + "nonAuthenticatedUser": { + "type": "object", + "properties": { + "datasets": { "type": "boolean" }, + "files": { "type": "boolean" }, + "instruments": { "type": "boolean" }, + "jobs": { "type": "boolean" }, + "policies": { "type": "boolean" }, + "proposals": { "type": "boolean" }, + "publishedData": { "type": "boolean" }, + "samples": { "type": "boolean" } + } + }, + "authenticatedUser": { + "type": "object", + "properties": { + "datasets": { "type": "boolean" }, + "files": { "type": "boolean" }, + "instruments": { "type": "boolean" }, + "jobs": { "type": "boolean" }, + "policies": { "type": "boolean" }, + "proposals": { "type": "boolean" }, + "publishedData": { "type": "boolean" }, + "samples": { "type": "boolean" } + } + } + } + } + } + }, + "uiSchema": { + "type": "VerticalLayout", + "elements": [ + { + "type": "Group", + "label": "Main Page", + "options": { + "expandable": true + }, + "elements": [ + { + "type": "Control", + "scope": "#/properties/defaultMainPage/properties/nonAuthenticatedUser" + }, + { + "type": "Control", + "scope": "#/properties/defaultMainPage/properties/authenticatedUser" + } + ] + }, + { + "type": "Group", + "label": "Ingestor Component", + "options": { + "expandable": true + }, + "elements": [ + { + "type": "Control", + "scope": "#/properties/ingestorComponent/properties/ingestorEnabled" + } + ] + }, + { + "type": "Group", + "label": "General Settings", + "options": { + "expandable": true + }, + "elements": [ + { "type": "Control", "scope": "#/properties/accessTokenPrefix" }, + { + "type": "Control", + "scope": "#/properties/checkBoxFilterClickTrigger" + }, + { "type": "Control", "scope": "#/properties/addDatasetEnabled" }, + { "type": "Control", "scope": "#/properties/archiveWorkflowEnabled" }, + { "type": "Control", "scope": "#/properties/datasetReduceEnabled" }, + { + "type": "Control", + "scope": "#/properties/datasetJsonScientificMetadata" + }, + { "type": "Control", "scope": "#/properties/editDatasetEnabled" }, + { + "type": "Control", + "scope": "#/properties/editDatasetSampleEnabled" + }, + { "type": "Control", "scope": "#/properties/editMetadataEnabled" }, + { "type": "Control", "scope": "#/properties/addSampleEnabled" } + ] + }, + { + "type": "Group", + "options": { + "expandable": true + }, + "label": "Frontend Labels & URLs", + "elements": [ + { + "type": "HorizontalLayout", + "elements": [ + { "type": "Control", "scope": "#/properties/lbBaseURL" }, + { + "type": "Control", + "scope": "#/properties/externalAuthEndpoint" + }, + { "type": "Control", "scope": "#/properties/facility" }, + { "type": "Control", "scope": "#/properties/siteIcon" }, + { "type": "Control", "scope": "#/properties/siteTitle" }, + { "type": "Control", "scope": "#/properties/siteSciCatLogo" } + ] + } + ] + }, + { + "type": "Group", + "options": { + "expandable": true + }, + "label": "Login Options", + "elements": [ + { + "type": "HorizontalLayout", + "elements": [ + { "type": "Control", "scope": "#/properties/loginFacilityLabel" }, + { "type": "Control", "scope": "#/properties/loginLdapLabel" }, + { "type": "Control", "scope": "#/properties/loginLocalLabel" } + ] + }, + { + "type": "HorizontalLayout", + "elements": [ + { + "type": "Control", + "scope": "#/properties/loginFacilityEnabled" + }, + { "type": "Control", "scope": "#/properties/loginLdapEnabled" }, + { "type": "Control", "scope": "#/properties/loginLocalEnabled" } + ] + } + ] + }, + { + "type": "Group", + "options": { + "expandable": true + }, + "label": "File/Display Flags", + "elements": [ + { + "type": "HorizontalLayout", + "elements": [ + { "type": "Control", "scope": "#/properties/jupyterHubUrl" }, + { "type": "Control", "scope": "#/properties/landingPage" }, + { "type": "Control", "scope": "#/properties/metadataStructure" } + ] + }, + + { "type": "Control", "scope": "#/properties/fileColorEnabled" }, + { "type": "Control", "scope": "#/properties/fileDownloadEnabled" }, + { "type": "Control", "scope": "#/properties/jobsEnabled" }, + { "type": "Control", "scope": "#/properties/jsonMetadataEnabled" }, + { "type": "Control", "scope": "#/properties/logbookEnabled" }, + { "type": "Control", "scope": "#/properties/loginFormEnabled" }, + { "type": "Control", "scope": "#/properties/metadataPreviewEnabled" } + ] + }, + { + "type": "Group", + "options": { + "expandable": true + }, + "label": "Download Settings", + "elements": [ + { "type": "Control", "scope": "#/properties/multipleDownloadAction" }, + { "type": "Control", "scope": "#/properties/multipleDownloadEnabled" } + ] + }, + + { + "type": "Group", + "options": { + "expandable": true + }, + "label": "OAuth2 Endpoints", + "elements": [ + { + "type": "Control", + "scope": "#/properties/oAuth2Endpoints" + } + ] + }, + { + "type": "Group", + "label": "SciCat Feature Flags", + "options": { + "expandable": true + }, + "elements": [ + { + "type": "VerticalLayout", + "elements": [ + { + "type": "Control", + "scope": "#/properties/retrieveDestinations" + }, + { "type": "Control", "scope": "#/properties/riotBaseUrl" } + ] + }, + { "type": "Control", "scope": "#/properties/policiesEnabled" }, + { "type": "Control", "scope": "#/properties/scienceSearchEnabled" }, + { + "type": "Control", + "scope": "#/properties/scienceSearchUnitsEnabled" + }, + { + "type": "Control", + "scope": "#/properties/searchPublicDataEnabled" + }, + { "type": "Control", "scope": "#/properties/searchSamples" } + ] + }, + { + "type": "Group", + "label": "SFTP Host & Source Folder", + "options": { + "expandable": true + }, + "elements": [ + { + "type": "HorizontalLayout", + "elements": [ + { "type": "Control", "scope": "#/properties/sftpHost" }, + { "type": "Control", "scope": "#/properties/sourceFolder" } + ] + } + ] + }, + { + "type": "Group", + "label": "Download Limits & Warnings", + "options": { + "expandable": true + }, + "elements": [ + { + "type": "HorizontalLayout", + "elements": [ + { + "type": "Control", + "scope": "#/properties/maxDirectDownloadSize" + }, + { "type": "Control", "scope": "#/properties/maxFileSizeWarning" } + ] + } + ] + }, + { + "type": "Group", + "label": "Datafiles Actions", + "options": { + "expandable": true + }, + "elements": [ + { + "type": "Control", + "scope": "#/properties/datafilesActions", + "options": { + "detail": { + "type": "VerticalLayout", + "elements": [ + { "type": "Control", "scope": "#/properties/id" }, + { + "type": "Control", + "scope": "#/properties/description" + }, + { "type": "Control", "scope": "#/properties/order" }, + { "type": "Control", "scope": "#/properties/label" }, + { "type": "Control", "scope": "#/properties/files" }, + { "type": "Control", "scope": "#/properties/mat_icon" }, + { "type": "Control", "scope": "#/properties/url" }, + { "type": "Control", "scope": "#/properties/target" }, + { "type": "Control", "scope": "#/properties/enabled" }, + { + "type": "Control", + "scope": "#/properties/authorization" + }, + { "type": "Control", "scope": "#/properties/type" }, + { "type": "Control", "scope": "#/properties/icon" }, + { "type": "Control", "scope": "#/properties/payload" }, + { "type": "Control", "scope": "#/properties/filename" } + ] + } + } + } + ] + }, + { + "type": "Group", + "label": "Localization", + "options": { + "expandable": true + }, + "elements": [ + { + "type": "Group", + "label": "Dataset", + "options": { "expandable": true }, + "elements": [ + { + "type": "Control", + "scope": "#/properties/labelsLocalization/properties/dataset" + } + ] + }, + { + "type": "Group", + "label": "Proposal", + "options": { "expandable": true }, + "elements": [ + { + "type": "Control", + "scope": "#/properties/labelsLocalization/properties/proposal" + } + ] + } + ] + }, + { + "type": "Group", + "label": "Dataset Detail Component", + "options": { + "expandable": true + }, + "elements": [ + { + "type": "Control", + "scope": "#/properties/datasetDetailComponent/properties/enableCustomizedComponent" + }, + { + "type": "Control", + "label": "Customization Configuration", + "scope": "#/properties/datasetDetailComponent/properties/customization", + "options": { + "detail": { + "type": "VerticalLayout", + "elements": [ + { + "type": "HorizontalLayout", + "elements": [ + { "type": "Control", "scope": "#/properties/type" }, + { "type": "Control", "scope": "#/properties/label" }, + { "type": "Control", "scope": "#/properties/order" }, + { "type": "Control", "scope": "#/properties/row" }, + { "type": "Control", "scope": "#/properties/col" }, + { "type": "Control", "scope": "#/properties/viewMode" } + ] + }, + { + "type": "Group", + "label": "Attachments Options", + "elements": [ + { + "type": "HorizontalLayout", + "elements": [ + { + "type": "Control", + "scope": "#/properties/options/properties/limit" + }, + { + "type": "Control", + "scope": "#/properties/options/properties/size" + } + ] + } + ] + }, + { + "type": "Control", + "label": "Fields", + "scope": "#/properties/fields", + "options": { + "detail": { + "type": "HorizontalLayout", + "elements": [ + { + "type": "Control", + "scope": "#/properties/element" + }, + { + "type": "Control", + "scope": "#/properties/source" + }, + { + "type": "Control", + "scope": "#/properties/order" + } + ] + } + } + } + ] + } + } + } + ] + }, + { + "type": "Group", + "options": { + "expandable": true + }, + "elements": [ + { + "type": "HorizontalLayout", + "elements": [ + { + "type": "VerticalLayout", + "label": "Non Authenticated User", + "options": { + "expandable": false + }, + "elements": [ + { + "type": "Control", + "scope": "#/properties/mainMenu/properties/nonAuthenticatedUser" + } + ] + }, + { + "type": "VerticalLayout", + "label": "Authenticated User", + "options": { + "expandable": false + }, + "elements": [ + { + "type": "Control", + "scope": "#/properties/mainMenu/properties/authenticatedUser" + } + ] + } + ] + } + ] + }, + { "type": "Control", "scope": "#/properties/dateFormat" } + ] + } +} diff --git a/src/app/app-config.service.ts b/src/app/app-config.service.ts index 65ca265659..d0736dd917 100644 --- a/src/app/app-config.service.ts +++ b/src/app/app-config.service.ts @@ -1,5 +1,6 @@ import { HttpClient } from "@angular/common/http"; import { Injectable } from "@angular/core"; +import { OutputRuntimeConfigDto } from "@scicatproject/scicat-sdk-ts-angular"; import { mergeWith } from "lodash-es"; import { firstValueFrom, of } from "rxjs"; import { catchError, timeout } from "rxjs/operators"; @@ -204,10 +205,12 @@ export class AppConfigService { async loadAppConfig(): Promise { try { - const config = await this.http - .get("/api/v3/admin/config") + const res = await this.http + .get("/api/v3/runtime-config/data/frontendConfig") .pipe(timeout(2000)) .toPromise(); + + const config = (res as OutputRuntimeConfigDto).data; this.appConfig = Object.assign({}, this.appConfig, config); } catch (err) { console.log("No config available in backend, trying with local config."); diff --git a/src/app/app-routing/admin.guard.ts b/src/app/app-routing/admin.guard.ts index 84ae2dbb6b..44e3fb8b4e 100644 --- a/src/app/app-routing/admin.guard.ts +++ b/src/app/app-routing/admin.guard.ts @@ -7,8 +7,11 @@ import { } from "@angular/router"; import { Store } from "@ngrx/store"; import { Observable } from "rxjs"; -import { map } from "rxjs/operators"; -import { selectIsAdmin } from "state-management/selectors/user.selectors"; +import { filter, map, switchMap, tap } from "rxjs/operators"; +import { + selectIsAdmin, + selectIsLoggedIn, +} from "state-management/selectors/user.selectors"; /** * Ensure that the current user is admin @@ -33,8 +36,10 @@ export class AdminGuard implements CanActivate { route: ActivatedRouteSnapshot, state: RouterStateSnapshot, ): Observable { - return this.store.select(selectIsAdmin).pipe( - map((isAdmin: boolean) => { + return this.store.select(selectIsLoggedIn).pipe( + filter((isLoggedIn) => isLoggedIn), + switchMap(() => this.store.select(selectIsAdmin)), + map((isAdmin) => { if (!isAdmin) { this.router.navigate(["/401"], { skipLocationChange: true, diff --git a/src/app/app-routing/app-routing.module.ts b/src/app/app-routing/app-routing.module.ts index bebe91433f..8a883f8ff6 100644 --- a/src/app/app-routing/app-routing.module.ts +++ b/src/app/app-routing/app-routing.module.ts @@ -8,6 +8,7 @@ import { ServiceGuard } from "./service.guard"; import { IngestorGuard } from "./ingestor.guard"; import { MainPageGuard } from "./main-page"; import { RedirectingComponent } from "./redirecting.component"; +import { AdminGuard } from "./admin.guard"; export const routes: Routes = [ { @@ -87,7 +88,6 @@ export const routes: Routes = [ (m) => m.PoliciesFeatureModule, ), }, - { path: "user", loadChildren: () => @@ -95,6 +95,14 @@ export const routes: Routes = [ (m) => m.UsersFeatureModule, ), }, + { + path: "admin", + loadChildren: () => + import("./lazy/admin-routing/admin.feature.module").then( + (m) => m.AdminFeatureModule, + ), + canActivate: [AdminGuard], + }, { path: "about", loadChildren: () => diff --git a/src/app/app-routing/lazy/admin-routing/admin.feature.module.ts b/src/app/app-routing/lazy/admin-routing/admin.feature.module.ts new file mode 100644 index 0000000000..0470cef2fd --- /dev/null +++ b/src/app/app-routing/lazy/admin-routing/admin.feature.module.ts @@ -0,0 +1,8 @@ +import { NgModule } from "@angular/core"; +import { AdminModule } from "admin/admin.module"; +import { AdminRoutingModule } from "./admin.routing.module"; + +@NgModule({ + imports: [AdminModule, AdminRoutingModule], +}) +export class AdminFeatureModule {} diff --git a/src/app/app-routing/lazy/admin-routing/admin.routing.module.ts b/src/app/app-routing/lazy/admin-routing/admin.routing.module.ts new file mode 100644 index 0000000000..dc5f33bc4b --- /dev/null +++ b/src/app/app-routing/lazy/admin-routing/admin.routing.module.ts @@ -0,0 +1,30 @@ +import { NgModule } from "@angular/core"; +import { RouterModule, Routes } from "@angular/router"; +import { AdminGuard } from "app-routing/admin.guard"; +import { AdminDashboardComponent } from "admin/admin-dashboard/admin-dashboard.component"; +import { AdminConfigEditComponent } from "admin/admin-config-edit/admin-config-edit.component"; +import { AdminUserlistViewComponent } from "admin/admin-userlist-view/admin-userlist-view.component"; + +const routes: Routes = [ + { + path: "", + component: AdminDashboardComponent, // parent with router-outlet + canActivate: [AdminGuard], + children: [ + { path: "", redirectTo: "configuration", pathMatch: "full" }, + { + path: "configuration", + component: AdminConfigEditComponent, + }, + { + path: "usersList", + component: AdminUserlistViewComponent, + }, + ], + }, +]; +@NgModule({ + imports: [RouterModule.forChild(routes)], + exports: [RouterModule], +}) +export class AdminRoutingModule {} diff --git a/src/app/shared/modules/jsonforms-custom-renderers/expand-group-renderer/expand-group-renderer.html b/src/app/shared/modules/jsonforms-custom-renderers/expand-group-renderer/expand-group-renderer.html new file mode 100644 index 0000000000..177c0c968f --- /dev/null +++ b/src/app/shared/modules/jsonforms-custom-renderers/expand-group-renderer/expand-group-renderer.html @@ -0,0 +1,21 @@ + + + {{ uischema?.['label'] || 'Group' }} + + +
+ +
+
+ + +
+

{{ uischema?.['label'] || 'Group' }}

+ +
+ +
+
+
diff --git a/src/app/shared/modules/jsonforms-custom-renderers/expand-group-renderer/expand-group-renderer.scss b/src/app/shared/modules/jsonforms-custom-renderers/expand-group-renderer/expand-group-renderer.scss new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/shared/modules/jsonforms-custom-renderers/expand-group-renderer/expand-group-renderer.ts b/src/app/shared/modules/jsonforms-custom-renderers/expand-group-renderer/expand-group-renderer.ts new file mode 100644 index 0000000000..e33a9ace5d --- /dev/null +++ b/src/app/shared/modules/jsonforms-custom-renderers/expand-group-renderer/expand-group-renderer.ts @@ -0,0 +1,40 @@ +import { Component, ChangeDetectionStrategy } from "@angular/core"; +import { + JsonFormsAngularService, + JsonFormsBaseRenderer, + JsonFormsControl, +} from "@jsonforms/angular"; +import { + GroupLayout, + Layout, + RankedTester, + rankWith, + uiTypeIs, +} from "@jsonforms/core"; + +@Component({ + selector: "app-expand-group-renderer", + templateUrl: "./expand-group-renderer.html", + styleUrls: ["./expand-group-renderer.scss"], + changeDetection: ChangeDetectionStrategy.OnPush, + standalone: false, +}) +export class ExpandGroupRendererComponent extends JsonFormsBaseRenderer { + getProps(idx: number) { + return { + uischema: this.uischema.elements[idx], + schema: this.schema, + path: this.path, + }; + } + + trackByFn(index: number) { + return index; + } + + isExpandable() { + return this.uischema.options?.expandable === true; + } +} + +export const expandGroupTester: RankedTester = rankWith(3, uiTypeIs("Group")); diff --git a/src/app/shared/modules/jsonforms-custom-renderers/jsonforms-custom-renderers.module.ts b/src/app/shared/modules/jsonforms-custom-renderers/jsonforms-custom-renderers.module.ts index 46693411d1..9bf0693d19 100644 --- a/src/app/shared/modules/jsonforms-custom-renderers/jsonforms-custom-renderers.module.ts +++ b/src/app/shared/modules/jsonforms-custom-renderers/jsonforms-custom-renderers.module.ts @@ -6,9 +6,13 @@ import { JsonFormsModule } from "@jsonforms/angular"; import { JsonFormsAngularMaterialModule } from "@jsonforms/angular-material"; import { MatTooltipModule } from "@angular/material/tooltip"; import { MatBadgeModule } from "@angular/material/badge"; +import { ExpandGroupRendererComponent } from "./expand-group-renderer/expand-group-renderer"; @NgModule({ - declarations: [AccordionArrayLayoutRendererComponent], + declarations: [ + AccordionArrayLayoutRendererComponent, + ExpandGroupRendererComponent, + ], imports: [ MatExpansionModule, JsonFormsModule, @@ -16,7 +20,10 @@ import { MatBadgeModule } from "@angular/material/badge"; MatTooltipModule, MatBadgeModule, ], - exports: [AccordionArrayLayoutRendererComponent], + exports: [ + AccordionArrayLayoutRendererComponent, + ExpandGroupRendererComponent, + ], providers: [], }) export class JsonFormsCustomRenderersModule {} diff --git a/src/app/state-management/actions/admin.action.spec.ts b/src/app/state-management/actions/admin.action.spec.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/state-management/actions/admin.action.ts b/src/app/state-management/actions/admin.action.ts new file mode 100644 index 0000000000..c8e9a0a49b --- /dev/null +++ b/src/app/state-management/actions/admin.action.ts @@ -0,0 +1,25 @@ +import { createAction, props } from "@ngrx/store"; +import { AdminConfiguration } from "state-management/effects/admin.effects"; + +export const loadConfiguration = createAction("[Admin] Load Configuration"); +export const loadConfigurationSuccess = createAction( + "[Admin] Load Configuration Success", + props<{ config: AdminConfiguration }>(), +); +export const loadConfigurationFailure = createAction( + "[Admin] Load Configuration Failure", + props<{ error: any }>(), +); + +export const updateConfiguration = createAction( + "[Admin] Update Configuration", + props<{ config: Partial }>(), +); +export const updateConfigurationSuccess = createAction( + "[Admin] Update Configuration Success", + props<{ config: AdminConfiguration }>(), +); +export const updateConfigurationFailure = createAction( + "[Admin] Update Configuration Failure", + props<{ error: any }>(), +); diff --git a/src/app/state-management/effects/admin.effects.spec.ts b/src/app/state-management/effects/admin.effects.spec.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/state-management/effects/admin.effects.ts b/src/app/state-management/effects/admin.effects.ts new file mode 100644 index 0000000000..9e30f0fd97 --- /dev/null +++ b/src/app/state-management/effects/admin.effects.ts @@ -0,0 +1,60 @@ +import { Injectable } from "@angular/core"; +import { Actions, createEffect, ofType } from "@ngrx/effects"; +import { of } from "rxjs"; +import { switchMap, map, catchError, exhaustMap } from "rxjs/operators"; +import { + loadConfiguration, + loadConfigurationFailure, + loadConfigurationSuccess, + updateConfiguration, + updateConfigurationFailure, + updateConfigurationSuccess, +} from "state-management/actions/admin.action"; +import { RuntimeConfigService } from "@scicatproject/scicat-sdk-ts-angular"; + +// Types +export interface AdminConfiguration { + [key: string]: any; +} + +@Injectable() +export class AdminEffects { + constructor( + private actions$: Actions, + private runtimeConfigService: RuntimeConfigService, + ) {} + + loadConfiguration$ = createEffect(() => + this.actions$.pipe( + ofType(loadConfiguration), + switchMap(() => { + const configName = "frontendConfig"; + return this.runtimeConfigService + .runtimeConfigControllerGetConfigV3(configName) + .pipe( + map((config: AdminConfiguration) => { + return loadConfigurationSuccess({ config }); + }), + catchError((error) => of(loadConfigurationFailure({ error }))), + ); + }), + ), + ); + + updateConfiguration$ = createEffect(() => + this.actions$.pipe( + ofType(updateConfiguration), + exhaustMap(({ config }) => { + const configName = "frontendConfig"; + return this.runtimeConfigService + .runtimeConfigControllerUpdateConfigV3(configName, config) + .pipe( + map((config: AdminConfiguration) => + updateConfigurationSuccess({ config }), + ), + catchError((error) => of(updateConfigurationFailure({ error }))), + ); + }), + ), + ); +} diff --git a/src/app/state-management/reducers/admin.reducer.spec.ts b/src/app/state-management/reducers/admin.reducer.spec.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/state-management/reducers/admin.reducer.ts b/src/app/state-management/reducers/admin.reducer.ts new file mode 100644 index 0000000000..269ebcfaf9 --- /dev/null +++ b/src/app/state-management/reducers/admin.reducer.ts @@ -0,0 +1,32 @@ +import { createReducer, Action, on } from "@ngrx/store"; +import * as fromActions from "state-management/actions/admin.action"; +import { + AdminState, + initialAdminState, +} from "state-management/state/admin.store"; + +const reducer = createReducer( + initialAdminState, + on( + fromActions.loadConfigurationSuccess, + (state, { config }): AdminState => ({ + ...state, + config, + }), + ), + + on( + fromActions.updateConfigurationSuccess, + (state, { config }): AdminState => ({ + ...state, + config, + }), + ), +); + +export const adminReducer = (state: AdminState | undefined, action: Action) => { + if (action.type.indexOf("[Admin]") !== -1) { + console.log("Action came in! " + action.type); + } + return reducer(state, action); +}; diff --git a/src/app/state-management/selectors/admin.selectors.spec.ts b/src/app/state-management/selectors/admin.selectors.spec.ts new file mode 100644 index 0000000000..e69de29bb2 diff --git a/src/app/state-management/selectors/admin.selectors.ts b/src/app/state-management/selectors/admin.selectors.ts new file mode 100644 index 0000000000..d8a0c77be6 --- /dev/null +++ b/src/app/state-management/selectors/admin.selectors.ts @@ -0,0 +1,9 @@ +import { createFeatureSelector, createSelector } from "@ngrx/store"; +import { AdminState } from "state-management/state/admin.store"; + +const selectAdminState = createFeatureSelector("admin"); + +export const selectConfig = createSelector( + selectAdminState, + (state) => state.config, +); diff --git a/src/app/state-management/state/admin.store.ts b/src/app/state-management/state/admin.store.ts new file mode 100644 index 0000000000..a788f5cab7 --- /dev/null +++ b/src/app/state-management/state/admin.store.ts @@ -0,0 +1,7 @@ +export interface AdminState { + config: any; +} + +export const initialAdminState: AdminState = { + config: {}, +}; diff --git a/src/styles.scss b/src/styles.scss index d945489c77..940dcfb3bd 100644 --- a/src/styles.scss +++ b/src/styles.scss @@ -416,12 +416,27 @@ a:hover { } // Jsonforms customizations -jsonforms .mat-mdc-form-field { - .mat-mdc-form-field-subscript-dynamic-size { - min-height: unset !important; +jsonforms { + .mat-mdc-form-field { + .mat-mdc-form-field-subscript-dynamic-size { + min-height: unset !important; + } + .mat-mdc-form-field-hint-wrapper { + display: none !important; + } + } + + .boolean-control { + mat-error:empty { + display: none !important; + } + .mdc-checkbox { + margin-left: -9px !important; + } } - .mat-mdc-form-field-hint-wrapper { - display: none !important; + + .mat-mdc-card-content { + padding: 0 !important; } }