Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,7 @@
{
"type": "initial",
"maximumWarning": "500kb",
"maximumError": "4mb"
"maximumError": "6mb"
},
{
"type": "anyComponentStyle",
Expand Down
37 changes: 18 additions & 19 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions src/app/_layout/app-header/app-header.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,11 @@
</mat-menu>

<mat-menu #userMenu="matMenu">
<button mat-menu-item routerLink="/admin" data-cy="admin-button">
<mat-icon> build</mat-icon>
<span> Admin Settings <mat-chip class="beta-badge">Beta</mat-chip></span>
Comment on lines +83 to +85
Copy link
Contributor

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 an isAdmin selector or feature flag), so only eligible users see it in the menu.

Suggested implementation:

  <mat-menu #userMenu="matMenu">

  <mat-menu #userMenu="matMenu">
    <button
      *ngIf="isAdmin$ | async"
      mat-menu-item
      routerLink="/admin"
      data-cy="admin-button"
    >
      <mat-icon> build</mat-icon>
      <span> Admin Settings <mat-chip class="beta-badge">Beta</mat-chip></span>
    </button>

    <button mat-menu-item routerLink="/user/" data-cy="setting-button">
      <mat-icon> settings</mat-icon>
      <span>Settings</span>

To fully implement this behavior, the component class (app-header.component.ts) will need:

  1. An isAdmin$ observable (e.g. isAdmin$: Observable<boolean>;) wired to your existing auth/permissions store or service (for example, via an NgRx selector like this.isAdmin$ = this.store.select(selectIsAdmin) or an auth service method).
  2. Any required imports for the observable and state management/auth service being used.
    If your codebase already exposes a different admin flag (e.g. userIsAdmin$, canAccessAdmin$), adjust the *ngIf expression to use that instead.

</button>

<button mat-menu-item routerLink="/user/" data-cy="setting-button">
<mat-icon> settings</mat-icon>
<span>Settings</span>
Expand Down
7 changes: 7 additions & 0 deletions src/app/_layout/app-header/app-header.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,13 @@
}
}

.beta-badge {
height: 1.25rem;
font-size: 0.75rem;
margin-left: 0.5rem;
background-color: var(--theme-header-3-default);
}

@media only screen and (max-width: 1279px) {
.large-screen-text {
display: none;
Expand Down
2 changes: 2 additions & 0 deletions src/app/_layout/layout.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { AppMainLayoutComponent } from "./app-main-layout/app-main-layout.compon
import { BatchCardModule } from "datasets/batch-card/batch-card.module";
import { BreadcrumbModule } from "shared/modules/breadcrumb/breadcrumb.module";
import { UsersModule } from "../users/users.module";
import { MatChipsModule } from "@angular/material/chips";

@NgModule({
declarations: [
Expand All @@ -26,6 +27,7 @@ import { UsersModule } from "../users/users.module";
MatButtonModule,
MatIconModule,
MatMenuModule,
MatChipsModule,
MatToolbarModule,
RouterModule,
BreadcrumbModule,
Expand Down
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
Copy link
Contributor

Choose a reason for hiding this comment

The 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 save() handler, which is confusing and likely incorrect.

Using the same save() handler for all three buttons prevents different logic for preview, export, and save, and is probably unintended. Please either give each button a dedicated handler (e.g. openPreview(), export(), save()) or pass a mode into save (e.g. (click)="save('preview')") and branch on that inside the method.

</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>
Empty file.
97 changes: 97 additions & 0 deletions src/app/admin/admin-config-edit/admin-config-edit.component.ts
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
Copy link
Contributor

Choose a reason for hiding this comment

The 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 currentData is initialized as {} and only updated in onChange, calling save() before any dataChange will persist {} and wipe the existing config. Initialize currentData from the loaded config (e.g. first emission from data$/config$), or have save() fall back to the latest value from data$ when currentData is still the initial {}.

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 }));
}
}
33 changes: 33 additions & 0 deletions src/app/admin/admin-dashboard/admin-dashboard.component.html
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>
Empty file.
82 changes: 82 additions & 0 deletions src/app/admin/admin-dashboard/admin-dashboard.component.ts
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>
Empty file.
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() {}
}
Loading