Initial Project Commit

This commit is contained in:
Orlando M Guerreiro 2025-05-22 19:23:40 +01:00
commit a6dea9c888
2148 changed files with 173870 additions and 0 deletions

View file

@ -0,0 +1,23 @@
<div class="d-flex justify-content-center">
<div class="col-8">
@if (activityDomain) {
<div>
<h2>
@if (_titleI18N) {<span jhiTranslate="{{ _titleI18N }}"></span>}
@else if (_title) {<span>{{ _title }}</span>}
@else {<span>__ no title __</span>}
</h2>
<hr />
<jhi-alert-error></jhi-alert-error>
<jhi-alert></jhi-alert>
<jhi-activity-progress [activityDomain]="activityDomain"></jhi-activity-progress>
<ng-content></ng-content>
</div>
}
</div>
</div>

View file

@ -0,0 +1,44 @@
// base-layout.component.ts
import { Component, Input } from '@angular/core';
import { IActivityDomain } from 'app/entities/activity/activity.model';
import { ResilientActivityProgressComponent } from 'app/resilient/resilient-activity/progress/resilient-activity-progress.component';
import SharedModule from 'app/shared/shared.module';
@Component({
standalone: true, // Mark as standalone
selector: 'base-detail-layout',
templateUrl: './base-detail.component.html',
styles: [`
.content { padding: 1rem; }
`],
imports: [
SharedModule,
ResilientActivityProgressComponent
]
})
export class BaseDetailLayoutComponent {
// Use [activityDomain]="entity()" to enable template domain values
@Input() activityDomain!: IActivityDomain | null; // The "!" makes this NOT optional. An exception will happen at runtime if not provided
/*
_activityDomain: IActivityDomain | null = null;
@Input('activityDomain') set activityDomain(value: IActivityDomain | null) {
this._activityDomain = value;
}
*/
// Use [title.text]="'My Title'" to provide the text of the title
_title: string | undefined;
@Input('title.text') set titleText(value: string) {
this._title = value;
console.log('title.text set to:', value);
}
// Use [title.translate]="app.entity.title" to provide the key for translation
_titleI18N: string | undefined;
@Input('title.translate') set titleTranslate(value: string) {
this._titleI18N = value;
console.log('title.translate set to:', value);
}
}

View file

@ -0,0 +1,15 @@
<div>
<div class="row">
<div class="col-md-4">
<span class="hipster img-fluid rounded"></span>
</div>
<div class="col-md-8">
<h1 jhiTranslate="error.title">Página de erro!</h1>
@if (errorMessage()) {
<div class="alert alert-danger">{{ errorMessage() }}</div>
}
</div>
</div>
</div>

View file

@ -0,0 +1,45 @@
import { Component, inject, signal, OnInit, OnDestroy } from '@angular/core';
import { ActivatedRoute } from '@angular/router';
import { Subscription } from 'rxjs';
import { TranslateService } from '@ngx-translate/core';
import SharedModule from 'app/shared/shared.module';
@Component({
standalone: true,
selector: 'jhi-error',
templateUrl: './error.component.html',
imports: [SharedModule],
})
export default class ErrorComponent implements OnInit, OnDestroy {
errorMessage = signal<string | undefined>(undefined);
errorKey?: string;
langChangeSubscription?: Subscription;
private translateService = inject(TranslateService);
private route = inject(ActivatedRoute);
ngOnInit(): void {
this.route.data.subscribe(routeData => {
if (routeData.errorMessage) {
this.errorKey = routeData.errorMessage;
this.getErrorMessageTranslation();
this.langChangeSubscription = this.translateService.onLangChange.subscribe(() => this.getErrorMessageTranslation());
}
});
}
ngOnDestroy(): void {
if (this.langChangeSubscription) {
this.langChangeSubscription.unsubscribe();
}
}
private getErrorMessageTranslation(): void {
this.errorMessage.set('');
if (this.errorKey) {
this.translateService.get(this.errorKey).subscribe(translatedErrorMessage => {
this.errorMessage.set(translatedErrorMessage);
});
}
}
}

View file

@ -0,0 +1,31 @@
import { Routes } from '@angular/router';
import ErrorComponent from './error.component';
export const errorRoute: Routes = [
{
path: 'error',
component: ErrorComponent,
title: 'error.title',
},
{
path: 'accessdenied',
component: ErrorComponent,
data: {
errorMessage: 'error.http.403',
},
title: 'error.title',
},
{
path: '404',
component: ErrorComponent,
data: {
errorMessage: 'error.http.404',
},
title: 'error.title',
},
{
path: '**',
redirectTo: '/404',
},
];

View file

@ -0,0 +1,6 @@
<div class="footer">
<p>
<span jhiTranslate="copyright">InNOVA-Sistema de Informação em Ambiente e Sustentabilidade da NOVA &#64;2025</span><br>
<span jhiTranslate="financed">Co-financiado por Fundo Ambiental</span>
</p>
</div>

View file

@ -0,0 +1,10 @@
import { Component } from '@angular/core';
import { TranslateDirective } from 'app/shared/language';
@Component({
standalone: true,
selector: 'jhi-footer',
templateUrl: './footer.component.html',
imports: [TranslateDirective],
})
export default class FooterComponent {}

View file

@ -0,0 +1,6 @@
<!-- TOP BANNER -->
<section>
<div class="container-fluid mb-5 p-0">
<img id="home-header" src="content/images/home_banner.jpg" class="img-fluid" alt="Route Zero">
</div>
</section>

View file

@ -0,0 +1,8 @@
import { Component } from '@angular/core';
@Component({
standalone: true,
selector: 'jhi-banner',
templateUrl: './banner.component.html',
})
export class BannerComponent {}

View file

@ -0,0 +1,12 @@
.main-component-banner {
display: none;
}
/*
::ng-deep is needed because .authenticated doesn't belong to main.component, its in the <body> tag
ViewEncapsulation prevented this to work. By using ::ng-deep, Angular is forced to evaluate this OUTSIDE
ViewEncapsulation
*/
::ng-deep .authenticated .main-component-banner {
display: block;
}

View file

@ -0,0 +1,15 @@
<jhi-page-ribbon></jhi-page-ribbon>
<div>
<router-outlet name="navbar"></router-outlet>
</div>
<jhi-banner class="main-component-banner" *ngIf="showBanner"></jhi-banner>
<div class="container-fluid">
<div class="card jh-card">
<router-outlet></router-outlet>
</div>
<jhi-footer></jhi-footer>
</div>

View file

@ -0,0 +1,230 @@
jest.mock('app/core/auth/account.service');
import { waitForAsync, ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing';
import { Router, TitleStrategy } from '@angular/router';
import { Title } from '@angular/platform-browser';
import { DOCUMENT } from '@angular/common';
import { Component, NgZone } from '@angular/core';
import { of } from 'rxjs';
import { TranslateModule, TranslateService, LangChangeEvent } from '@ngx-translate/core';
import { AccountService } from 'app/core/auth/account.service';
import { AppPageTitleStrategy } from 'app/app-page-title-strategy';
import MainComponent from './main.component';
describe('MainComponent', () => {
let comp: MainComponent;
let fixture: ComponentFixture<MainComponent>;
let titleService: Title;
let translateService: TranslateService;
let mockAccountService: AccountService;
let ngZone: NgZone;
const routerState: any = { snapshot: { root: { data: {} } } };
let router: Router;
let document: Document;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [TranslateModule.forRoot(), MainComponent],
providers: [Title, AccountService, { provide: TitleStrategy, useClass: AppPageTitleStrategy }],
})
.overrideTemplate(MainComponent, '')
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MainComponent);
comp = fixture.componentInstance;
titleService = TestBed.inject(Title);
translateService = TestBed.inject(TranslateService);
mockAccountService = TestBed.inject(AccountService);
mockAccountService.identity = jest.fn(() => of(null));
mockAccountService.getAuthenticationState = jest.fn(() => of(null));
ngZone = TestBed.inject(NgZone);
router = TestBed.inject(Router);
document = TestBed.inject(DOCUMENT);
});
describe('page title', () => {
const defaultPageTitle = 'global.title';
const parentRoutePageTitle = 'parentTitle';
const childRoutePageTitle = 'childTitle';
const langChangeEvent: LangChangeEvent = { lang: 'pt-pt', translations: null };
beforeEach(() => {
routerState.snapshot.root = { data: {} };
jest.spyOn(translateService, 'get').mockImplementation((key: string | string[]) => of(`${key as string} translated`));
translateService.currentLang = 'pt-pt';
jest.spyOn(titleService, 'setTitle');
comp.ngOnInit();
});
describe('navigation end', () => {
it('should set page title to default title if pageTitle is missing on routes', fakeAsync(() => {
// WHEN
ngZone.run(() => router.navigateByUrl(''));
tick();
// THEN
expect(document.title).toBe(defaultPageTitle + ' translated');
}));
it('should set page title to root route pageTitle if there is no child routes', fakeAsync(() => {
// GIVEN
router.resetConfig([{ path: '', title: parentRoutePageTitle, component: BlankComponent }]);
// WHEN
ngZone.run(() => router.navigateByUrl(''));
tick();
// THEN
expect(document.title).toBe(parentRoutePageTitle + ' translated');
}));
it('should set page title to child route pageTitle if child routes exist and pageTitle is set for child route', fakeAsync(() => {
// GIVEN
router.resetConfig([
{
path: 'home',
title: parentRoutePageTitle,
children: [{ path: '', title: childRoutePageTitle, component: BlankComponent }],
},
]);
// WHEN
ngZone.run(() => router.navigateByUrl('home'));
tick();
// THEN
expect(document.title).toBe(childRoutePageTitle + ' translated');
}));
it('should set page title to parent route pageTitle if child routes exists but pageTitle is not set for child route data', fakeAsync(() => {
// GIVEN
router.resetConfig([
{
path: 'home',
title: parentRoutePageTitle,
children: [{ path: '', component: BlankComponent }],
},
]);
// WHEN
ngZone.run(() => router.navigateByUrl('home'));
tick();
// THEN
expect(document.title).toBe(parentRoutePageTitle + ' translated');
}));
});
describe('language change', () => {
it('should set page title to default title if pageTitle is missing on routes', () => {
// WHEN
translateService.onLangChange.emit(langChangeEvent);
// THEN
expect(document.title).toBe(defaultPageTitle + ' translated');
});
it('should set page title to root route pageTitle if there is no child routes', fakeAsync(() => {
// GIVEN
routerState.snapshot.root.data = { pageTitle: parentRoutePageTitle };
router.resetConfig([{ path: '', title: parentRoutePageTitle, component: BlankComponent }]);
// WHEN
ngZone.run(() => router.navigateByUrl(''));
tick();
// THEN
expect(document.title).toBe(parentRoutePageTitle + ' translated');
// GIVEN
document.title = 'other title';
// WHEN
translateService.onLangChange.emit(langChangeEvent);
// THEN
expect(document.title).toBe(parentRoutePageTitle + ' translated');
}));
it('should set page title to child route pageTitle if child routes exist and pageTitle is set for child route', fakeAsync(() => {
// GIVEN
router.resetConfig([
{
path: 'home',
title: parentRoutePageTitle,
children: [{ path: '', title: childRoutePageTitle, component: BlankComponent }],
},
]);
// WHEN
ngZone.run(() => router.navigateByUrl('home'));
tick();
// THEN
expect(document.title).toBe(childRoutePageTitle + ' translated');
// GIVEN
document.title = 'other title';
// WHEN
translateService.onLangChange.emit(langChangeEvent);
// THEN
expect(document.title).toBe(childRoutePageTitle + ' translated');
}));
it('should set page title to parent route pageTitle if child routes exists but pageTitle is not set for child route data', fakeAsync(() => {
// GIVEN
router.resetConfig([
{
path: 'home',
title: parentRoutePageTitle,
children: [{ path: '', component: BlankComponent }],
},
]);
// WHEN
ngZone.run(() => router.navigateByUrl('home'));
tick();
// THEN
expect(document.title).toBe(parentRoutePageTitle + ' translated');
// GIVEN
document.title = 'other title';
// WHEN
translateService.onLangChange.emit(langChangeEvent);
// THEN
expect(document.title).toBe(parentRoutePageTitle + ' translated');
}));
});
});
describe('page language attribute', () => {
it('should change page language attribute on language change', () => {
// GIVEN
comp.ngOnInit();
// WHEN
translateService.onLangChange.emit({ lang: 'lang1', translations: null });
// THEN
expect(document.querySelector('html')?.getAttribute('lang')).toEqual('lang1');
// WHEN
translateService.onLangChange.emit({ lang: 'lang2', translations: null });
// THEN
expect(document.querySelector('html')?.getAttribute('lang')).toEqual('lang2');
});
});
});
@Component({ template: '' })
export class BlankComponent {}

View file

@ -0,0 +1,61 @@
import { Component, inject, OnInit, RendererFactory2, Renderer2 } from '@angular/core';
import { RouterOutlet, Router, NavigationEnd } from '@angular/router';
import { TranslateService, LangChangeEvent } from '@ngx-translate/core';
import dayjs from 'dayjs/esm';
import { filter } from 'rxjs/operators';
import { CommonModule } from '@angular/common'; // Add this!
import { AccountService } from 'app/core/auth/account.service';
import { AppPageTitleStrategy } from 'app/app-page-title-strategy';
import FooterComponent from '../footer/footer.component';
import PageRibbonComponent from '../profiles/page-ribbon.component';
import { BannerComponent } from './banner/banner.component';
@Component({
standalone: true,
selector: 'jhi-main',
templateUrl: './main.component.html',
styleUrl: './main.component.css',
providers: [AppPageTitleStrategy],
imports: [RouterOutlet, FooterComponent, PageRibbonComponent, CommonModule, BannerComponent],
})
export default class MainComponent implements OnInit {
private renderer: Renderer2;
private router = inject(Router);
private appPageTitleStrategy = inject(AppPageTitleStrategy);
private accountService = inject(AccountService);
private translateService = inject(TranslateService);
private rootRenderer = inject(RendererFactory2);
showBanner = false;
constructor() {
this.renderer = this.rootRenderer.createRenderer(document.querySelector('html'), null);
}
ngOnInit(): void {
// try to log in automatically
this.accountService.identity().subscribe();
this.translateService.onLangChange.subscribe((langChangeEvent: LangChangeEvent) => {
this.appPageTitleStrategy.updateTitle(this.router.routerState.snapshot);
dayjs.locale(langChangeEvent.lang);
this.renderer.setAttribute(document.querySelector('html'), 'lang', langChangeEvent.lang);
});
this.router.events
.pipe(filter(event => event instanceof NavigationEnd))
.subscribe((event) => {
const navEndEvent = event as NavigationEnd; // Explicitly tell TypeScript
const isLoggedIn = this.accountService.isAuthenticated();
// List of routes where the banner should be shown
const bannerRoutes = ['/'];
// Check if current route matches one of the bannerRoutes
// For this to work, CSS was needed. See main.component.css
this.showBanner = bannerRoutes.some(route => this.router.url === route);
});
}
}

View file

@ -0,0 +1,30 @@
import { Directive, OnInit, ElementRef, Renderer2, inject, input } from '@angular/core';
import { TranslateService, LangChangeEvent } from '@ngx-translate/core';
@Directive({
standalone: true,
selector: '[jhiActiveMenu]',
})
export default class ActiveMenuDirective implements OnInit {
jhiActiveMenu = input();
private el = inject(ElementRef);
private renderer = inject(Renderer2);
private translateService = inject(TranslateService);
ngOnInit(): void {
this.translateService.onLangChange.subscribe((event: LangChangeEvent) => {
this.updateActiveFlag(event.lang);
});
this.updateActiveFlag(this.translateService.currentLang);
}
updateActiveFlag(selectedLanguage: string): void {
if (this.jhiActiveMenu() === selectedLanguage) {
this.renderer.addClass(this.el.nativeElement, 'active');
} else {
this.renderer.removeClass(this.el.nativeElement, 'active');
}
}
}

View file

@ -0,0 +1,7 @@
type NavbarItem = {
name: string;
route: string;
translationKey: string;
};
export default NavbarItem;

View file

@ -0,0 +1,622 @@
<nav data-cy="navbar" class="navbar navbar-dark navbar-expand-md bg-dark">
<div class="container-fluid">
<a class="navbar-brand logo" routerLink="/" (click)="collapseNavbar()">
<span class="logo-img"></span>
<!-- span class="navbar-title" jhiTranslate="global.title">Resilient</span -->
<!-- span class="navbar-version">{{ version }}</span -->
</a>
<a
class="navbar-toggler d-lg-none"
href="javascript:void(0);"
data-toggle="collapse"
data-target="#navbarResponsive"
aria-controls="navbarResponsive"
aria-expanded="false"
aria-label="Toggle navigation"
(click)="toggleNavbar()"
>
<fa-icon icon="bars"></fa-icon>
</a>
<div class="navbar-collapse collapse" id="navbarResponsive" [ngbCollapse]="isNavbarCollapsed()">
<ul class="navbar-nav ms-auto">
@if (account() !== null) {
<li class="nav-item" routerLinkActive="active" [routerLinkActiveOptions]="{ exact: true }">
<a class="nav-link" routerLink="/" (click)="collapseNavbar()">
<span>
<fa-icon icon="home"></fa-icon>
<span jhiTranslate="global.menu.home">Início</span>
</span>
</a>
</li>
}
<!-- jhipster-needle-add-element-to-menu - JHipster will add new menu items here -->
@if (account() !== null) {
<li
*ngIf="hasRole('ROLE_ADMIN')
||
(
(hasRole('ROLE_MANAGER') || hasRole('ROLE_COORDINATOR') || hasRole('ROLE_USER'))
&&
hasReadPermission(Resources.OUTPUT_DATA)
)"
class="nav-item"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }"
>
<a
class="nav-link"
routerLink="/dashboard/EMISSIONS/1500"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }"
(click)="collapseNavbar()"
>
<fa-icon icon="asterisk" [fixedWidth]="true"></fa-icon>
<span jhiTranslate="global.menu.dashboard.emissions">Emissões</span>
</a>
</li>
<li
*ngIf="hasRole('ROLE_ADMIN')
||
(
(hasRole('ROLE_MANAGER') || hasRole('ROLE_COORDINATOR') || hasRole('ROLE_USER'))
&&
hasReadPermission(Resources.OUTPUT_DATA)
)"
class="nav-item"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }"
>
<a
class="nav-link"
routerLink="/dashboard/INDICATORS/1500"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }"
(click)="collapseNavbar()"
>
<fa-icon icon="asterisk" [fixedWidth]="true"></fa-icon>
<span jhiTranslate="global.menu.dashboard.indicators">Indicadores</span>
</a>
</li>
<li
*ngIf="hasRole('ROLE_ADMIN')
||
(
( hasRole('ROLE_MANAGER') || hasRole('ROLE_COORDINATOR') || hasRole('ROLE_USER'))
&&
hasReadPermission(Resources.INPUT_DATA)
)"
class="nav-item"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }"
>
<a
class="nav-link"
routerLink="/inventory"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }"
(click)="collapseNavbar()"
>
<fa-icon icon="asterisk" [fixedWidth]="true"></fa-icon>
<span jhiTranslate="global.menu.inventory.main">Dados de Atividade</span>
</a>
</li>
<li
*ngIf="hasRole('ROLE_ADMIN')
||
(
(hasRole('ROLE_MANAGER') || hasRole('ROLE_COORDINATOR') || hasRole('ROLE_USER'))
&&
hasReadPermission(Resources.EMISSION_FACTOR)
)"
class="nav-item"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }"
>
<a
class="nav-link"
routerLink="/emission-factor/list"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }"
(click)="collapseNavbar()"
>
<fa-icon icon="asterisk" [fixedWidth]="true"></fa-icon>
<span jhiTranslate="global.menu.emissionFactor.main">Fatores Emissão</span>
</a>
</li>
<li
*ngIf="hasRole('ROLE_ADMIN') || hasRole('ROLE_MANAGER') || hasRole('ROLE_COORDINATOR')"
ngbDropdown
class="nav-item dropdown pointer"
display="dynamic"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }"
>
<a class="nav-link dropdown-toggle" ngbDropdownToggle href="javascript:void(0);" id="entity-menu" data-cy="entity">
<span>
<fa-icon icon="th-list"></fa-icon>
<span jhiTranslate="global.menu.entities.main">Entidades</span>
</span>
</a>
<ul class="dropdown-menu" ngbDropdownMenu aria-labelledby="entity-menu">
<li *ngIf="hasRole('ROLE_ADMIN') || ( hasRole('ROLE_MANAGER') && hasReadPermission(Resources.ORGANIZATION) )">
<a
class="dropdown-item"
routerLink="/organization"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }"
(click)="collapseNavbar()"
>
<fa-icon icon="asterisk" [fixedWidth]="true"></fa-icon>
<span jhiTranslate="global.menu.entities.organization">Organization</span>
</a>
</li>
<li *ngIf="hasRole('ROLE_ADMIN') || ( hasRole('ROLE_MANAGER') && hasReadPermission(Resources.PERIOD) )">
<a
class="dropdown-item"
routerLink="/period"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }"
(click)="collapseNavbar()"
>
<fa-icon icon="asterisk" [fixedWidth]="true"></fa-icon>
<span jhiTranslate="global.menu.entities.period">Period</span>
</a>
</li>
<li><div class="dropdown-divider"></div></li>
<li *ngIf="hasRole('ROLE_ADMIN') || (hasRole('ROLE_MANAGER') && hasReadPermission(Resources.UNIT_TYPE) )">
<a
class="dropdown-item"
routerLink="/unit-type"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }"
(click)="collapseNavbar()"
>
<fa-icon icon="asterisk" [fixedWidth]="true"></fa-icon>
<span jhiTranslate="global.menu.entities.unitType">Unit Type</span>
</a>
</li>
<li *ngIf="hasRole('ROLE_ADMIN') || (hasRole('ROLE_MANAGER') && hasReadPermission(Resources.UNIT) )">
<a
class="dropdown-item"
routerLink="/unit"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }"
(click)="collapseNavbar()"
>
<fa-icon icon="asterisk" [fixedWidth]="true"></fa-icon>
<span jhiTranslate="global.menu.entities.unit">Unit</span>
</a>
</li>
<li *ngIf="hasRole('ROLE_ADMIN') || (hasRole('ROLE_MANAGER') && hasReadPermission(Resources.UNIT_CONVERTER) )">
<a
class="dropdown-item"
routerLink="/unit-converter"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }"
(click)="collapseNavbar()"
>
<fa-icon icon="asterisk" [fixedWidth]="true"></fa-icon>
<span jhiTranslate="global.menu.entities.unitConverter">Unit Converter</span>
</a>
</li>
<li *ngIf="hasRole('ROLE_ADMIN') || hasRole('ROLE_MANAGER')"><div class="dropdown-divider"></div></li>
<li *ngIf="hasRole('ROLE_ADMIN') || (hasRole('ROLE_MANAGER') && hasReadPermission(Resources.VARIABLE_SCOPE) )">
<a
class="dropdown-item"
routerLink="/variable-scope"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }"
(click)="collapseNavbar()"
>
<fa-icon icon="asterisk" [fixedWidth]="true"></fa-icon>
<span jhiTranslate="global.menu.entities.variableScope">Variable Scope</span>
</a>
</li>
<li *ngIf="hasRole('ROLE_ADMIN') || (hasRole('ROLE_MANAGER') && hasReadPermission(Resources.VARIABLE_CATEGORY) )">
<a
class="dropdown-item"
routerLink="/variable-category"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }"
(click)="collapseNavbar()"
>
<fa-icon icon="asterisk" [fixedWidth]="true"></fa-icon>
<span jhiTranslate="global.menu.entities.variableCategory">Variable Category</span>
</a>
</li>
<li *ngIf="hasRole('ROLE_ADMIN') || (hasRole('ROLE_MANAGER') && hasReadPermission(Resources.VARIABLE) )">
<a
class="dropdown-item"
routerLink="/variable"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }"
(click)="collapseNavbar()"
>
<fa-icon icon="asterisk" [fixedWidth]="true"></fa-icon>
<span jhiTranslate="global.menu.entities.variable">Variable</span>
</a>
</li>
<li *ngIf="hasRole('ROLE_ADMIN') || hasRole('ROLE_MANAGER')"><div class="dropdown-divider"></div></li>
<li
*ngIf="hasRole('ROLE_ADMIN')
||
(
(hasRole('ROLE_MANAGER') || hasRole('ROLE_COORDINATOR'))
&&
hasReadPermission(Resources.INPUT_DATA_UPLOAD)
)"
>
<a
class="dropdown-item"
routerLink="/input-data-upload"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }"
(click)="collapseNavbar()"
>
<fa-icon icon="asterisk" [fixedWidth]="true"></fa-icon>
<span jhiTranslate="global.menu.entities.inputDataUpload">Input Data Upload</span>
</a>
</li>
<li
*ngIf="hasRole('ROLE_ADMIN')
||
(
( hasRole('ROLE_MANAGER') || hasRole('ROLE_COORDINATOR') )
&&
hasReadPermission(Resources.INPUT_DATA)
)"
>
<a
class="dropdown-item"
routerLink="/input-data"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }"
(click)="collapseNavbar()"
>
<fa-icon icon="asterisk" [fixedWidth]="true"></fa-icon>
<span jhiTranslate="global.menu.entities.inputData">Input Data</span>
</a>
</li>
<li
*ngIf="hasRole('ROLE_ADMIN')
||
(
(hasRole('ROLE_MANAGER') || hasRole('ROLE_COORDINATOR'))
&&
hasReadPermission(Resources.OUTPUT_DATA)
)"
>
<a
class="dropdown-item"
routerLink="/output-data"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }"
(click)="collapseNavbar()"
>
<fa-icon icon="asterisk" [fixedWidth]="true"></fa-icon>
<span jhiTranslate="global.menu.entities.outputData">Output Data</span>
</a>
</li>
</ul>
</li>
<li
*ngIf="hasRole('ROLE_ADMIN') || hasRole('ROLE_MANAGER')"
ngbDropdown
class="nav-item dropdown pointer"
display="dynamic"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }"
>
<a class="nav-link dropdown-toggle" ngbDropdownToggle href="javascript:void(0);" id="setup-menu" data-cy="entity">
<span>
<fa-icon icon="th-list"></fa-icon>
<span jhiTranslate="global.menu.setup.main">Parametrização</span>
</span>
</a>
<ul class="dropdown-menu" ngbDropdownMenu aria-labelledby="setup-menu">
<li *ngIf="hasRole('ROLE_ADMIN') || hasReadPermission(Resources.ORGANIZATION_TYPE)">
<a
class="dropdown-item"
routerLink="/organization-type"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }"
(click)="collapseNavbar()"
>
<fa-icon icon="asterisk" [fixedWidth]="true"></fa-icon>
<span jhiTranslate="global.menu.entities.organizationType">Organization Type</span>
</a>
</li>
<li *ngIf="hasRole('ROLE_ADMIN') || hasReadPermission(Resources.METADATA_PROPERTY)">
<a
class="dropdown-item"
routerLink="/metadata-property"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }"
(click)="collapseNavbar()"
>
<fa-icon icon="asterisk" [fixedWidth]="true"></fa-icon>
<span jhiTranslate="global.menu.entities.metadataProperty">Metadata Property</span>
</a>
</li>
<li *ngIf="hasRole('ROLE_ADMIN') || hasReadPermission(Resources.METADATA_VALUE)">
<a
class="dropdown-item"
routerLink="/metadata-value"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }"
(click)="collapseNavbar()"
>
<fa-icon icon="asterisk" [fixedWidth]="true"></fa-icon>
<span jhiTranslate="global.menu.entities.metadataValue">Metadata Value</span>
</a>
</li>
<li *ngIf="hasRole('ROLE_ADMIN') || hasReadPermission(Resources.VARIABLE_CLASS_TYPE)">
<a
class="dropdown-item"
routerLink="/variable-class-type"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }"
(click)="collapseNavbar()"
>
<fa-icon icon="asterisk" [fixedWidth]="true"></fa-icon>
<span jhiTranslate="global.menu.entities.variableClassType">Variable Class Type</span>
</a>
</li>
<li *ngIf="hasRole('ROLE_ADMIN') || hasReadPermission(Resources.EMISSION_FACTOR)">
<a
class="dropdown-item"
routerLink="/emission-factor"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }"
(click)="collapseNavbar()"
>
<fa-icon icon="asterisk" [fixedWidth]="true"></fa-icon>
<span jhiTranslate="global.menu.entities.emissionFactor">Emission Factors</span>
</a>
</li>
<li *ngIf="hasRole('ROLE_ADMIN') || hasReadPermission(Resources.DASHBOARD_COMPONENT)">
<a
class="dropdown-item"
routerLink="/dashboard-component"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }"
(click)="collapseNavbar()"
>
<fa-icon icon="asterisk" [fixedWidth]="true"></fa-icon>
<span jhiTranslate="global.menu.entities.dashboardComponent">Dashboard Comp.</span>
</a>
</li>
<li *ngIf="hasRole('ROLE_ADMIN') || hasReadPermission(Resources.DOCUMENT)">
<a
class="dropdown-item"
routerLink="/document"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }"
(click)="collapseNavbar()"
>
<fa-icon icon="asterisk" [fixedWidth]="true"></fa-icon>
<span jhiTranslate="global.menu.admin.document">Document</span>
</a>
</li>
<li *ngIf="hasRole('ROLE_ADMIN') || hasReadPermission(Resources.CONTENT_PAGE)">
<a
class="dropdown-item"
routerLink="/content-page"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }"
(click)="collapseNavbar()"
>
<fa-icon icon="asterisk" [fixedWidth]="true"></fa-icon>
<span jhiTranslate="global.menu.admin.contentPage">Content Page</span>
</a>
</li>
</ul>
</li>
}
<li
*jhiHasAnyAuthority="'ROLE_ADMIN'"
ngbDropdown
class="nav-item dropdown pointer"
display="dynamic"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }"
>
<a class="nav-link dropdown-toggle" ngbDropdownToggle href="javascript:void(0);" id="admin-menu" data-cy="adminMenu">
<span>
<fa-icon icon="users-cog"></fa-icon>
<span jhiTranslate="global.menu.admin.main">Administração</span>
</span>
</a>
<ul class="dropdown-menu" ngbDropdownMenu aria-labelledby="admin-menu">
<li>
<a
class="dropdown-item"
routerLink="/authority"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }"
(click)="collapseNavbar()"
>
<fa-icon icon="asterisk" [fixedWidth]="true"></fa-icon>
<span jhiTranslate="global.menu.entities.adminAuthority">Authority</span>
</a>
</li>
<!-- jhipster-needle-add-element-to-admin-menu - JHipster will add entities to the admin menu here -->
<li>
<a class="dropdown-item" routerLink="/admin/user-management" routerLinkActive="active" (click)="collapseNavbar()">
<fa-icon icon="users" [fixedWidth]="true"></fa-icon>
<span jhiTranslate="global.menu.admin.userManagement">Gestão de utilizadores</span>
</a>
</li>
<li>
<a class="dropdown-item" routerLink="/security/security-group" routerLinkActive="active" (click)="collapseNavbar()">
<fa-icon icon="users" [fixedWidth]="true"></fa-icon>
<span jhiTranslate="global.menu.admin.security.securityGroup">Gestão de Permissões</span>
</a>
</li>
<li>
<a class="dropdown-item" routerLink="/admin/metrics" routerLinkActive="active" (click)="collapseNavbar()">
<fa-icon icon="tachometer-alt" [fixedWidth]="true"></fa-icon>
<span jhiTranslate="global.menu.admin.metrics">Métricas</span>
</a>
</li>
<li>
<a class="dropdown-item" routerLink="/admin/health" routerLinkActive="active" (click)="collapseNavbar()">
<fa-icon icon="heart" [fixedWidth]="true"></fa-icon>
<span jhiTranslate="global.menu.admin.health">Estado do Sistema</span>
</a>
</li>
<li>
<a class="dropdown-item" routerLink="/admin/configuration" routerLinkActive="active" (click)="collapseNavbar()">
<fa-icon icon="cogs" [fixedWidth]="true"></fa-icon>
<span jhiTranslate="global.menu.admin.configuration">Configuração</span>
</a>
</li>
<li>
<a class="dropdown-item" routerLink="/admin/logs" routerLinkActive="active" (click)="collapseNavbar()">
<fa-icon icon="tasks" [fixedWidth]="true"></fa-icon>
<span jhiTranslate="global.menu.admin.logs">Logs</span>
</a>
</li>
@if (openAPIEnabled) {
<li>
<a class="dropdown-item" routerLink="/admin/docs" routerLinkActive="active" (click)="collapseNavbar()">
<fa-icon icon="book" [fixedWidth]="true"></fa-icon>
<span jhiTranslate="global.menu.admin.apidocs">API</span>
</a>
</li>
}
</ul>
</li>
{{ '' // Disable the language selector}}
@if (false && languages && languages.length > 1) {
<li ngbDropdown class="nav-item dropdown pointer" display="dynamic">
<a class="nav-link dropdown-toggle" ngbDropdownToggle href="javascript:void(0);" id="languagesnavBarDropdown">
<span>
<fa-icon icon="flag"></fa-icon>
<span jhiTranslate="global.menu.language">Idioma</span>
</span>
</a>
<ul class="dropdown-menu" ngbDropdownMenu aria-labelledby="languagesnavBarDropdown">
@for (language of languages; track $index) {
<li>
<a
class="dropdown-item"
[jhiActiveMenu]="language"
href="javascript:void(0);"
(click)="changeLanguage(language); collapseNavbar()"
>{{ language | findLanguageFromKey }}</a
>
</li>
}
</ul>
</li>
}
@if (account() !== null && documents && documents.length>0) {
<li
ngbDropdown
#myDocumentsDropdown="ngbDropdown"
*ngIf="hasRole('ROLE_ADMIN') || hasRole('ROLE_MANAGER') || hasRole('ROLE_USER')"
class="nav-item dropdown pointer"
display="dynamic"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }"
>
<a class="nav-link dropdown-toggle" ngbDropdownToggle href="javascript:void(0);" id="documents-menu" data-cy="documentsMenu" title="Documentos de Suporte">
<fa-icon class="fa-lg" icon="file-pdf"></fa-icon>
<span jhiTranslate="global.menu.documents.main">Ficheiros</span>
</a>
<ul class="dropdown-menu" ngbDropdownMenu aria-labelledby="documents-menu">
<li *ngFor="let document of documents">
<a
(click)="openFile(document!.dataFile ?? '', document!.dataFileContentType); myDocumentsDropdown.close()">
<span>{{ document.title }}</span>
</a>
</li>
</ul>
</li>
}
@if (account() !== null) {
<li
ngbDropdown
class="nav-item dropdown pointer"
display="dynamic"
routerLinkActive="active"
[routerLinkActiveOptions]="{ exact: true }"
>
<a class="nav-link dropdown-toggle" ngbDropdownToggle href="javascript:void(0);" id="account-menu" data-cy="accountMenu">
@if (!account()?.imageUrl) {
<span>
<fa-icon icon="user"></fa-icon>
<span jhiTranslate="global.menu.account.main">Conta</span>
</span>
} @else {
<span>
<img [src]="account()!.imageUrl" class="profile-image rounded-circle" alt="Avatar" />
</span>
}
</a>
<ul class="dropdown-menu" ngbDropdownMenu aria-labelledby="account-menu">
<li>
<a
class="dropdown-item"
routerLink="/account/settings"
routerLinkActive="active"
(click)="collapseNavbar()"
data-cy="settings"
>
<fa-icon icon="wrench" [fixedWidth]="true"></fa-icon>
<span jhiTranslate="global.menu.account.settings">Configuração</span>
</a>
</li>
<li>
<a
class="dropdown-item"
routerLink="/account/password"
routerLinkActive="active"
(click)="collapseNavbar()"
data-cy="passwordItem"
>
<fa-icon icon="lock" [fixedWidth]="true"></fa-icon>
<span jhiTranslate="global.menu.account.password">Palavra-passe</span>
</a>
</li>
<li *ngIf="hasRole('ROLE_ADMIN')">
<a class="dropdown-item" routerLink="/account/sessions" routerLinkActive="active" (click)="collapseNavbar()">
<fa-icon icon="cloud" [fixedWidth]="true"></fa-icon>
<span jhiTranslate="global.menu.account.sessions">Sessões</span>
</a>
</li>
<li>
<a class="dropdown-item" (click)="logout()" id="logout" data-cy="logout">
<fa-icon icon="sign-out-alt" [fixedWidth]="true"></fa-icon>
<span jhiTranslate="global.menu.account.logout">Sair</span>
</a>
</li>
</ul>
</li>
} @else {
<li>
<a class="dropdown-item" (click)="login()" id="login" data-cy="login">
<fa-icon icon="user" [fixedWidth]="true"></fa-icon>
<span jhiTranslate="global.menu.account.login">Entrar</span>
</a>
</li>
}
@if (account() !== null) {
<jhi-resilient-organizations></jhi-resilient-organizations>
}
</ul>
</div>
</div>
</nav>

View file

@ -0,0 +1,50 @@
@import 'bootstrap/scss/functions';
@import 'bootstrap/scss/variables';
/* ==========================================================================
Navbar
========================================================================== */
.navbar-version {
font-size: 0.65em;
color: $navbar-dark-color;
}
.profile-image {
height: 1.75em;
width: 1.75em;
}
.navbar {
padding: 0.2rem 1rem;
a.nav-link {
font-weight: 400;
}
}
/* ==========================================================================
Logo styles
========================================================================== */
.logo-img {
height: 45px;
width: 200px;
display: inline-block;
vertical-align: middle;
background: url('/content/images/logo-nova-group-zero.svg') no-repeat center center;
background-size: contain;
}
/* ==========================================================================
Fix navbar to top
========================================================================== */
.navbar {
position: fixed; /* Fixes it at the top */
top: 0; /* Aligns it to the top */
left: 0; /* Ensures it starts from the left */
width: 100%; /* Full-width */
background-color: #333; /* Example background */
color: white;
padding: 10px 20px;
z-index: 1000; /* Ensures it stays above other elements */
}

View file

@ -0,0 +1,95 @@
jest.mock('app/login/login.service');
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { of } from 'rxjs';
import { TranslateModule } from '@ngx-translate/core';
import { ProfileInfo } from 'app/layouts/profiles/profile-info.model';
import { Account } from 'app/core/auth/account.model';
import { AccountService } from 'app/core/auth/account.service';
import { ProfileService } from 'app/layouts/profiles/profile.service';
import { LoginService } from 'app/login/login.service';
import NavbarComponent from './navbar.component';
describe('Navbar Component', () => {
let comp: NavbarComponent;
let fixture: ComponentFixture<NavbarComponent>;
let accountService: AccountService;
let profileService: ProfileService;
const account: Account = {
activated: true,
authorities: [],
email: '',
firstName: 'John',
langKey: '',
lastName: 'Doe',
login: 'john.doe',
imageUrl: '',
};
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [NavbarComponent, HttpClientTestingModule, TranslateModule.forRoot()],
providers: [LoginService],
})
.overrideTemplate(NavbarComponent, '')
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(NavbarComponent);
comp = fixture.componentInstance;
accountService = TestBed.inject(AccountService);
profileService = TestBed.inject(ProfileService);
});
it('Should call profileService.getProfileInfo on init', () => {
// GIVEN
jest.spyOn(profileService, 'getProfileInfo').mockReturnValue(of(new ProfileInfo()));
// WHEN
comp.ngOnInit();
// THEN
expect(profileService.getProfileInfo).toHaveBeenCalled();
});
it('Should hold current authenticated user in variable account', () => {
// WHEN
comp.ngOnInit();
// THEN
expect(comp.account()).toBeNull();
// WHEN
accountService.authenticate(account);
// THEN
expect(comp.account()).toEqual(account);
// WHEN
accountService.authenticate(null);
// THEN
expect(comp.account()).toBeNull();
});
it('Should hold current authenticated user in variable account if user is authenticated before page load', () => {
// GIVEN
accountService.authenticate(account);
// WHEN
comp.ngOnInit();
// THEN
expect(comp.account()).toEqual(account);
// WHEN
accountService.authenticate(null);
// THEN
expect(comp.account()).toBeNull();
});
});

View file

@ -0,0 +1,154 @@
import { Component, inject, signal, OnInit, OnDestroy, Renderer2 } from '@angular/core';
import { Router, RouterModule } from '@angular/router';
import { TranslateService } from '@ngx-translate/core';
import { Subscription, map } from 'rxjs';
import { StateStorageService } from 'app/core/auth/state-storage.service';
import SharedModule from 'app/shared/shared.module';
import HasAnyAuthorityDirective from 'app/shared/auth/has-any-authority.directive';
import { VERSION } from 'app/app.constants';
import { LANGUAGES } from 'app/config/language.constants';
import { AccountService } from 'app/core/auth/account.service';
import { LoginService } from 'app/login/login.service';
import { ProfileService } from 'app/layouts/profiles/profile.service';
import { EntityNavbarItems } from 'app/entities/entity-navbar-items';
import ActiveMenuDirective from './active-menu.directive';
import NavbarItem from './navbar-item.model';
import {ResilientOrganizationsComponent} from 'app/resilient/resilient-environment/organizations/resilient-organizations.component'
import { DocumentService } from 'app/entities/document/service/document.service';
import { IDocument } from 'app/entities/document/document.model';
import { HttpResponse } from '@angular/common/http';
import { DataUtils } from 'app/core/util/data-util.service';
import { Resources } from 'app/security/resources.model';
import { SecurityPermission } from 'app/security/security-permission.model';
import { SecurityAction } from 'app/security/security-action.model';
@Component({
standalone: true,
selector: 'jhi-navbar',
templateUrl: './navbar.component.html',
styleUrl: './navbar.component.scss',
imports: [RouterModule, SharedModule, HasAnyAuthorityDirective, ActiveMenuDirective, ResilientOrganizationsComponent],
})
export default class NavbarComponent implements OnInit, OnDestroy {
private authSubscription?: Subscription;
inProduction?: boolean;
isNavbarCollapsed = signal(true);
languages = LANGUAGES;
openAPIEnabled?: boolean;
version = '';
entitiesNavbarItems: NavbarItem[] = [];
documents?: IDocument[];
private loginService = inject(LoginService);
private translateService = inject(TranslateService);
private stateStorageService = inject(StateStorageService);
private profileService = inject(ProfileService);
private router = inject(Router);
private accountService = inject(AccountService);
private documentService = inject(DocumentService);
protected dataUtils = inject(DataUtils);
account = this.accountService.trackCurrentAccount();
/**
* This makes the enum accessible in the template
* Use it, mainly, in html templates for security.
* Example:
* <button *ngIf="hasCreatePermission(Resources.UNIT_TYPE)"></button>
*/
Resources = Resources;
constructor(private renderer: Renderer2) {
if (VERSION) {
this.version = VERSION.toLowerCase().startsWith('v') ? VERSION : `v${VERSION}`;
}
}
ngOnInit(): void {
this.entitiesNavbarItems = EntityNavbarItems;
this.profileService.getProfileInfo().subscribe(profileInfo => {
this.inProduction = profileInfo.inProduction;
this.openAPIEnabled = profileInfo.openAPIEnabled;
});
this.authSubscription = this.accountService.getAuthenticationState().subscribe(account => {
if (account) {
this.renderer.addClass(document.body, 'authenticated');
if (!this.documents || this.documents.length==0) {
// Only when user is authenticated. And if documents[] NOT already loaded.
// NOTE: Before, I was doing it always, for unauthenticated users it failed (security) and redirected him to login page. THIS is not intended,
// the user must be hable to access public anonymous HOME page.
this.loadDocuments();
}
} else {
this.renderer.removeClass(document.body, 'authenticated');
}
});
// add 'navbar-present' class
this.renderer.addClass(document.body, 'navbar-present');
}
ngOnDestroy(): void {
this.authSubscription?.unsubscribe();
// Remove class
this.renderer.removeClass(document.body, 'navbar-present');
}
changeLanguage(languageKey: string): void {
this.stateStorageService.storeLocale(languageKey);
this.translateService.use(languageKey);
}
collapseNavbar(): void {
this.isNavbarCollapsed.set(true);
}
login(): void {
this.router.navigate(['/login']);
}
logout(): void {
this.collapseNavbar();
this.loginService.logout();
this.router.navigate(['']);
}
toggleNavbar(): void {
this.isNavbarCollapsed.update(isNavbarCollapsed => !isNavbarCollapsed);
}
hasRole(role: string): boolean {
return this.accountService.hasAnyAuthority([role]);
}
hasReadPermission(evalResource: Resources): boolean {
const allowPermissions = [SecurityPermission.ALL, SecurityPermission.HIERARCHY];
return allowPermissions.includes(this.accountService.getPermission(evalResource, SecurityAction.READ));
}
openFile(base64String: string, contentType: string | null | undefined): void {
this.dataUtils.openFile(base64String, contentType);
}
protected loadDocuments(): void {
const queryObject: any = {
eagerload: true,
};
// Load documents
this.documentService
.query(queryObject)
.pipe(
map((res: HttpResponse<IDocument[]>) => res.body ?? [])
).subscribe({
next: (docs: IDocument[]) => {
this.documents = docs;
},
});;
}
}

View file

@ -0,0 +1,25 @@
/* ==========================================================================
Development Ribbon
========================================================================== */
.ribbon {
background-color: rgba(170, 0, 0, 0.5);
overflow: hidden;
position: absolute;
top: 40px;
white-space: nowrap;
width: 15em;
z-index: 9999;
pointer-events: none;
opacity: 0.75;
a {
color: #fff;
display: block;
font-weight: 400;
margin: 1px 0;
padding: 10px 50px;
text-align: center;
text-decoration: none;
text-shadow: 0 0 5px #444;
pointer-events: none;
}
}

View file

@ -0,0 +1,39 @@
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
import { HttpClientTestingModule } from '@angular/common/http/testing';
import { of } from 'rxjs';
import { ProfileInfo } from 'app/layouts/profiles/profile-info.model';
import { ProfileService } from 'app/layouts/profiles/profile.service';
import PageRibbonComponent from './page-ribbon.component';
describe('Page Ribbon Component', () => {
let comp: PageRibbonComponent;
let fixture: ComponentFixture<PageRibbonComponent>;
let profileService: ProfileService;
beforeEach(waitForAsync(() => {
TestBed.configureTestingModule({
imports: [HttpClientTestingModule, PageRibbonComponent],
})
.overrideTemplate(PageRibbonComponent, '')
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(PageRibbonComponent);
comp = fixture.componentInstance;
profileService = TestBed.inject(ProfileService);
});
it('Should call profileService.getProfileInfo on init', () => {
// GIVEN
jest.spyOn(profileService, 'getProfileInfo').mockReturnValue(of(new ProfileInfo()));
// WHEN
comp.ngOnInit();
// THEN
expect(profileService.getProfileInfo).toHaveBeenCalled();
});
});

View file

@ -0,0 +1,29 @@
import { Component, inject, OnInit } from '@angular/core';
import { Observable } from 'rxjs';
import { map } from 'rxjs/operators';
import SharedModule from 'app/shared/shared.module';
import { ProfileService } from './profile.service';
@Component({
standalone: true,
selector: 'jhi-page-ribbon',
template: `
@if (ribbonEnv$ | async; as ribbonEnv) {
<div class="ribbon">
<a href="" [jhiTranslate]="'global.ribbon.' + (ribbonEnv ?? '')">{{ { dev: 'Desenvolvimento' }[ribbonEnv ?? ''] }}</a>
</div>
}
`,
styleUrl: './page-ribbon.component.scss',
imports: [SharedModule],
})
export default class PageRibbonComponent implements OnInit {
ribbonEnv$?: Observable<string | undefined>;
private profileService = inject(ProfileService);
ngOnInit(): void {
this.ribbonEnv$ = this.profileService.getProfileInfo().pipe(map(profileInfo => profileInfo.ribbonEnv));
}
}

View file

@ -0,0 +1,15 @@
export interface InfoResponse {
'display-ribbon-on-profiles'?: string;
git?: any;
build?: any;
activeProfiles?: string[];
}
export class ProfileInfo {
constructor(
public activeProfiles?: string[],
public ribbonEnv?: string,
public inProduction?: boolean,
public openAPIEnabled?: boolean,
) {}
}

View file

@ -0,0 +1,42 @@
import { inject, Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
import { map, shareReplay } from 'rxjs/operators';
import { Observable } from 'rxjs';
import { ApplicationConfigService } from 'app/core/config/application-config.service';
import { ProfileInfo, InfoResponse } from './profile-info.model';
@Injectable({ providedIn: 'root' })
export class ProfileService {
private http = inject(HttpClient);
private applicationConfigService = inject(ApplicationConfigService);
private infoUrl = this.applicationConfigService.getEndpointFor('management/info');
private profileInfo$?: Observable<ProfileInfo>;
getProfileInfo(): Observable<ProfileInfo> {
if (this.profileInfo$) {
return this.profileInfo$;
}
this.profileInfo$ = this.http.get<InfoResponse>(this.infoUrl).pipe(
map((response: InfoResponse) => {
const profileInfo: ProfileInfo = {
activeProfiles: response.activeProfiles,
inProduction: response.activeProfiles?.includes('prod'),
openAPIEnabled: response.activeProfiles?.includes('api-docs'),
};
if (response.activeProfiles && response['display-ribbon-on-profiles']) {
const displayRibbonOnProfiles = response['display-ribbon-on-profiles'].split(',');
const ribbonProfiles = displayRibbonOnProfiles.filter(profile => response.activeProfiles?.includes(profile));
if (ribbonProfiles.length > 0) {
profileInfo.ribbonEnv = ribbonProfiles[0];
}
}
return profileInfo;
}),
shareReplay(),
);
return this.profileInfo$;
}
}