Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
f944635
feat[frontend](federation_server): added federation server front hand…
AlexSanchez-bit Jun 18, 2026
085daf9
fix[frontend](federation_service): added instance selector and actions
AlexSanchez-bit Jun 18, 2026
4f12e13
fix[frontend](federation_service): added remove modal on instance remove
AlexSanchez-bit Jun 18, 2026
27db1ad
fix[frontend](federation_service): added profile management
AlexSanchez-bit Jun 18, 2026
b9b0d03
fix[frontend](federation_service): added team management
AlexSanchez-bit Jun 18, 2026
f960688
fix[federation](white_label): reload images after federation instance…
AlexSanchez-bit Jun 22, 2026
b29af62
feat[instances](overview): added instances stadistics overview
AlexSanchez-bit Jun 22, 2026
b293531
feat[frontend](federation): added federation left sidebar
AlexSanchez-bit Jun 22, 2026
5c86fdd
feat[frontend](federation): fixed overview charts
AlexSanchez-bit Jun 22, 2026
645fb23
feat[frontend](federation): fixed overview icons
AlexSanchez-bit Jun 22, 2026
c31a0f5
feat[frontend](federation): setted user options only on federation si…
AlexSanchez-bit Jun 22, 2026
43f6794
feat[frontend](federation): moved second header to fit federation sid…
AlexSanchez-bit Jun 23, 2026
98a7d96
fix[frontend](federation): removed misplaced letter on federation sid…
AlexSanchez-bit Jun 23, 2026
b66694c
fix[frontend](federation): passed by actions on overview cards instea…
AlexSanchez-bit Jun 23, 2026
4e85c0a
fix[frontend](federation): removed non related headers on federation …
AlexSanchez-bit Jun 23, 2026
f275930
fix[frontend](federation): removed federation sidebar
AlexSanchez-bit Jun 23, 2026
ee97287
fix[frontend](federation): added federation user profile dropdown
AlexSanchez-bit Jun 23, 2026
a7d61bc
fix[frontend](federation): fixed federation instance selection menu
AlexSanchez-bit Jun 23, 2026
556d077
fix[frontend](federation): added federation management option on fede…
AlexSanchez-bit Jun 23, 2026
9de2d98
fix[frontend](federation): removed federation welcome navigation button
AlexSanchez-bit Jun 23, 2026
951719a
fix[frontend](federation): removed secondary sidebar if is not in fed…
AlexSanchez-bit Jun 23, 2026
1c7b58a
fix[frontend](federation):separated notifications and user actions bu…
AlexSanchez-bit Jun 23, 2026
6a2e609
fix[frontend](federation): fixed overview card components
AlexSanchez-bit Jun 23, 2026
c06f33a
fix[frontend](federation): added password validation to have 8 charac…
AlexSanchez-bit Jun 23, 2026
2287c95
fix[frontend](federation): added needed query param on alert overview…
AlexSanchez-bit Jun 23, 2026
e45e43e
fix[frontend](federation): fixed overview redirection
AlexSanchez-bit Jun 23, 2026
d1af47a
fix[frontend](federation): fixed new instance reload on create
AlexSanchez-bit Jun 23, 2026
932f6dc
fix[frontend](federation): added email configuration for federation use
AlexSanchez-bit Jun 23, 2026
0806bf2
fix[frontend](federation): added email configuration for federation use
AlexSanchez-bit Jun 23, 2026
49245f8
fix[frontend](federation): added email configuration initial paramete…
AlexSanchez-bit Jun 23, 2026
d08d9c6
fix[frontend](federation/email_conf): removed route guard on email co…
AlexSanchez-bit Jun 24, 2026
c9ea833
fix[frontend](federation/settings_nav): added route guard to avoid in…
AlexSanchez-bit Jun 24, 2026
ea021e0
fix[frontend](federation/settings_nav):rolled back menu header changes
AlexSanchez-bit Jun 24, 2026
06f15ec
fix[frontend](team): setted 0-indexed user list and header pagination…
AlexSanchez-bit Sep 24, 2026
a503f21
fix[frontend](config): reset email/TFA checked flags on field change …
AlexSanchez-bit Sep 24, 2026
aa98fd3
fix[frontend](password_reset): setted up password reset so reset mail…
AlexSanchez-bit Sep 29, 2026
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
26 changes: 25 additions & 1 deletion frontend/src/app/app-routing.module.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,12 @@
import {NgModule} from '@angular/core';
import {RouterModule, Routes} from '@angular/router';
import {UserRouteAccessService} from './core/auth/user-route-access-service';
import {FederationDisabledGuard} from './federation/guards/federation-disabled.guard';
import {
FederationEmailConfigPageComponent
} from './federation/pages/email-config/federation-email-config.page.component';
import {TeamMembersPageComponent} from './federation/pages/team-members/team-members.page.component';
import {WelcomeComponent as FederationWelcomeComponent} from './federation/pages/welcome/welcome.component';
import {ConfirmIdentityComponent} from './shared/components/auth/confirm-identity/confirm-identity.component';
import {LoginComponent} from './shared/components/auth/login/login.component';
import {
Expand Down Expand Up @@ -69,7 +75,8 @@ const routes: Routes = [
{
path: 'app-management',
loadChildren: './app-management/app-management.module#AppManagementModule',
canActivate: [UserRouteAccessService],
canActivate: [UserRouteAccessService, FederationDisabledGuard],
canActivateChild: [FederationDisabledGuard],
data: {authorities: [USER_ROLE, ADMIN_ROLE]}
},
{
Expand Down Expand Up @@ -152,9 +159,26 @@ const routes: Routes = [
canActivate: [UserRouteAccessService],
data: {authorities: [ADMIN_ROLE, USER_ROLE]}
},
{
path: 'federation',
canActivate: [UserRouteAccessService],
data: {authorities: [ADMIN_ROLE, USER_ROLE]},
children: [
{path: '', redirectTo: 'welcome', pathMatch: 'full'},
{path: 'welcome', component: FederationWelcomeComponent},
{path: 'instances', component: FederationWelcomeComponent},
{path: 'team-members', component: TeamMembersPageComponent},
{
path: 'email-config',
component: FederationEmailConfigPageComponent,
data: {authorities: [ADMIN_ROLE]}
}
]
},
{path: '', component: LoginComponent},
{path: 'totp', component: TotpComponent},
{path: 'enroll-tfa', component: TfaSetupComponent},
{path: 'auth/login', component: PasswordResetFinishComponent},
{path: 'reset/finish', component: PasswordResetFinishComponent},
{path: 'page-not-found', component: NotFoundComponent},
{path: 'confirm-identity/:id', component: ConfirmIdentityComponent},
Expand Down
15 changes: 9 additions & 6 deletions frontend/src/app/app.component.html
Original file line number Diff line number Diff line change
@@ -1,16 +1,19 @@
<div class="app-container">
<app-header *appHasAnyAuthority="roles" [hidden]="isInExportRoute()"></app-header>
<div [class]="isInExportRoute() ? ' wrapper wrapper-no-top' : 'wrapper'" [ngClass]="menu ? 'menu-open': 'menu-close'">
<div [class]="isInExportRoute() ? ' wrapper wrapper-no-top' : 'wrapper'"
[ngClass]="(federationActive$ | async) ? 'federation-mode' : (menu ? 'menu-open': 'menu-close')">
<div class="main-content">
<router-outlet></router-outlet>
</div>

<div *appHasAnyAuthority="roles" [hidden]="!menu || isInExportRoute()" class="side-menu">
<div
class="sidebar sidebar-light sidebar-main sidebar-expand-md">
<app-sidebar></app-sidebar>
<ng-container *appHasAnyAuthority="roles">
<div *ngIf="!(federationActive$ | async)"
[hidden]="!menu || isInExportRoute()" class="side-menu">
<div class="sidebar sidebar-light sidebar-main sidebar-expand-md">
<app-sidebar></app-sidebar>
</div>
</div>
</div>
</ng-container>
</div>
<!--<app-app-restart-api></app-app-restart-api>-->
<ngx-spinner
Expand Down
4 changes: 4 additions & 0 deletions frontend/src/app/app.component.scss
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,10 @@
animation: ease-in;
}

.federation-mode {
margin-left: 0;
}

//
//app-mobile-header {
// display: none;
Expand Down
16 changes: 13 additions & 3 deletions frontend/src/app/app.component.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,19 @@
import {Component, HostListener, OnInit, Renderer2} from '@angular/core';
import {Router} from '@angular/router';
import {TranslateService} from '@ngx-translate/core';
import {catchError, delay, distinctUntilChanged, filter, map, takeUntil, tap} from 'rxjs/operators';
import {catchError, delay, distinctUntilChanged, filter, map, skip, takeUntil, tap} from 'rxjs/operators';
import {AccountService} from './core/auth/account.service';
import {ApiServiceCheckerService} from './core/auth/api-checker-service';
import {FederationInstanceStateService} from './federation/services/federation-instance-state.service';
import {FederationModeService} from './federation/services/federation-mode.service';
import {MenuBehavior} from './shared/behaviors/menu.behavior';
import {ThemeChangeBehavior} from './shared/behaviors/theme-change.behavior';
import {ADMIN_ROLE, USER_ROLE} from './shared/constants/global.constant';
import {AppThemeLocationEnum} from './shared/enums/app-theme-location.enum';
import {UtmAppThemeService} from './shared/services/theme/utm-app-theme.service';
import {AppVersionInfo} from "./shared/types/updates/updates.type";
import {VersionType, VersionInfoService} from "./shared/services/version/version-info.service";
import {EMPTY} from "rxjs";
import {EMPTY, Observable} from "rxjs";
import {AppVersionService} from './shared/services/version/app-version.service';

@Component({
Expand All @@ -32,6 +34,7 @@ export class AppComponent implements OnInit {
hideStatus = false;
isAuth = false;
viewportHeight: number;
federationActive$: Observable<boolean>;

constructor(
private translate: TranslateService,
Expand All @@ -42,7 +45,10 @@ export class AppComponent implements OnInit {
private apiServiceCheckerService: ApiServiceCheckerService,
private accountService: AccountService,
private checkForUpdatesService: AppVersionService,
private versionTypeService: VersionInfoService) {
private versionTypeService: VersionInfoService,
private federationInstanceState: FederationInstanceStateService,
private federationModeService: FederationModeService) {
this.federationActive$ = this.federationModeService.active$;

this.translate.setDefaultLang('en');

Expand Down Expand Up @@ -82,6 +88,10 @@ export class AppComponent implements OnInit {
}
});

this.federationInstanceState.switch$
.pipe(skip(1))
.subscribe(() => this.getReportLogo());

this.accountService.getAuthenticationState()
.pipe(distinctUntilChanged((prev, next) => prev && next && prev.id === next.id))
.subscribe(identity => {
Expand Down
14 changes: 14 additions & 0 deletions frontend/src/app/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import {AppComponent} from './app.component';
import {AuthExpiredInterceptor} from './blocks/interceptor/auth-expired.interceptor';
import {AuthInterceptor} from './blocks/interceptor/auth.interceptor';
import {ErrorHandlerInterceptor} from './blocks/interceptor/errorhandler.interceptor';
import {FederationInstanceInterceptor} from './blocks/interceptor/federation-instance.interceptor';
import {ManageHttpInterceptor} from './blocks/interceptor/managehttp.interceptor';
import {NotificationInterceptor} from './blocks/interceptor/notification.interceptor';
import {HttpCancelService} from './blocks/service/httpcancel.service';
Expand All @@ -36,6 +37,8 @@ import {AlertIncidentStatusChangeBehavior} from './shared/behaviors/alert-incide
import {GettingStartedBehavior} from './shared/behaviors/getting-started.behavior';
import {NavBehavior} from './shared/behaviors/nav.behavior';
import {NewAlertBehavior} from './shared/behaviors/new-alert.behavior';
import {initFederationMode} from './federation/hooks/federation-bootstrap';
import {FederationModeService} from './federation/services/federation-mode.service';
import {TimezoneFormatService} from './shared/services/utm-timezone.service';
import {AppVersionService} from './shared/services/version/app-version.service';
import {UtmSharedModule} from './shared/utm-shared.module';
Expand Down Expand Up @@ -109,6 +112,11 @@ export function initTimezoneFormat(apiChecker: ApiServiceCheckerService,
deps: [SessionStorageService, LocalStorageService],
multi: true
},
{
provide: HTTP_INTERCEPTORS,
useClass: FederationInstanceInterceptor,
multi: true
},
{
provide: HTTP_INTERCEPTORS,
useClass: AuthExpiredInterceptor,
Expand All @@ -132,6 +140,12 @@ export function initTimezoneFormat(apiChecker: ApiServiceCheckerService,
useClass: ManageHttpInterceptor,
multi: true,
},
{
provide: APP_INITIALIZER,
useFactory: initFederationMode,
deps: [FederationModeService],
multi: true
},
{
provide: APP_INITIALIZER,
useFactory: initTimezoneFormat,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import {HttpEvent, HttpHandler, HttpInterceptor, HttpRequest} from '@angular/common/http';
import {Injectable} from '@angular/core';
import {Observable} from 'rxjs';
import {SERVER_API_URL} from '../../app.constants';
import {FederationInstanceStateService} from '../../federation/services/federation-instance-state.service';
import {FederationModeService} from '../../federation/services/federation-mode.service';

const AUTH_PATH_PREFIXES = [
'api/authenticate',
'api/v1/auth/',
'api/tfa/',
'auth/'
];

@Injectable()
export class FederationInstanceInterceptor implements HttpInterceptor {
constructor(private modeService: FederationModeService,
private instanceState: FederationInstanceStateService) {}

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
if (!this.modeService.isActive) {
return next.handle(request);
}
if (!this.isApiRequest(request.url)) {
return next.handle(request);
}
if (this.isAuthEndpoint(request.url)) {
return next.handle(request);
}
const instance = this.instanceState.current;
if (!instance) {
return next.handle(request);
}
const cloned = request.clone({
setHeaders: {'X-UTM-Instance': String(instance.id)}
});
return next.handle(cloned);
}

private isApiRequest(url: string): boolean {
if (!url) {
return false;
}
if (/^https?:/i.test(url)) {
return !!SERVER_API_URL && url.startsWith(SERVER_API_URL);
}
return true;
}

private isAuthEndpoint(url: string): boolean {
return AUTH_PATH_PREFIXES.some(prefix => url.includes(prefix));
}
}
46 changes: 41 additions & 5 deletions frontend/src/app/core/auth/account.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import {Observable, Subject} from 'rxjs';

import {SERVER_API_URL} from '../../app.constants';
import {HttpCancelService} from '../../blocks/service/httpcancel.service';
import {FederationInstancesService} from '../../federation/services/federation-instances.service';
import {FederationInstanceStateService} from '../../federation/services/federation-instance-state.service';
import {FederationModeService} from '../../federation/services/federation-mode.service';
import {Account} from '../user/account.model';
import {AuthServerProvider} from './auth-jwt.service';
import {extractQueryParamsForNavigation} from "../../shared/util/query-params-to-filter.util";
Expand All @@ -18,14 +21,18 @@ export class AccountService {
private userIdentity: Account;
private authenticated = false;
private authenticationState = new Subject<any>();
private instancesFetchInFlight = false;

constructor(private http: HttpClient,
private authServerProvider: AuthServerProvider,
private httpCancelService: HttpCancelService,
private stateStorageService: StateStorageService,
private router: Router,
private spinner: NgxSpinnerService,
private utmToast: UtmToastService) {
private utmToast: UtmToastService,
private federationModeService: FederationModeService,
private federationInstancesService: FederationInstancesService,
private federationInstanceState: FederationInstanceStateService) {
}

fetch(): Observable<HttpResponse<Account>> {
Expand Down Expand Up @@ -100,10 +107,13 @@ export class AccountService {
if (account) {
this.userIdentity = account;
this.authenticated = true;
} else {
this.userIdentity = null;
this.authenticated = false;
return this.ensureFederationInstancesLoaded().then(() => {
this.authenticationState.next(this.userIdentity);
return this.userIdentity;
});
}
this.userIdentity = null;
this.authenticated = false;
this.authenticationState.next(this.userIdentity);
return this.userIdentity;
})
Expand Down Expand Up @@ -141,17 +151,43 @@ export class AccountService {
}
}

private ensureFederationInstancesLoaded(): Promise<void> {
if (!this.federationModeService.isActive) {
return Promise.resolve();
}
if (this.federationInstanceState.instances.length > 0) {
return Promise.resolve();
}
if (this.instancesFetchInFlight) {
return Promise.resolve();
}
this.instancesFetchInFlight = true;
return this.federationInstancesService.list()
.toPromise()
.then(instances => {
this.instancesFetchInFlight = false;
this.federationInstanceState.setInstances(instances || []);
})
.catch(() => {
this.instancesFetchInFlight = false;
});
}

startNavigation() {
this.identity(true).then(account => {
if (account) {
if (this.federationModeService.isActive && this.federationInstanceState.instances.length === 0) {
this.router.navigate(['/federation/welcome'])
.then(() => this.spinner.hide());
return;
}
const { path, queryParams } =
extractQueryParamsForNavigation(this.stateStorageService.getUrl() ? this.stateStorageService.getUrl() : '' );
if (path) {
this.stateStorageService.resetPreviousUrl();
}
const redirectTo = (account.authorities.includes(ADMIN_ROLE) && account.email === ADMIN_DEFAULT_EMAIL)
? '/getting-started' : !!path ? path : '/dashboard/overview';
console.log(redirectTo);
this.router.navigate([redirectTo], {queryParams})
.then(() => this.spinner.hide());
} else {
Expand Down
16 changes: 15 additions & 1 deletion frontend/src/app/core/auth/user-route-access-service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,21 @@ import {Injectable, isDevMode} from '@angular/core';
import {ActivatedRouteSnapshot, CanActivate, Router, RouterStateSnapshot} from '@angular/router';
import {ModalService} from '../modal/modal.service';

import {FederationInstanceStateService} from '../../federation/services/federation-instance-state.service';
import {FederationModeService} from '../../federation/services/federation-mode.service';
import {AccountService} from './account.service';
import {StateStorageService} from './state-storage.service';

const FEDERATION_WELCOME_URL = '/federation/welcome';

@Injectable({providedIn: 'root'})
export class UserRouteAccessService implements CanActivate {
constructor(
private router: Router,
private accountService: AccountService,
private stateStorageService: StateStorageService
private stateStorageService: StateStorageService,
private federationModeService: FederationModeService,
private federationInstanceState: FederationInstanceStateService
) {
}

Expand All @@ -28,6 +34,14 @@ export class UserRouteAccessService implements CanActivate {
return false;
}
if (account) {
if (
this.federationModeService.isActive &&
this.federationInstanceState.instances.length === 0 &&
!url.startsWith(FEDERATION_WELCOME_URL)
) {
this.router.navigate([FEDERATION_WELCOME_URL]);
return false;
}
const hasAnyAuthority = this.accountService.hasAnyAuthority(authorities);
if (hasAnyAuthority) {
return true;
Expand Down
Loading
Loading