-
Notifications
You must be signed in to change notification settings - Fork 34
Swap 5087 scicat fe poc render frontend config with json fo #2143
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
282e0b4
463ab53
545d1ab
51cdc85
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| <div style="display: flex; justify-content: space-between; margin-bottom: 10px"> | ||
| <!-- LEFT --> | ||
| <div> | ||
| <button mat-button color="primary" (click)="save()">Json Preview</button> | ||
| </div> | ||
|
|
||
| <!-- RIGHT --> | ||
| <div> | ||
| <button mat-button color="primary" (click)="save()">Export</button> | ||
| <button mat-button color="primary" (click)="save()">Save</button> | ||
|
Comment on lines
+4
to
+10
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue (bug_risk): All three actions (Json Preview, Export, Save) are wired to the same Using the same |
||
| </div> | ||
| </div> | ||
| <div> | ||
| <jsonforms | ||
| [data]="data$ | async" | ||
| [schema]="schema" | ||
| [uischema]="uiSchema" | ||
| [renderers]="renderers" | ||
| (dataChange)="onChange($event)" | ||
| ></jsonforms> | ||
| <div style="display: flex; justify-content: space-between; margin-top: 10px"> | ||
| <!-- LEFT --> | ||
| <div> | ||
| <button mat-button color="primary" (click)="save()">Json Preview</button> | ||
| </div> | ||
|
|
||
| <!-- RIGHT --> | ||
| <div> | ||
| <button mat-button color="primary" (click)="save()">Export</button> | ||
| <button mat-button color="primary" (click)="save()">Save</button> | ||
| </div> | ||
| </div> | ||
| </div> | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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, | ||
| }, | ||
| { | ||
|
Comment on lines
+45
to
+54
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. issue (bug_risk): Saving before any change sends an empty object instead of the loaded configuration. Because |
||
| 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 })); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,33 @@ | ||
| <!-- src/app/admin/admin-dashboard/admin-dashboard.component.html --> | ||
|
|
||
| <nav mat-tab-nav-bar [tabPanel]="tabPanel"> | ||
| <ng-container *ngFor="let link of navLinks"> | ||
| <a | ||
| mat-tab-link | ||
| *ngIf="link.enabled" | ||
| routerLink="{{ link.location }}" | ||
| routerLinkActive | ||
| #rla="routerLinkActive" | ||
| [routerLinkActiveOptions]="routerLinkActiveOptions" | ||
| [active]="rla.isActive" | ||
| [replaceUrl]="true" | ||
| (click)="onTabSelected(link.label)" | ||
| > | ||
| <mat-icon>{{ link.icon }}</mat-icon> | ||
| <span>{{ link.label }}</span> | ||
| </a> | ||
| </ng-container> | ||
| </nav> | ||
| <!-- <ng-template> | ||
| <error-page | ||
| *ngIf="showError" | ||
| [errorTitle]="'Dataset not found'" | ||
| [message]=" | ||
| 'The dataset you are trying to view either doesn\'t exist or you don\'t have permission to view it.' | ||
| " | ||
| > | ||
| </error-page> | ||
| </ng-template> --> | ||
| <mat-tab-nav-panel #tabPanel> | ||
| <router-outlet></router-outlet> | ||
| </mat-tab-nav-panel> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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)); | ||
| // } | ||
| } | ||
| } | ||
| } | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| <div>To be implemented</div> |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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() {} | ||
| } |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
suggestion: Admin menu entry is always shown, which leads non-admin users into a guarded route ending in a 401.
Since the route is guarded by
AdminGuard, non-admins can still click this entry and get redirected to/401, which is a poor experience. Consider conditionally rendering or disabling this item for non-admin users (e.g. based on anisAdminselector or feature flag), so only eligible users see it in the menu.Suggested implementation:
To fully implement this behavior, the component class (
app-header.component.ts) will need:isAdmin$observable (e.g.isAdmin$: Observable<boolean>;) wired to your existing auth/permissions store or service (for example, via an NgRx selector likethis.isAdmin$ = this.store.select(selectIsAdmin)or an auth service method).If your codebase already exposes a different admin flag (e.g.
userIsAdmin$,canAccessAdmin$), adjust the*ngIfexpression to use that instead.