diff --git a/client/src/app/admin/admin.component.html b/client/src/app/admin/admin.component.html index bd1e260acf97f000884a585cceef5991791c07e7..5c884c3b52f4a5225b2b6596965fb0ac8845b72c 100644 --- a/client/src/app/admin/admin.component.html +++ b/client/src/app/admin/admin.component.html @@ -1,17 +1,12 @@ <header> - <app-navbar - [links]="links" + <app-admin-navbar [isAuthenticated]="isAuthenticated | async" [userProfile]="userProfile | async" - [userRoles]="userRoles | async" - [baseHref]="getBaseHref()" [authenticationEnabled]="getAuthenticationEnabled()" - [adminRoles]="getAdminRoles()" - [url]="url | async" (login)="login()" (logout)="logout()" (openEditProfile)="openEditProfile()"> - </app-navbar> + </app-admin-navbar> </header> <main role="main" class="container-fluid pb-4"> <router-outlet></router-outlet> diff --git a/client/src/app/admin/admin.component.ts b/client/src/app/admin/admin.component.ts index d8f886749a1519025e28d8556ec5207b4e81095c..3c55077ed60f028e2af755b90491a0becd7770c4 100644 --- a/client/src/app/admin/admin.component.ts +++ b/client/src/app/admin/admin.component.ts @@ -11,7 +11,6 @@ import { Component, OnInit } from '@angular/core'; import { Observable } from 'rxjs'; import { Store } from '@ngrx/store'; -import * as fromRouter from '@ngrx/router-store'; import { UserProfile } from 'src/app/auth/user-profile.model'; import * as authActions from 'src/app/auth/auth.actions'; @@ -25,7 +24,7 @@ import { AppConfigService } from 'src/app/app-config.service'; }) /** * @class - * @classdesc Portal home container. + * @classdesc Admin container. * * @implements OnInit */ @@ -33,20 +32,12 @@ export class AdminComponent implements OnInit { public favIcon: HTMLLinkElement = document.querySelector('#favicon'); public title: HTMLLinkElement = document.querySelector('#title'); public body: HTMLBodyElement = document.querySelector('body'); - public links = [ - { label: 'Instances', icon: 'fas fa-object-group', routerLink: 'instance/instance-list' }, - { label: 'Databases', icon: 'fas fa-database', routerLink: 'database/database-list'} - ]; public isAuthenticated: Observable<boolean>; public userProfile: Observable<UserProfile>; - public userRoles: Observable<string[]>; - public url: Observable<string>; constructor(private store: Store<{ }>, private config: AppConfigService) { this.isAuthenticated = store.select(authSelector.selectIsAuthenticated); this.userProfile = store.select(authSelector.selectUserProfile); - this.userRoles = store.select(authSelector.selectUserRoles); - this.url = store.select(fromRouter.getSelectors().selectUrl); } ngOnInit() { @@ -56,18 +47,10 @@ export class AdminComponent implements OnInit { Promise.resolve(null).then(() => this.store.dispatch(databaseActions.loadDatabaseList())); } - getBaseHref() { - return this.config.baseHref; - } - getAuthenticationEnabled() { return this.config.authenticationEnabled; } - getAdminRoles(): string[] { - return this.config.adminRoles; - } - login(): void { this.store.dispatch(authActions.login({ redirectUri: window.location.toString() })); } diff --git a/client/src/app/admin/admin.module.ts b/client/src/app/admin/admin.module.ts index fc26cfe972c1cf7866d9180daa6b057dc56ac238..432b436da0520a466142cc0557cbda93913ef495 100644 --- a/client/src/app/admin/admin.module.ts +++ b/client/src/app/admin/admin.module.ts @@ -15,6 +15,7 @@ import { EffectsModule } from '@ngrx/effects'; import { SharedModule } from 'src/app/shared/shared.module'; import { AdminSharedModule } from './admin-shared/admin-shared.module'; import { AdminRoutingModule, routedComponents } from './admin-routing.module'; +import { dummiesComponents } from './components'; import { adminReducer } from './admin.reducer'; import { adminEffects } from './store/effects'; import { adminServices } from './store/services'; @@ -28,7 +29,8 @@ import { adminServices } from './store/services'; EffectsModule.forFeature(adminEffects) ], declarations: [ - routedComponents + routedComponents, + dummiesComponents ], providers: [ adminServices diff --git a/client/src/app/shared/components/navbar.component.html b/client/src/app/admin/components/admin-navbar.component.html similarity index 63% rename from client/src/app/shared/components/navbar.component.html rename to client/src/app/admin/components/admin-navbar.component.html index 347d4b7b7f1480d137d18442569f7f47c6f58426..155e4ffc30fb23b2a7bd94425beda48fddc04b8e 100644 --- a/client/src/app/shared/components/navbar.component.html +++ b/client/src/app/admin/components/admin-navbar.component.html @@ -1,35 +1,31 @@ <nav class="navbar navbar-light bg-light navbar-expand-md fixed-top border-bottom"> <!-- Logo --> - <a *ngIf="!instance" href="{{ baseHref }}" class="navbar-brand"> + <a routerLink="/admin" class="navbar-brand"> <img src="assets/cesam_anis40.png" alt="ANIS logo" /> </a> - <a *ngIf="instance" routerLink="/instance/{{ instance.name }}" class="navbar-brand"> - <img *ngIf="instance.public" [src]="getLogoHref()" alt="Instance logo" /> - <img *ngIf="!instance.public" [src]="getLogoHref() | authImage | async" alt="Instance logo" /> - </a> - - <!-- Right Navigation --> + <!-- Navigation --> <div class="collapse navbar-collapse" id="navbarCollapse"> <ul class="navbar-nav mr-auto"> - <li *ngFor="let link of links" class="nav-item pr-3"> - <a class="nav-link" [routerLink]="link.routerLink" routerLinkActive="active"> - <span [ngClass]="link.icon"></span> {{ link.label }} + <li class="nav-item pr-3"> + <a class="nav-link" routerLink="instance/instance-list" routerLinkActive="active"> + <span class="fas fa-object-group"></span> Instances + </a> + </li> + <li class="nav-item pr-3"> + <a class="nav-link" routerLink="database/database-list" routerLinkActive="active"> + <span class="fas fa-database"></span> Databases </a> </li> </ul> <ul class="navbar-nav justify-content-end"> <li class="nav-item pr-3"> - <a *ngIf="isAdminRoute() || (instance && instance.back_to_portal)" class="nav-link" routerLink="/portal" routerLinkActive="active"> + <a class="nav-link" routerLink="/portal" routerLinkActive="active"> <span class="fa-solid fa-right-to-bracket"></span> Back to portal </a> </li> - <li *ngIf="!authenticationEnabled && !isAdminRoute()" class="nav-item pr-3"> - <a class="nav-link" routerLink="/admin" routerLinkActive="active"> - <span class="fas fa-tools"></span> Admin - </a> - </li> </ul> + <!-- sign in / sign out --> <button *ngIf="authenticationEnabled && !isAuthenticated" class="btn btn-outline-success my-2 my-sm-0" id="button-sign-in" @@ -38,7 +34,7 @@ </button> <span *ngIf="isAuthenticated" id="dropdown-menu" dropdown> <button id="button-basic" dropdownToggle type="button" class="btn btn-light" aria-controls="dropdown-basic"> - <span class="fa-stack" [ngStyle]="{ color: instance ? instance.design_color : '#7AC29A' }"> + <span class="fa-stack" [ngStyle]="{ color: '#7AC29A' }"> <span class="fas fa-circle fa-2x"></span> <span class="fas fa-user fa-stack-1x fa-inverse"></span> </span> @@ -51,9 +47,6 @@ </li> <li class="divider dropdown-divider"></li> <li role="menuitem"> - <a *ngIf="isAdmin() && !isAdminRoute()" class="dropdown-item pointer" routerLink="/admin"> - <span class="fas fa-tools"></span> Admin - </a> <a class="dropdown-item pointer" (click)="openEditProfile.emit()"> <span class="fas fa-id-card"></span> Edit profile </a> @@ -68,7 +61,7 @@ </span> </div> - <!-- Dropdown appearing on mobile only --> + <!-- Navigation Mobile --> <span dropdown> <button id="button-basic" dropdownToggle type="button" class="navbar-toggler" aria-controls="dropdown-basic"> <span class="fas fa-bars"></span> @@ -78,27 +71,24 @@ <li *ngIf="isAuthenticated" role="menuitem"> <span class="dropdown-item font-italic">{{ userProfile.email }}</span> </li> - <li *ngIf="isAuthenticated && links.length > 0" class="divider dropdown-divider"></li> - <li *ngFor="let link of links" role="menuitem"> - <a class="dropdown-item" [routerLink]="link.routerLink"> - <span [ngClass]="link.icon" class="fa-fw"></span> {{ link.label }} + <li *ngIf="isAuthenticated" class="divider dropdown-divider"></li> + <li role="menuitem"> + <a class="dropdown-item" routerLink="instance/instance-list"> + <span class="fas fa-object-group fa-fw"></span> Instances </a> </li> <li role="menuitem"> - <a *ngIf="!isPortalRoute()" class="dropdown-item" routerLink="/portal"> - <span class="fa-solid fa-right-to-bracket fa-fw"></span> Back to portal + <a class="dropdown-item" routerLink="database/database-list"> + <span class="fas fa-database fa-fw"></span> Databases </a> </li> - <li *ngIf="isAuthenticated || (!authenticationEnabled && !isAdminRoute())" class="divider dropdown-divider"></li> - <li *ngIf="!authenticationEnabled && !isAdminRoute()" role="menuitem"> - <a class="dropdown-item pointer" routerLink="/admin"> - <span class="fas fa-tools"></span> Admin + <li role="menuitem"> + <a class="dropdown-item" routerLink="/portal"> + <span class="fa-solid fa-right-to-bracket fa-fw"></span> Back to portal </a> </li> + <li *ngIf="isAuthenticated" class="divider dropdown-divider"></li> <li *ngIf="isAuthenticated" role="menuitem"> - <a *ngIf="isAdmin() && !isAdminRoute()" class="dropdown-item pointer" routerLink="/admin"> - <span class="fas fa-tools"></span> Admin - </a> <a class="dropdown-item pointer" (click)="openEditProfile.emit()"> <span class="fas fa-id-card"></span> Edit profile </a> diff --git a/client/src/app/shared/components/navbar.component.scss b/client/src/app/admin/components/admin-navbar.component.scss similarity index 100% rename from client/src/app/shared/components/navbar.component.scss rename to client/src/app/admin/components/admin-navbar.component.scss diff --git a/client/src/app/admin/components/admin-navbar.component.ts b/client/src/app/admin/components/admin-navbar.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..9e61c3efdf1d468c8bdfbd0aeecbb63457af637b --- /dev/null +++ b/client/src/app/admin/components/admin-navbar.component.ts @@ -0,0 +1,18 @@ +import { Component, ChangeDetectionStrategy, Input, Output, EventEmitter } from '@angular/core'; + +import { UserProfile } from 'src/app/auth/user-profile.model'; + +@Component({ + selector: 'app-admin-navbar', + templateUrl: 'admin-navbar.component.html', + styleUrls: [ 'admin-navbar.component.scss' ], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class AdminNavbarComponent { + @Input() isAuthenticated: boolean; + @Input() userProfile: UserProfile = null; + @Input() authenticationEnabled: boolean; + @Output() login: EventEmitter<any> = new EventEmitter(); + @Output() logout: EventEmitter<any> = new EventEmitter(); + @Output() openEditProfile: EventEmitter<any> = new EventEmitter(); +} diff --git a/client/src/app/admin/components/index.ts b/client/src/app/admin/components/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..f66c3652c6a8d53e08910ceeddbbdfbb1e11b2c7 --- /dev/null +++ b/client/src/app/admin/components/index.ts @@ -0,0 +1,14 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { AdminNavbarComponent } from './admin-navbar.component'; + +export const dummiesComponents = [ + AdminNavbarComponent +]; diff --git a/client/src/app/instance/components/index.ts b/client/src/app/instance/components/index.ts new file mode 100644 index 0000000000000000000000000000000000000000..2c1e865542fe568f0b576167f3e0a6d6a0b9faab --- /dev/null +++ b/client/src/app/instance/components/index.ts @@ -0,0 +1,14 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +import { InstanceNavbarComponent } from './instance-navbar.component'; + +export const dummiesComponents = [ + InstanceNavbarComponent +]; diff --git a/client/src/app/instance/components/instance-navbar.component.html b/client/src/app/instance/components/instance-navbar.component.html new file mode 100644 index 0000000000000000000000000000000000000000..e0390921f8a381486bc361b49ad9ce07e4284798 --- /dev/null +++ b/client/src/app/instance/components/instance-navbar.component.html @@ -0,0 +1,147 @@ +<nav class="navbar navbar-light bg-light navbar-expand-md fixed-top border-bottom"> + <!-- Logo --> + <a routerLink="/instance/{{ instance.name }}" class="navbar-brand"> + <img *ngIf="instance.public" [src]="getLogoHref()" alt="Instance logo" /> + <img *ngIf="!instance.public" [src]="getLogoHref() | authImage | async" alt="Instance logo" /> + </a> + + <!-- Navigation --> + <div class="collapse navbar-collapse" id="navbarCollapse"> + <ul class="navbar-nav mr-auto"> + <li class="nav-item pr-3"> + <a class="nav-link" routerLink="home" routerLinkActive="active"> + <span class="fas fa-home"></span> Home + </a> + </li> + <li *ngIf="instance.search_by_criteria_allowed" class="nav-item pr-3"> + <a class="nav-link" routerLink="search" routerLinkActive="active"> + <span class="fas fa-search"></span> {{ instance.search_by_criteria_label }} + </a> + </li> + <li *ngIf="instance.search_multiple_allowed" class="nav-item pr-3"> + <a class="nav-link" routerLink="search-multiple" routerLinkActive="active"> + <span class="fas fa-search-plus"></span> {{ instance.search_multiple_label }} + </a> + </li> + <li *ngIf="instance.documentation_allowed" class="nav-item pr-3"> + <a class="nav-link" routerLink="documentation" routerLinkActive="active"> + <span class="fas fa-question"></span> {{ instance.documentation_label }} + </a> + </li> + </ul> + + <ul class="navbar-nav justify-content-end"> + <li class="nav-item pr-3"> + <a *ngIf="instance.back_to_portal" class="nav-link" routerLink="/portal" routerLinkActive="active"> + <span class="fa-solid fa-right-to-bracket"></span> Back to portal + </a> + </li> + <li *ngIf="!authenticationEnabled" class="nav-item pr-3"> + <a class="nav-link" routerLink="/admin" routerLinkActive="active"> + <span class="fas fa-tools"></span> Admin + </a> + </li> + </ul> + <!-- sign in / sign out --> + <button *ngIf="authenticationEnabled && !isAuthenticated" + class="btn btn-outline-success my-2 my-sm-0" + id="button-sign-in" + (click)="login.emit()"> + Sign In / Register + </button> + <span *ngIf="isAuthenticated" id="dropdown-menu" dropdown> + <button id="button-basic" dropdownToggle type="button" class="btn btn-light" aria-controls="dropdown-basic"> + <span class="fa-stack" [ngStyle]="{ color: instance.design_color }"> + <span class="fas fa-circle fa-2x"></span> + <span class="fas fa-user fa-stack-1x fa-inverse"></span> + </span> + + <span class="fas fa-chevron-down text-secondary"></span> + </button> + <ul id="basic-link-dropdown" *dropdownMenu class="dropdown-menu dropdown-menu-right dropdown-up" role="menu" aria-labelledby="basic-link"> + <li id="li-email" role="menuitem"> + <span class="dropdown-item font-italic">{{ userProfile.email }}</span> + </li> + <li class="divider dropdown-divider"></li> + <li role="menuitem"> + <a *ngIf="isAdmin()" class="dropdown-item pointer" routerLink="/admin"> + <span class="fas fa-tools"></span> Admin + </a> + <a class="dropdown-item pointer" (click)="openEditProfile.emit()"> + <span class="fas fa-id-card"></span> Edit profile + </a> + </li> + <li class="divider dropdown-divider"></li> + <li role="menuitem"> + <a class="dropdown-item text-danger pointer" (click)="logout.emit()"> + <span class="fas fa-sign-out-alt fa-fw"></span> Sign Out + </a> + </li> + </ul> + </span> + </div> + + <!-- Navigation Mobile --> + <span dropdown> + <button id="button-basic" dropdownToggle type="button" class="navbar-toggler" aria-controls="dropdown-basic"> + <span class="fas fa-bars"></span> + </button> + <ul id="basic-link-dropdown" *dropdownMenu class="dropdown-menu dropdown-menu-right dropdown-up" role="menu" + aria-labelledby="basic-link"> + <li *ngIf="isAuthenticated" role="menuitem"> + <span class="dropdown-item font-italic">{{ userProfile.email }}</span> + </li> + <li *ngIf="isAuthenticated" class="divider dropdown-divider"></li> + <li role="menuitem"> + <a class="dropdown-item" routerLink="home"> + <span class="fas fa-home fa-fw"></span> Home + </a> + </li> + <li *ngIf="instance.search_by_criteria_allowed" role="menuitem"> + <a class="dropdown-item" routerLink="search"> + <span class="fas fa-search fa-fw"></span> {{ instance.search_by_criteria_label }} + </a> + </li> + <li *ngIf="instance.search_multiple_allowed" role="menuitem"> + <a class="dropdown-item" routerLink="search-multiple"> + <span class="fas fa-search-plus fa-fw"></span> {{ instance.search_multiple_label }} + </a> + </li> + <li *ngIf="instance.documentation_allowed" role="menuitem"> + <a class="dropdown-item" routerLink="documentation"> + <span class="fas fa-question fa-fw"></span> {{ instance.documentation_label }} + </a> + </li> + <li role="menuitem"> + <a *ngIf="instance.back_to_portal" class="dropdown-item" routerLink="/portal"> + <span class="fa-solid fa-right-to-bracket fa-fw"></span> Back to portal + </a> + </li> + <li *ngIf="isAuthenticated || !authenticationEnabled" class="divider dropdown-divider"></li> + <li *ngIf="!authenticationEnabled" role="menuitem"> + <a class="dropdown-item pointer" routerLink="/admin"> + <span class="fas fa-tools"></span> Admin + </a> + </li> + <li *ngIf="isAuthenticated" role="menuitem"> + <a *ngIf="isAdmin()" class="dropdown-item pointer" routerLink="/admin"> + <span class="fas fa-tools"></span> Admin + </a> + <a class="dropdown-item pointer" (click)="openEditProfile.emit()"> + <span class="fas fa-id-card"></span> Edit profile + </a> + </li> + <li *ngIf="authenticationEnabled" class="divider dropdown-divider"></li> + <li *ngIf="authenticationEnabled && !isAuthenticated" role="menuitem"> + <a class="dropdown-item pointer text-success" (click)="login.emit()"> + <span class="fas fa-sign-in-alt fa-fw"></span> Sign In / Register + </a> + </li> + <li *ngIf="isAuthenticated" role="menuitem"> + <a class="dropdown-item pointer text-danger" (click)="logout.emit()"> + <span class="fas fa-sign-out-alt fa-fw"></span> Sign Out + </a> + </li> + </ul> + </span> +</nav> diff --git a/client/src/app/instance/components/instance-navbar.component.scss b/client/src/app/instance/components/instance-navbar.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..1cc4ce8a2b777fb65944b30cbb3363caf453b52f --- /dev/null +++ b/client/src/app/instance/components/instance-navbar.component.scss @@ -0,0 +1,17 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +.dropdown-up { + top: 80% !important; + right: 5px !important; +} + +img { + height: 60px; +} diff --git a/client/src/app/shared/components/navbar.component.ts b/client/src/app/instance/components/instance-navbar.component.ts similarity index 59% rename from client/src/app/shared/components/navbar.component.ts rename to client/src/app/instance/components/instance-navbar.component.ts index 18f9cba7087998a49e403ff6f56ec91ecf13228b..c1b8d338dd2aace85883b425d16d4b9a48c9de88 100644 --- a/client/src/app/shared/components/navbar.component.ts +++ b/client/src/app/instance/components/instance-navbar.component.ts @@ -1,38 +1,22 @@ -/** - * This file is part of Anis Client. - * - * @copyright Laboratoire d'Astrophysique de Marseille / CNRS - * - * For the full copyright and license information, please view the LICENSE - * file that was distributed with this source code. - */ - -import { Component, Input, Output, EventEmitter, ChangeDetectionStrategy } from '@angular/core'; +import { Component, ChangeDetectionStrategy, Input, Output, EventEmitter } from '@angular/core'; import { Instance } from 'src/app/metamodel/models'; import { UserProfile } from 'src/app/auth/user-profile.model'; import { isAdmin } from 'src/app/shared/utils'; -/** - * @class - * @classdesc Navbar component. - */ @Component({ - selector: 'app-navbar', - templateUrl: 'navbar.component.html', - styleUrls: [ 'navbar.component.scss' ], + selector: 'app-instance-navbar', + templateUrl: 'instance-navbar.component.html', + styleUrls: [ 'instance-navbar.component.scss' ], changeDetection: ChangeDetectionStrategy.OnPush }) -export class NavbarComponent { - @Input() links: {label: string, icon: string, routerLink: string}[]; +export class InstanceNavbarComponent { @Input() isAuthenticated: boolean; @Input() userProfile: UserProfile = null; @Input() userRoles: string[]; - @Input() baseHref: string; @Input() authenticationEnabled: boolean; @Input() apiUrl: string; @Input() adminRoles: string[]; - @Input() url: string; @Input() instance: Instance; @Output() login: EventEmitter<any> = new EventEmitter(); @Output() logout: EventEmitter<any> = new EventEmitter(); @@ -47,14 +31,6 @@ export class NavbarComponent { return isAdmin(this.adminRoles, this.userRoles); } - isPortalRoute() { - return this.url.includes('portal'); - } - - isAdminRoute() { - return this.url.includes('admin'); - } - /** * Returns logo href. * diff --git a/client/src/app/instance/instance.component.html b/client/src/app/instance/instance.component.html index e31861eec51d537e3f662d6054c19336ddd0c0da..9de6bd52146f976fa679cf401405aba8f9135a47 100644 --- a/client/src/app/instance/instance.component.html +++ b/client/src/app/instance/instance.component.html @@ -1,20 +1,21 @@ -<header> - <app-navbar - [links]="links" - [isAuthenticated]="isAuthenticated | async" - [userProfile]="userProfile | async" - [userRoles]="userRoles | async" - [baseHref]="getBaseHref()" - [authenticationEnabled]="getAuthenticationEnabled()" - [adminRoles]="getAdminRoles()" - [url]="url | async" - [apiUrl]="getApiUrl()" - [instance]="instance | async" - (login)="login()" - (logout)="logout()" - (openEditProfile)="openEditProfile()"> - </app-navbar> -</header> -<main role="main" class="container-fluid pb-4"> - <router-outlet></router-outlet> -</main> +<app-spinner *ngIf="(webpageFamilyListIsLoading | async) || (webpageListIsLoading | async)"></app-spinner> + +<ng-container *ngIf="(webpageFamilyListIsLoaded | async) && (webpageListIsLoaded | async)"> + <header> + <app-instance-navbar + [isAuthenticated]="isAuthenticated | async" + [userProfile]="userProfile | async" + [userRoles]="userRoles | async" + [authenticationEnabled]="getAuthenticationEnabled()" + [apiUrl]="getApiUrl()" + [adminRoles]="getAdminRoles()" + [instance]="instance | async" + (login)="login()" + (logout)="logout()" + (openEditProfile)="openEditProfile()"> + </app-instance-navbar> + </header> + <main role="main" class="container-fluid pb-4"> + <router-outlet></router-outlet> + </main> +</ng-container> diff --git a/client/src/app/instance/instance.component.ts b/client/src/app/instance/instance.component.ts index 66e2fc4df9788f64d7b4cb3b47671ef939025365..95c015b3b42b1a91db116b6761d6a70aace8681c 100644 --- a/client/src/app/instance/instance.component.ts +++ b/client/src/app/instance/instance.component.ts @@ -12,10 +12,9 @@ import { HttpClient } from '@angular/common/http'; import { Store } from '@ngrx/store'; import { Observable, Subscription } from 'rxjs'; -import * as fromRouter from '@ngrx/router-store'; import { UserProfile } from 'src/app/auth/user-profile.model'; -import { Instance } from 'src/app/metamodel/models'; +import { Instance, WebpageFamily, Webpage } from 'src/app/metamodel/models'; import * as authActions from 'src/app/auth/auth.actions'; import * as authSelector from 'src/app/auth/auth.selector'; import * as datasetActions from 'src/app/metamodel/actions/dataset.actions'; @@ -23,7 +22,9 @@ import * as datasetFamilyActions from 'src/app/metamodel/actions/dataset-family. import * as instanceSelector from 'src/app/metamodel/selectors/instance.selector'; import * as datasetGroupActions from 'src/app/metamodel/actions/dataset-group.actions'; import * as webpageFamilyActions from 'src/app/metamodel/actions/webpage-family.actions'; +import * as webpageFamilySelector from 'src/app/metamodel/selectors/webpage-family.selector'; import * as webpageActions from 'src/app/metamodel/actions/webpage.actions'; +import * as webpageSelector from 'src/app/metamodel/selectors/webpage.selector'; import { AppConfigService } from 'src/app/app-config.service'; /** @@ -41,14 +42,16 @@ export class InstanceComponent implements OnInit, OnDestroy { public favIcon: HTMLLinkElement = document.querySelector('#favicon'); public title: HTMLLinkElement = document.querySelector('#title'); public body: HTMLBodyElement = document.querySelector('body'); - public links = [ - { label: 'Home', icon: 'fas fa-home', routerLink: 'home' } - ]; public instance: Observable<Instance>; public isAuthenticated: Observable<boolean>; public userProfile: Observable<UserProfile>; public userRoles: Observable<string[]>; - public url: Observable<string>; + public webpageFamilyListIsLoading: Observable<boolean>; + public webpageFamilyListIsLoaded: Observable<boolean>; + public webpageFamilyList: Observable<WebpageFamily[]>; + public webpageListIsLoading: Observable<boolean>; + public webpageListIsLoaded: Observable<boolean>; + public webpageList: Observable<Webpage[]>; public instanceSubscription: Subscription; constructor(private store: Store<{ }>, private config: AppConfigService, private http: HttpClient) { @@ -56,7 +59,12 @@ export class InstanceComponent implements OnInit, OnDestroy { this.isAuthenticated = store.select(authSelector.selectIsAuthenticated); this.userProfile = store.select(authSelector.selectUserProfile); this.userRoles = store.select(authSelector.selectUserRoles); - this.url = store.select(fromRouter.getSelectors().selectUrl); + this.webpageFamilyListIsLoading = store.select(webpageFamilySelector.selectWebpageFamilyListIsLoading); + this.webpageFamilyListIsLoaded = store.select(webpageFamilySelector.selectWebpageFamilyListIsLoaded); + this.webpageFamilyList = store.select(webpageFamilySelector.selectAllWebpageFamilies); + this.webpageListIsLoading = store.select(webpageSelector.selectWebpageListIsLoading); + this.webpageListIsLoaded = store.select(webpageSelector.selectWebpageListIsLoaded); + this.webpageList = store.select(webpageSelector.selectAllWebpages); } ngOnInit() { @@ -69,15 +77,6 @@ export class InstanceComponent implements OnInit, OnDestroy { Promise.resolve(null).then(() => this.store.dispatch(webpageActions.loadWebpageList())); this.instanceSubscription = this.instance.subscribe(instance => { if (instance) { - if (instance.search_by_criteria_allowed) { - this.links.push({ label: instance.search_by_criteria_label, icon: 'fas fa-search', routerLink: 'search' }); - } - if (instance.search_multiple_allowed) { - this.links.push({ label: instance.search_multiple_label, icon: 'fas fa-search-plus', routerLink: 'search-multiple' }); - } - if (instance.documentation_allowed) { - this.links.push({ label: instance.documentation_label, icon: 'fas fa-question', routerLink: 'documentation' }); - } if (instance.design_favicon !== '') { this.setFaviconHref(instance); } @@ -103,15 +102,6 @@ export class InstanceComponent implements OnInit, OnDestroy { } } - /** - * Returns application base href. - * - * @return string - */ - getBaseHref(): string { - return this.config.baseHref; - } - /** * Checks if authentication is enabled. * diff --git a/client/src/app/instance/instance.module.ts b/client/src/app/instance/instance.module.ts index 33a6514300c63f72977df896246e0cf0bc1135a9..a0f28693cc50fe455be948c517f5dae9883f114f 100644 --- a/client/src/app/instance/instance.module.ts +++ b/client/src/app/instance/instance.module.ts @@ -14,6 +14,7 @@ import { EffectsModule } from '@ngrx/effects'; import { SharedModule } from 'src/app/shared/shared.module'; import { InstanceRoutingModule, routedComponents } from './instance-routing.module'; +import { dummiesComponents } from './components'; import { instanceReducer } from './instance.reducer'; import { instanceEffects } from './store/effects'; import { instanceServices } from './store/services'; @@ -30,7 +31,8 @@ import { instanceServices } from './store/services'; EffectsModule.forFeature(instanceEffects) ], declarations: [ - routedComponents + routedComponents, + dummiesComponents ], providers: [ instanceServices diff --git a/client/src/app/portal/components/index.ts b/client/src/app/portal/components/index.ts index 20812c31b8e9f4c4c6456920902086940296a386..685b2995ee7cafc764e12b01b5f2d5becd7bd4fd 100644 --- a/client/src/app/portal/components/index.ts +++ b/client/src/app/portal/components/index.ts @@ -8,7 +8,9 @@ */ import { InstanceCardComponent } from './instance-card.component'; +import { PortalNavbarComponent } from './portal-navbar.component'; export const dummiesComponents = [ - InstanceCardComponent + InstanceCardComponent, + PortalNavbarComponent ]; diff --git a/client/src/app/portal/components/portal-navbar.component.html b/client/src/app/portal/components/portal-navbar.component.html new file mode 100644 index 0000000000000000000000000000000000000000..b253077a43932bf1eeb8215b9b3f9b0f89bf436b --- /dev/null +++ b/client/src/app/portal/components/portal-navbar.component.html @@ -0,0 +1,93 @@ +<nav class="navbar navbar-light bg-light navbar-expand-md fixed-top border-bottom"> + <!-- Logo --> + <a routerLink="/" class="navbar-brand"> + <img src="assets/cesam_anis40.png" alt="ANIS logo" /> + </a> + + <!-- Navigation --> + <div class="collapse navbar-collapse" id="navbarCollapse"> + <ul class="navbar-nav mr-auto"></ul> + <ul class="navbar-nav justify-content-end"> + <li *ngIf="!authenticationEnabled" class="nav-item pr-3"> + <a class="nav-link" routerLink="/admin" routerLinkActive="active"> + <span class="fas fa-tools"></span> Admin + </a> + </li> + </ul> + <!-- sign in / sign out --> + <button *ngIf="authenticationEnabled && !isAuthenticated" + class="btn btn-outline-success my-2 my-sm-0" + id="button-sign-in" + (click)="login.emit()"> + Sign In / Register + </button> + <span *ngIf="isAuthenticated" id="dropdown-menu" dropdown> + <button id="button-basic" dropdownToggle type="button" class="btn btn-light" aria-controls="dropdown-basic"> + <span class="fa-stack" [ngStyle]="{ color: '#7AC29A' }"> + <span class="fas fa-circle fa-2x"></span> + <span class="fas fa-user fa-stack-1x fa-inverse"></span> + </span> + + <span class="fas fa-chevron-down text-secondary"></span> + </button> + <ul id="basic-link-dropdown" *dropdownMenu class="dropdown-menu dropdown-menu-right dropdown-up" role="menu" aria-labelledby="basic-link"> + <li id="li-email" role="menuitem"> + <span class="dropdown-item font-italic">{{ userProfile.email }}</span> + </li> + <li class="divider dropdown-divider"></li> + <li role="menuitem"> + <a *ngIf="isAdmin()" class="dropdown-item pointer" routerLink="/admin"> + <span class="fas fa-tools"></span> Admin + </a> + <a class="dropdown-item pointer" (click)="openEditProfile.emit()"> + <span class="fas fa-id-card"></span> Edit profile + </a> + </li> + <li class="divider dropdown-divider"></li> + <li role="menuitem"> + <a class="dropdown-item text-danger pointer" (click)="logout.emit()"> + <span class="fas fa-sign-out-alt fa-fw"></span> Sign Out + </a> + </li> + </ul> + </span> + </div> + + <!-- Navigation Mobile --> + <span dropdown> + <button id="button-basic" dropdownToggle type="button" class="navbar-toggler" aria-controls="dropdown-basic"> + <span class="fas fa-bars"></span> + </button> + <ul id="basic-link-dropdown" *dropdownMenu class="dropdown-menu dropdown-menu-right dropdown-up" role="menu" + aria-labelledby="basic-link"> + <li *ngIf="isAuthenticated" role="menuitem"> + <span class="dropdown-item font-italic">{{ userProfile.email }}</span> + </li> + <li *ngIf="isAuthenticated" class="divider dropdown-divider"></li> + <li *ngIf="!authenticationEnabled" role="menuitem"> + <a class="dropdown-item pointer" routerLink="/admin"> + <span class="fas fa-tools"></span> Admin + </a> + </li> + <li *ngIf="isAuthenticated" role="menuitem"> + <a *ngIf="isAdmin()" class="dropdown-item pointer" routerLink="/admin"> + <span class="fas fa-tools"></span> Admin + </a> + <a class="dropdown-item pointer" (click)="openEditProfile.emit()"> + <span class="fas fa-id-card"></span> Edit profile + </a> + </li> + <li *ngIf="isAuthenticated" class="divider dropdown-divider"></li> + <li *ngIf="authenticationEnabled && !isAuthenticated" role="menuitem"> + <a class="dropdown-item pointer text-success" (click)="login.emit()"> + <span class="fas fa-sign-in-alt fa-fw"></span> Sign In / Register + </a> + </li> + <li *ngIf="isAuthenticated" role="menuitem"> + <a class="dropdown-item pointer text-danger" (click)="logout.emit()"> + <span class="fas fa-sign-out-alt fa-fw"></span> Sign Out + </a> + </li> + </ul> + </span> +</nav> diff --git a/client/src/app/portal/components/portal-navbar.component.scss b/client/src/app/portal/components/portal-navbar.component.scss new file mode 100644 index 0000000000000000000000000000000000000000..1cc4ce8a2b777fb65944b30cbb3363caf453b52f --- /dev/null +++ b/client/src/app/portal/components/portal-navbar.component.scss @@ -0,0 +1,17 @@ +/** + * This file is part of Anis Client. + * + * @copyright Laboratoire d'Astrophysique de Marseille / CNRS + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +.dropdown-up { + top: 80% !important; + right: 5px !important; +} + +img { + height: 60px; +} diff --git a/client/src/app/portal/components/portal-navbar.component.ts b/client/src/app/portal/components/portal-navbar.component.ts new file mode 100644 index 0000000000000000000000000000000000000000..b8d0ab4b1719b5cf797d1c9bd2df523637883815 --- /dev/null +++ b/client/src/app/portal/components/portal-navbar.component.ts @@ -0,0 +1,30 @@ +import { Component, ChangeDetectionStrategy, Input, Output, EventEmitter } from '@angular/core'; + +import { UserProfile } from 'src/app/auth/user-profile.model'; +import { isAdmin } from 'src/app/shared/utils'; + +@Component({ + selector: 'app-portal-navbar', + templateUrl: 'portal-navbar.component.html', + styleUrls: [ 'portal-navbar.component.scss' ], + changeDetection: ChangeDetectionStrategy.OnPush +}) +export class PortalNavbarComponent { + @Input() isAuthenticated: boolean; + @Input() userProfile: UserProfile = null; + @Input() userRoles: string[]; + @Input() authenticationEnabled: boolean; + @Input() adminRoles: string[]; + @Output() login: EventEmitter<any> = new EventEmitter(); + @Output() logout: EventEmitter<any> = new EventEmitter(); + @Output() openEditProfile: EventEmitter<any> = new EventEmitter(); + + /** + * Returns true if user is admin + * + * @returns boolean + */ + isAdmin() { + return isAdmin(this.adminRoles, this.userRoles); + } +} diff --git a/client/src/app/portal/containers/portal-home.component.html b/client/src/app/portal/containers/portal-home.component.html index 29c5b00fe27cdae382d870f9afc4b0861280b62d..ba726a547cb4689f290c2be833c770c860bb8301 100644 --- a/client/src/app/portal/containers/portal-home.component.html +++ b/client/src/app/portal/containers/portal-home.component.html @@ -1,17 +1,14 @@ <header> - <app-navbar - [links]="links" + <app-portal-navbar [isAuthenticated]="isAuthenticated | async" [userProfile]="userProfile | async" [userRoles]="userRoles | async" - [baseHref]="getBaseHref()" [authenticationEnabled]="getAuthenticationEnabled()" [adminRoles]="getAdminRoles()" - [url]="url | async" (login)="login()" (logout)="logout()" (openEditProfile)="openEditProfile()"> - </app-navbar> + </app-portal-navbar> </header> <main role="main" class="container-fluid pb-4"> <div class="container"> diff --git a/client/src/app/portal/containers/portal-home.component.ts b/client/src/app/portal/containers/portal-home.component.ts index ceb30652b6bd4251dfe7971389d5f02bb1ca515c..56ea525430382c9f1b143d912dcc3951e5518334 100644 --- a/client/src/app/portal/containers/portal-home.component.ts +++ b/client/src/app/portal/containers/portal-home.component.ts @@ -11,7 +11,6 @@ import { Component, OnInit } from '@angular/core'; import { Observable, Subscription } from 'rxjs'; import { Store } from '@ngrx/store'; -import * as fromRouter from '@ngrx/router-store'; import { UserProfile } from 'src/app/auth/user-profile.model'; import { Instance, InstanceGroup } from 'src/app/metamodel/models'; @@ -36,13 +35,11 @@ export class PortalHomeComponent implements OnInit { public favIcon: HTMLLinkElement = document.querySelector('#favicon'); public title: HTMLLinkElement = document.querySelector('#title'); public body: HTMLBodyElement = document.querySelector('body'); - public links = []; public isAuthenticated: Observable<boolean>; public userProfile: Observable<UserProfile>; public userRoles: Observable<string[]>; public instanceList: Observable<Instance[]>; public instanceGroupList: Observable<InstanceGroup[]>; - public url: Observable<string>; public userRolesSubscription: Subscription; constructor(private store: Store<{ }>, private config: AppConfigService) { @@ -51,7 +48,6 @@ export class PortalHomeComponent implements OnInit { this.userRoles = store.select(authSelector.selectUserRoles); this.instanceList = store.select(instanceSelector.selectAllInstances); this.instanceGroupList = store.select(instanceGroupSelector.selectAllInstanceGroups); - this.url = store.select(fromRouter.getSelectors().selectUrl); } ngOnInit() { @@ -60,15 +56,6 @@ export class PortalHomeComponent implements OnInit { this.body.style.backgroundColor = 'white'; } - /** - * Returns application base href. - * - * @return string - */ - getBaseHref(): string { - return this.config.baseHref; - } - /** * Checks if authentication is enabled. * diff --git a/client/src/app/shared/components/index.ts b/client/src/app/shared/components/index.ts index 87259b3c83248b10b2b7058ac79b71127a11b7ca..18a3063846b295a531f6311a1637ce0c83265947 100644 --- a/client/src/app/shared/components/index.ts +++ b/client/src/app/shared/components/index.ts @@ -8,9 +8,7 @@ */ import { SpinnerComponent } from "./spinner.component"; -import { NavbarComponent } from './navbar.component'; export const sharedComponents = [ - SpinnerComponent, - NavbarComponent + SpinnerComponent ]; diff --git a/client/src/app/shared/components/navbar.component.spec.ts b/client/src/app/shared/components/navbar.component.spec.ts deleted file mode 100644 index c7c9debb79f73e155085b3d568c820ef6951cdc9..0000000000000000000000000000000000000000 --- a/client/src/app/shared/components/navbar.component.spec.ts +++ /dev/null @@ -1,32 +0,0 @@ -import { TestBed, ComponentFixture } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; - -import { NavbarComponent } from './navbar.component'; -import { INSTANCE } from '../../../test-data'; - -describe('[Shared][Component] NavbarComponent', () => { - let component: NavbarComponent; - let fixture: ComponentFixture<NavbarComponent>; - - beforeEach(() => { - TestBed.configureTestingModule({ - declarations: [NavbarComponent], - imports: [RouterTestingModule] - - }).compileComponents(); - fixture = TestBed.createComponent(NavbarComponent); - component = fixture.componentInstance; - }); - - it('should create the component', () => { - expect(component).toBeDefined(); - }); - - it('should return logo href', () => { - component.apiUrl = 'http://test.com'; - component.instance = INSTANCE; - expect(component.getLogoHref()).toEqual('http://test.com/instance/myInstance/file-explorer/path/to/logo'); - component.instance.design_logo = ''; - expect(component.getLogoHref()).toEqual('assets/cesam_anis40.png'); - }); -}); diff --git a/docker-compose.yml b/docker-compose.yml index 3172ebe30d803d962f581ac165d6825ebd15c5cd..77088f75351fe2a0ec792528e547995a16365b89 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -35,7 +35,7 @@ services: SSO_AUTH_URL: "http://localhost:8180/auth" SSO_REALM: "anis" SSO_CLIENT_ID: "anis-client" - TOKEN_ENABLED: 0 + TOKEN_ENABLED: 1 TOKEN_JWKS_URL: "http://keycloak:8180/auth/realms/anis/protocol/openid-connect/certs" TOKEN_ADMIN_ROLES: anis_admin,superuser RMQ_HOST: rmq