RED-6523: simplify tenants handling
This commit is contained in:
parent
b0afe65bbe
commit
069a345aa3
@ -3,6 +3,8 @@ import {
|
||||
CompositeRouteGuard,
|
||||
CustomRouteReuseStrategy,
|
||||
DEFAULT_REDIRECT_KEY,
|
||||
ifLoggedIn,
|
||||
ifNotLoggedIn,
|
||||
IqserAuthGuard,
|
||||
IqserPermissionsGuard,
|
||||
IqserRoutes,
|
||||
@ -24,6 +26,7 @@ import { ARCHIVE_ROUTE, BreadcrumbTypes, DOSSIER_ID, DOSSIER_TEMPLATE_ID, DOSSIE
|
||||
import { DossierFilesGuard } from '@guards/dossier-files-guard';
|
||||
import { WebViewerLoadedGuard } from './modules/pdf-viewer/services/webviewer-loaded.guard';
|
||||
import { ROLES } from '@users/roles';
|
||||
import { mainResolver } from '@utils/main.resolver';
|
||||
|
||||
const dossierTemplateIdRoutes: IqserRoutes = [
|
||||
{
|
||||
@ -94,127 +97,135 @@ const dossierTemplateIdRoutes: IqserRoutes = [
|
||||
},
|
||||
];
|
||||
|
||||
const mainRoutes: IqserRoutes = [
|
||||
{
|
||||
path: '',
|
||||
redirectTo: 'dashboard',
|
||||
pathMatch: 'full',
|
||||
},
|
||||
{
|
||||
path: 'account',
|
||||
loadChildren: () => import('./modules/account/account.module').then(m => m.AccountModule),
|
||||
},
|
||||
{
|
||||
path: 'admin',
|
||||
loadChildren: () => import('./modules/admin/admin.module').then(m => m.AdminModule),
|
||||
canActivate: [RedRoleGuard],
|
||||
},
|
||||
{
|
||||
path: 'dashboard',
|
||||
loadChildren: () => import('./modules/dashboard/dashboard.module').then(m => m.DashboardModule),
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [IqserAuthGuard, RedRoleGuard, IqserPermissionsGuard, DossierTemplatesGuard, DashboardGuard],
|
||||
permissions: {
|
||||
allow: [
|
||||
ROLES.any,
|
||||
ROLES.templates.read,
|
||||
ROLES.fileAttributes.readConfig,
|
||||
ROLES.watermarks.read,
|
||||
ROLES.dictionaryTypes.read,
|
||||
ROLES.colors.read,
|
||||
ROLES.states.read,
|
||||
ROLES.notifications.read,
|
||||
'RED_USER',
|
||||
],
|
||||
redirectTo: {
|
||||
RED_USER: '/main/admin',
|
||||
[ROLES.templates.read]: '/main/admin',
|
||||
[DEFAULT_REDIRECT_KEY]: '/auth-error',
|
||||
},
|
||||
},
|
||||
skeleton: 'dashboard',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'downloads',
|
||||
// TODO: transform into a lazy loaded module
|
||||
component: DownloadsListScreenComponent,
|
||||
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
|
||||
data: {
|
||||
routeGuards: [IqserAuthGuard, RedRoleGuard],
|
||||
permissions: {
|
||||
allow: ROLES.readDownloadStatus,
|
||||
redirectTo: '/auth-error',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'search',
|
||||
loadChildren: () => import('./modules/search/search.module').then(m => m.SearchModule),
|
||||
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
|
||||
data: {
|
||||
routeGuards: [IqserAuthGuard, RedRoleGuard, DossiersGuard],
|
||||
permissions: {
|
||||
allow: [ROLES.search],
|
||||
redirectTo: '/auth-error',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'trash',
|
||||
loadChildren: () => import('./modules/trash/trash.module').then(m => m.TrashModule),
|
||||
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
|
||||
data: {
|
||||
routeGuards: [IqserAuthGuard, RedRoleGuard, DossiersGuard, TrashGuard],
|
||||
dossiersService: ACTIVE_DOSSIERS_SERVICE,
|
||||
permissions: {
|
||||
allow: [ROLES.dossiers.read, ROLES.files.readStatus],
|
||||
redirectTo: '/auth-error',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
path: `:${DOSSIER_TEMPLATE_ID}`,
|
||||
children: dossierTemplateIdRoutes,
|
||||
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
|
||||
data: {
|
||||
routeGuards: [IqserAuthGuard, RedRoleGuard, DossierTemplatesGuard, DashboardGuard, DossierTemplateExistsGuard],
|
||||
permissions: {
|
||||
allow: [
|
||||
ROLES.any,
|
||||
ROLES.templates.read,
|
||||
ROLES.fileAttributes.readConfig,
|
||||
ROLES.watermarks.read,
|
||||
ROLES.dictionaryTypes.read,
|
||||
ROLES.colors.read,
|
||||
ROLES.states.read,
|
||||
ROLES.notifications.read,
|
||||
ROLES.dossiers.read,
|
||||
'RED_USER',
|
||||
],
|
||||
redirectTo: {
|
||||
[ROLES.any]: '/auth-error',
|
||||
RED_USER: '/main/admin',
|
||||
[DEFAULT_REDIRECT_KEY]: '/',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
];
|
||||
|
||||
const routes: IqserRoutes = [
|
||||
{
|
||||
path: '',
|
||||
pathMatch: 'full',
|
||||
canActivate: [ifNotLoggedIn],
|
||||
component: TenantResolveComponent,
|
||||
},
|
||||
{
|
||||
path: ':tenant',
|
||||
component: TenantResolveComponent,
|
||||
redirectTo: ':tenant/main',
|
||||
pathMatch: 'full',
|
||||
},
|
||||
{
|
||||
path: ':tenant/main',
|
||||
canActivate: [ifLoggedIn],
|
||||
component: BaseScreenComponent,
|
||||
children: [
|
||||
{
|
||||
path: '',
|
||||
redirectTo: 'dashboard',
|
||||
pathMatch: 'full',
|
||||
},
|
||||
{
|
||||
path: 'account',
|
||||
loadChildren: () => import('./modules/account/account.module').then(m => m.AccountModule),
|
||||
},
|
||||
{
|
||||
path: 'admin',
|
||||
loadChildren: () => import('./modules/admin/admin.module').then(m => m.AdminModule),
|
||||
canActivate: [RedRoleGuard],
|
||||
},
|
||||
{
|
||||
path: 'dashboard',
|
||||
loadChildren: () => import('./modules/dashboard/dashboard.module').then(m => m.DashboardModule),
|
||||
canActivate: [CompositeRouteGuard],
|
||||
data: {
|
||||
routeGuards: [IqserAuthGuard, RedRoleGuard, IqserPermissionsGuard, DossierTemplatesGuard, DashboardGuard],
|
||||
permissions: {
|
||||
allow: [
|
||||
ROLES.any,
|
||||
ROLES.templates.read,
|
||||
ROLES.fileAttributes.readConfig,
|
||||
ROLES.watermarks.read,
|
||||
ROLES.dictionaryTypes.read,
|
||||
ROLES.colors.read,
|
||||
ROLES.states.read,
|
||||
ROLES.notifications.read,
|
||||
'RED_USER',
|
||||
],
|
||||
redirectTo: {
|
||||
RED_USER: '/main/admin',
|
||||
[ROLES.templates.read]: '/main/admin',
|
||||
[DEFAULT_REDIRECT_KEY]: '/auth-error',
|
||||
},
|
||||
},
|
||||
skeleton: 'dashboard',
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'downloads',
|
||||
// TODO: transform into a lazy loaded module
|
||||
component: DownloadsListScreenComponent,
|
||||
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
|
||||
data: {
|
||||
routeGuards: [IqserAuthGuard, RedRoleGuard],
|
||||
permissions: {
|
||||
allow: ROLES.readDownloadStatus,
|
||||
redirectTo: '/auth-error',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'search',
|
||||
loadChildren: () => import('./modules/search/search.module').then(m => m.SearchModule),
|
||||
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
|
||||
data: {
|
||||
routeGuards: [IqserAuthGuard, RedRoleGuard, DossiersGuard],
|
||||
permissions: {
|
||||
allow: [ROLES.search],
|
||||
redirectTo: '/auth-error',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'trash',
|
||||
loadChildren: () => import('./modules/trash/trash.module').then(m => m.TrashModule),
|
||||
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
|
||||
data: {
|
||||
routeGuards: [IqserAuthGuard, RedRoleGuard, DossiersGuard, TrashGuard],
|
||||
dossiersService: ACTIVE_DOSSIERS_SERVICE,
|
||||
permissions: {
|
||||
allow: [ROLES.dossiers.read, ROLES.files.readStatus],
|
||||
redirectTo: '/auth-error',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
path: `:${DOSSIER_TEMPLATE_ID}`,
|
||||
children: dossierTemplateIdRoutes,
|
||||
canActivate: [CompositeRouteGuard, IqserPermissionsGuard],
|
||||
data: {
|
||||
routeGuards: [IqserAuthGuard, RedRoleGuard, DossierTemplatesGuard, DashboardGuard, DossierTemplateExistsGuard],
|
||||
permissions: {
|
||||
allow: [
|
||||
ROLES.any,
|
||||
ROLES.templates.read,
|
||||
ROLES.fileAttributes.readConfig,
|
||||
ROLES.watermarks.read,
|
||||
ROLES.dictionaryTypes.read,
|
||||
ROLES.colors.read,
|
||||
ROLES.states.read,
|
||||
ROLES.notifications.read,
|
||||
ROLES.dossiers.read,
|
||||
'RED_USER',
|
||||
],
|
||||
redirectTo: {
|
||||
[ROLES.any]: '/auth-error',
|
||||
RED_USER: '/main/admin',
|
||||
[DEFAULT_REDIRECT_KEY]: '/',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
resolve: {
|
||||
whateverThisMainRouteNeeds: mainResolver,
|
||||
},
|
||||
children: mainRoutes,
|
||||
},
|
||||
{
|
||||
path: ':tenant/auth-error',
|
||||
|
||||
@ -1,17 +1,5 @@
|
||||
<router-outlet></router-outlet>
|
||||
|
||||
<redaction-pdf-viewer [style.visibility]="(documentViewer.loaded$ | async) ? 'visible' : 'hidden'"></redaction-pdf-viewer>
|
||||
|
||||
<iqser-full-page-loading-indicator></iqser-full-page-loading-indicator>
|
||||
<iqser-connection-status></iqser-connection-status>
|
||||
<iqser-full-page-error></iqser-full-page-error>
|
||||
|
||||
<iqser-skeleton [templates]="{ dashboard: dashboardSkeleton, dossier: dossierSkeleton }"></iqser-skeleton>
|
||||
|
||||
<ng-template #dashboardSkeleton>
|
||||
<redaction-dashboard-skeleton></redaction-dashboard-skeleton>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #dossierSkeleton>
|
||||
<redaction-dossier-skeleton></redaction-dossier-skeleton>
|
||||
</ng-template>
|
||||
|
||||
@ -1,11 +1,8 @@
|
||||
import { Component, Inject, OnDestroy, Renderer2, ViewContainerRef } from '@angular/core';
|
||||
import { RouterHistoryService } from '@services/router-history.service';
|
||||
import { REDDocumentViewer } from './modules/pdf-viewer/services/document-viewer.service';
|
||||
import { DossiersChangesService } from '@services/dossiers/dossier-changes.service';
|
||||
import { DOCUMENT } from '@angular/common';
|
||||
import { UserPreferenceService } from '@users/user-preference.service';
|
||||
import { getConfig, IqserPermissionsService } from '@iqser/common-ui';
|
||||
import { ROLES } from '@users/roles';
|
||||
import { getConfig } from '@iqser/common-ui';
|
||||
import { AppConfig } from '@red/domain';
|
||||
import { ActivatedRoute, ParamMap, Router } from '@angular/router';
|
||||
import { Subscription } from 'rxjs';
|
||||
@ -38,22 +35,13 @@ export class AppComponent implements OnDestroy {
|
||||
/** RouterHistoryService needs to be injected for last dossiers screen to be updated on first app load */
|
||||
private readonly _routerHistoryService: RouterHistoryService,
|
||||
userPreferenceService: UserPreferenceService,
|
||||
readonly documentViewer: REDDocumentViewer,
|
||||
dossierChangesService: DossiersChangesService,
|
||||
@Inject(DOCUMENT) document: Document,
|
||||
renderer: Renderer2,
|
||||
permissionsService: IqserPermissionsService,
|
||||
private readonly _router: Router,
|
||||
route: ActivatedRoute,
|
||||
) {
|
||||
renderer.addClass(document.body, userPreferenceService.getTheme());
|
||||
loadCustomTheme();
|
||||
// TODO: Find a better place to initialize dossiers refresh
|
||||
if (permissionsService.has(ROLES.dossiers.read)) {
|
||||
const refreshSub = dossierChangesService.initializeRefresh().subscribe();
|
||||
this.#subscription.add(refreshSub);
|
||||
}
|
||||
|
||||
const sub = route.queryParamMap.subscribe(queryParams => this.#navigate(queryParams));
|
||||
this.#subscription.add(sub);
|
||||
}
|
||||
|
||||
@ -1,12 +1,11 @@
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
import { APP_INITIALIZER, ErrorHandler, NgModule } from '@angular/core';
|
||||
import { ENVIRONMENT_INITIALIZER, ErrorHandler, inject, NgModule } from '@angular/core';
|
||||
import { AppComponent } from './app.component';
|
||||
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
|
||||
import { HTTP_INTERCEPTORS } from '@angular/common/http';
|
||||
import { BaseScreenComponent } from '@components/base-screen/base-screen.component';
|
||||
import { MissingTranslationHandler } from '@ngx-translate/core';
|
||||
import {
|
||||
BASE_HREF,
|
||||
CachingModule,
|
||||
CircleButtonComponent,
|
||||
CommonUiModule,
|
||||
@ -19,10 +18,8 @@ import {
|
||||
IqserHelpModeModule,
|
||||
IqserListingModule,
|
||||
IqserLoadingModule,
|
||||
IqserPermissionsService,
|
||||
IqserTranslateModule,
|
||||
IqserUsersModule,
|
||||
KeycloakStatusService,
|
||||
LanguageService,
|
||||
LogoComponent,
|
||||
MAX_RETRIES_ON_SERVER_ERROR,
|
||||
@ -48,53 +45,43 @@ import { FileUploadDownloadModule } from '@upload-download/file-upload-download.
|
||||
import { DatePipe as BaseDatePipe } from '@angular/common';
|
||||
import { ACTIVE_DOSSIERS_SERVICE, ARCHIVED_DOSSIERS_SERVICE } from './tokens';
|
||||
import { MonacoEditorModule } from '@materia-ui/ngx-monaco-editor';
|
||||
import { GlobalErrorHandler } from '@utils/global-error-handler.service';
|
||||
import { GlobalErrorHandler } from '@services/global-error-handler.service';
|
||||
import { REDMissingTranslationHandler } from '@utils/missing-translations-handler';
|
||||
import { configurationInitializer } from '@utils/configuration.initializer';
|
||||
import { ConfigService } from '@services/config.service';
|
||||
import { SpotlightSearchComponent } from '@components/spotlight-search/spotlight-search.component';
|
||||
import { DatePipe } from '@shared/pipes/date.pipe';
|
||||
import * as links from '../assets/help-mode/links.json';
|
||||
import { KeycloakService } from 'keycloak-angular';
|
||||
import { GeneralSettingsService } from '@services/general-settings.service';
|
||||
import { BreadcrumbsComponent } from '@components/breadcrumbs/breadcrumbs.component';
|
||||
import { UserPreferenceService } from '@users/user-preference.service';
|
||||
import { UserService } from '@users/user.service';
|
||||
import { ActiveDossiersService } from '@services/dossiers/active-dossiers.service';
|
||||
import { ArchivedDossiersService } from '@services/dossiers/archived-dossiers.service';
|
||||
import { FeaturesService } from '@services/features.service';
|
||||
import { MAT_TOOLTIP_DEFAULT_OPTIONS } from '@angular/material/tooltip';
|
||||
import { LoggerModule, NgxLoggerLevel, TOKEN_LOGGER_CONFIG, TOKEN_LOGGER_RULES_SERVICE } from 'ngx-logger';
|
||||
import { LoggerRulesService } from '@services/logger-rules.service';
|
||||
import { AppConfig, ILoggerConfig } from '@red/domain';
|
||||
import { SystemPreferencesService } from '@services/system-preferences.service';
|
||||
import { PdfViewerModule } from './modules/pdf-viewer/pdf-viewer.module';
|
||||
import { LicenseService } from '@services/license.service';
|
||||
import { UI_CACHES } from '@utils/constants';
|
||||
import { RedRoleGuard } from '@users/red-role.guard';
|
||||
import { SkeletonTopBarComponent } from '@components/skeleton/skeleton-top-bar/skeleton-top-bar.component';
|
||||
import { DossierSkeletonComponent } from '@components/skeleton/dossier-skeleton/dossier-skeleton.component';
|
||||
import { SkeletonStatsComponent } from '@components/skeleton/skeleton-stats/skeleton-stats.component';
|
||||
|
||||
const screens = [BaseScreenComponent, DownloadsListScreenComponent];
|
||||
|
||||
const components = [
|
||||
AppComponent,
|
||||
AuthErrorComponent,
|
||||
NotificationsComponent,
|
||||
SpotlightSearchComponent,
|
||||
BreadcrumbsComponent,
|
||||
DashboardSkeletonComponent,
|
||||
DossierSkeletonComponent,
|
||||
SkeletonTopBarComponent,
|
||||
SkeletonStatsComponent,
|
||||
|
||||
...screens,
|
||||
];
|
||||
|
||||
export const appModuleFactory = (config: AppConfig) => {
|
||||
@NgModule({
|
||||
declarations: [...components],
|
||||
declarations: [
|
||||
AppComponent,
|
||||
AuthErrorComponent,
|
||||
NotificationsComponent,
|
||||
SpotlightSearchComponent,
|
||||
BreadcrumbsComponent,
|
||||
DashboardSkeletonComponent,
|
||||
DossierSkeletonComponent,
|
||||
SkeletonTopBarComponent,
|
||||
SkeletonStatsComponent,
|
||||
BaseScreenComponent,
|
||||
DownloadsListScreenComponent,
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
BrowserAnimationsModule,
|
||||
@ -187,23 +174,12 @@ export const appModuleFactory = (config: AppConfig) => {
|
||||
useClass: GlobalErrorHandler,
|
||||
},
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
provide: ENVIRONMENT_INITIALIZER,
|
||||
multi: true,
|
||||
useFactory: configurationInitializer,
|
||||
deps: [
|
||||
BASE_HREF,
|
||||
KeycloakStatusService,
|
||||
KeycloakService,
|
||||
ConfigService,
|
||||
SystemPreferencesService,
|
||||
FeaturesService,
|
||||
GeneralSettingsService,
|
||||
LanguageService,
|
||||
UserService,
|
||||
UserPreferenceService,
|
||||
LicenseService,
|
||||
IqserPermissionsService,
|
||||
],
|
||||
useValue: async () => {
|
||||
const languageService = inject(LanguageService);
|
||||
return languageService.setInitialLanguage();
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: MissingTranslationHandler,
|
||||
|
||||
@ -43,10 +43,6 @@
|
||||
</a>
|
||||
</ng-container>
|
||||
|
||||
<button (click)="selectTenant()" *ngIf="tenantContext.hasMultipleTenants" id="select-tenant" mat-menu-item>
|
||||
<span translate="top-bar.navigation-items.my-account.children.select-tenant"> </span>
|
||||
</button>
|
||||
|
||||
<button (click)="userService.logout()" id="logout" mat-menu-item>
|
||||
<mat-icon svgIcon="iqser:logout"></mat-icon>
|
||||
<span translate="top-bar.navigation-items.my-account.children.logout"> </span>
|
||||
@ -59,3 +55,15 @@
|
||||
<div *ngIf="userPreferenceService.areDevFeaturesEnabled" class="dev-mode" translate="dev-mode"></div>
|
||||
|
||||
<router-outlet></router-outlet>
|
||||
|
||||
<redaction-pdf-viewer [style.visibility]="(documentViewer.loaded$ | async) ? 'visible' : 'hidden'"></redaction-pdf-viewer>
|
||||
|
||||
<iqser-skeleton [templates]="{ dashboard: dashboardSkeleton, dossier: dossierSkeleton }"></iqser-skeleton>
|
||||
|
||||
<ng-template #dashboardSkeleton>
|
||||
<redaction-dashboard-skeleton></redaction-dashboard-skeleton>
|
||||
</ng-template>
|
||||
|
||||
<ng-template #dossierSkeleton>
|
||||
<redaction-dossier-skeleton></redaction-dossier-skeleton>
|
||||
</ng-template>
|
||||
|
||||
@ -7,11 +7,12 @@ import { TranslateService } from '@ngx-translate/core';
|
||||
import { SpotlightSearchAction } from '@components/spotlight-search/spotlight-search-action';
|
||||
import { marker as _ } from '@biesbjerg/ngx-translate-extract-marker';
|
||||
import { filter, map, startWith } from 'rxjs/operators';
|
||||
import { BASE_HREF, IqserPermissionsService, shareDistinctLast, TenantContext, TenantContextHolder } from '@iqser/common-ui';
|
||||
import { BASE_HREF, IqserPermissionsService, shareDistinctLast, TenantContextHolder, TenantsService } from '@iqser/common-ui';
|
||||
import { BreadcrumbsService } from '@services/breadcrumbs.service';
|
||||
import { FeaturesService } from '@services/features.service';
|
||||
import { ARCHIVE_ROUTE, DOSSIERS_ARCHIVE, DOSSIERS_ROUTE } from '@red/domain';
|
||||
import { ROLES } from '@users/roles';
|
||||
import { REDDocumentViewer } from '../../modules/pdf-viewer/services/document-viewer.service';
|
||||
|
||||
interface MenuItem {
|
||||
readonly id: string;
|
||||
@ -31,8 +32,8 @@ const isSearchScreen: (url: string) => boolean = url => url.includes('/search');
|
||||
})
|
||||
export class BaseScreenComponent {
|
||||
readonly roles = ROLES;
|
||||
private readonly _baseHref = inject(BASE_HREF);
|
||||
readonly tenantContext = inject(TenantContext);
|
||||
readonly tenantContext = inject(TenantsService);
|
||||
readonly documentViewer = inject(REDDocumentViewer);
|
||||
readonly currentUser = this.userService.currentUser;
|
||||
readonly userMenuItems: readonly MenuItem[] = [
|
||||
{
|
||||
@ -59,6 +60,12 @@ export class BaseScreenComponent {
|
||||
routerLink: '/main/trash',
|
||||
show: this.currentUser.isUser && this.permissionsService.has([ROLES.dossiers.read, ROLES.files.readStatus]),
|
||||
},
|
||||
{
|
||||
id: 'select-tenant',
|
||||
name: _('top-bar.navigation-items.my-account.children.select-tenant'),
|
||||
action: () => this.selectTenant(),
|
||||
show: this.tenantContext.hasMultipleTenants,
|
||||
},
|
||||
];
|
||||
readonly searchActions: readonly SpotlightSearchAction[] = [
|
||||
{
|
||||
@ -79,6 +86,7 @@ export class BaseScreenComponent {
|
||||
action: (query): void => this._search(query, []),
|
||||
},
|
||||
];
|
||||
private readonly _baseHref = inject(BASE_HREF);
|
||||
private readonly _navigationStart$ = this._router.events.pipe(
|
||||
filter(isNavigationStart),
|
||||
map((event: NavigationStart) => event.url),
|
||||
@ -113,6 +121,10 @@ export class BaseScreenComponent {
|
||||
return item.name;
|
||||
}
|
||||
|
||||
selectTenant() {
|
||||
window.open(window.location.origin + this._baseHref, '_blank');
|
||||
}
|
||||
|
||||
private _search(query: string, dossierIds: string[], onlyActive = false) {
|
||||
const queryParams = { query, dossierIds: dossierIds.join(','), onlyActive };
|
||||
this._router.navigate([`/${this._tenantContextHolder.currentTenant}/main/search`], { queryParams }).then();
|
||||
@ -126,8 +138,4 @@ export class BaseScreenComponent {
|
||||
const dossierId = routerLink[2];
|
||||
return this._search(query, [dossierId]);
|
||||
}
|
||||
|
||||
selectTenant() {
|
||||
window.open(window.location.origin + this._baseHref, '_blank');
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,7 +2,6 @@ import { Component } from '@angular/core';
|
||||
import { UntypedFormBuilder, UntypedFormGroup, Validators } from '@angular/forms';
|
||||
import { SystemPreferences } from '@red/domain';
|
||||
import { BaseFormComponent, IqserPermissionsService, KeysOf, LoadingService } from '@iqser/common-ui';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { SystemPreferencesService } from '@services/system-preferences.service';
|
||||
import { systemPreferencesTranslations } from '@translations/system-preferences-translations';
|
||||
import { ROLES } from '@users/roles';
|
||||
@ -38,7 +37,7 @@ export class SystemPreferencesFormComponent extends BaseFormComponent {
|
||||
|
||||
async save(): Promise<void> {
|
||||
this._loadingService.start();
|
||||
await firstValueFrom(this._systemPreferencesService.update(this.form.getRawValue()));
|
||||
await this._systemPreferencesService.update(this.form.getRawValue());
|
||||
this._loadData();
|
||||
this._loadingService.stop();
|
||||
}
|
||||
|
||||
@ -1,18 +1,19 @@
|
||||
import { GenericService, LAST_CHECKED_OFFSET, List, QueryParam, ROOT_CHANGES_KEY } from '@iqser/common-ui';
|
||||
import { Dossier, DossierStats, IDossierChanges } from '@red/domain';
|
||||
import { forkJoin, Observable, of, throwError, timer } from 'rxjs';
|
||||
import { forkJoin, Observable, of, Subscription, throwError, timer } from 'rxjs';
|
||||
import { catchError, filter, map, switchMap, take, tap } from 'rxjs/operators';
|
||||
import { HttpErrorResponse, HttpStatusCode } from '@angular/common/http';
|
||||
import { NGXLogger } from 'ngx-logger';
|
||||
import { ActiveDossiersService } from './active-dossiers.service';
|
||||
import { ArchivedDossiersService } from './archived-dossiers.service';
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { inject, Injectable, OnDestroy } from '@angular/core';
|
||||
import { DashboardStatsService } from '../dossier-templates/dashboard-stats.service';
|
||||
import { CHANGED_CHECK_INTERVAL } from '@utils/constants';
|
||||
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class DossiersChangesService extends GenericService<Dossier> {
|
||||
export class DossiersChangesService extends GenericService<Dossier> implements OnDestroy {
|
||||
protected readonly _defaultModelPath = 'dossier';
|
||||
readonly #subscription = new Subscription();
|
||||
|
||||
readonly #activeDossiersService = inject(ActiveDossiersService);
|
||||
readonly #archivedDossiersService = inject(ArchivedDossiersService);
|
||||
@ -31,7 +32,7 @@ export class DossiersChangesService extends GenericService<Dossier> {
|
||||
});
|
||||
|
||||
const load = (changes: IDossierChanges) =>
|
||||
changes.map(change => this._load(change.dossierId).pipe(removeIfNotFound(change.dossierId)));
|
||||
changes.map(change => this.#load(change.dossierId).pipe(removeIfNotFound(change.dossierId)));
|
||||
|
||||
return this.hasChangesDetails$().pipe(
|
||||
tap(changes => this.#logger.info('[CHANGES] ', changes)),
|
||||
@ -50,17 +51,23 @@ export class DossiersChangesService extends GenericService<Dossier> {
|
||||
);
|
||||
}
|
||||
|
||||
initializeRefresh() {
|
||||
return timer(CHANGED_CHECK_INTERVAL, CHANGED_CHECK_INTERVAL).pipe(
|
||||
initialize() {
|
||||
this.#logger.info('[DOSSIERS_CHANGES] Initialize timer');
|
||||
const subscription = timer(CHANGED_CHECK_INTERVAL, CHANGED_CHECK_INTERVAL).pipe(
|
||||
switchMap(() => this.loadOnlyChanged()),
|
||||
tap(changes => {
|
||||
this.#activeDossiersService.emitFileChanges(changes);
|
||||
this.#archivedDossiersService.emitFileChanges(changes);
|
||||
}),
|
||||
);
|
||||
this.#subscription.add(subscription.subscribe());
|
||||
}
|
||||
|
||||
private _load(id: string): Observable<DossierStats[]> {
|
||||
ngOnDestroy() {
|
||||
this.#subscription.unsubscribe();
|
||||
}
|
||||
|
||||
#load(id: string): Observable<DossierStats[]> {
|
||||
const queryParams: List<QueryParam> = [{ key: 'includeArchived', value: true }];
|
||||
return super._getOne([id], this._defaultModelPath, queryParams).pipe(
|
||||
map(entity => new Dossier(entity)),
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { ConfigService } from './config.service';
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import featuresJson from '../../assets/features/features.json';
|
||||
import { getConfig } from '@iqser/common-ui';
|
||||
import { AppConfig } from '@red/domain';
|
||||
import { NGXLogger } from 'ngx-logger';
|
||||
|
||||
function transform(version: string): number {
|
||||
const [major, minor, patch, release] = version.split(/[.-]/).map(x => parseInt(x, 10));
|
||||
@ -11,19 +13,20 @@ function transform(version: string): number {
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class FeaturesService {
|
||||
private _features = new Map<string, boolean>();
|
||||
|
||||
constructor(private readonly _configService: ConfigService) {}
|
||||
readonly #features = new Map<string, boolean>();
|
||||
readonly #config = getConfig<AppConfig>();
|
||||
readonly #logger = inject(NGXLogger);
|
||||
|
||||
isEnabled(feature: string): boolean {
|
||||
// If feature is not defined in config object return true
|
||||
return this._features.get(feature) !== false;
|
||||
return this.#features.get(feature) !== false;
|
||||
}
|
||||
|
||||
loadConfig() {
|
||||
const BACKEND_APP_VERSION = transform(this._configService.values.BACKEND_APP_VERSION as string);
|
||||
const BACKEND_APP_VERSION = transform(this.#config.BACKEND_APP_VERSION as string);
|
||||
featuresJson.features.forEach(feature => {
|
||||
this._features.set(feature.name, transform(feature.minVersion) <= BACKEND_APP_VERSION);
|
||||
this.#features.set(feature.name, transform(feature.minVersion) <= BACKEND_APP_VERSION);
|
||||
});
|
||||
this.#logger.info('[INFO] Features loaded: ', this.#features);
|
||||
}
|
||||
}
|
||||
|
||||
@ -129,7 +129,7 @@ export class LicenseService extends GenericService<ILicenseReport> {
|
||||
}
|
||||
|
||||
@Validate()
|
||||
getReport$(@RequiredParam() body: ILicenseReportRequest, limit?: number, offset?: number) {
|
||||
getReport(@RequiredParam() body: ILicenseReportRequest, limit?: number, offset?: number) {
|
||||
const queryParams: QueryParam[] = [];
|
||||
if (limit) {
|
||||
queryParams.push({ key: 'limit', value: limit });
|
||||
@ -139,19 +139,14 @@ export class LicenseService extends GenericService<ILicenseReport> {
|
||||
queryParams.push({ key: 'offset', value: offset });
|
||||
}
|
||||
|
||||
return this._post(body, `${this._defaultModelPath}/license`, queryParams);
|
||||
return firstValueFrom(this._post(body, `${this._defaultModelPath}/license`, queryParams));
|
||||
}
|
||||
|
||||
getReport(body: ILicenseReportRequest, limit?: number, offset?: number) {
|
||||
return firstValueFrom(this.getReport$(body, limit, offset));
|
||||
}
|
||||
|
||||
loadLicense() {
|
||||
return this._http.get<ILicenses>('/license').pipe(
|
||||
catchError(() => of(defaultOnError)),
|
||||
tap(licenses => this.#licenseData$.next(licenses)),
|
||||
tap(() => this.setDefaultSelectedLicense()),
|
||||
);
|
||||
async loadLicenses() {
|
||||
const licenses$ = this._http.get<ILicenses>('/license').pipe(catchError(() => of(defaultOnError)));
|
||||
const licenses = await firstValueFrom(licenses$);
|
||||
this.#licenseData$.next(licenses);
|
||||
this.setDefaultSelectedLicense();
|
||||
}
|
||||
|
||||
setSelectedLicense($event: ILicense) {
|
||||
|
||||
@ -1,26 +1,25 @@
|
||||
import { Injectable } from '@angular/core';
|
||||
import { inject, Injectable } from '@angular/core';
|
||||
import { GenericService } from '@iqser/common-ui';
|
||||
import { SystemPreferences } from '@red/domain';
|
||||
import { Observable, switchMap } from 'rxjs';
|
||||
import { tap } from 'rxjs/operators';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { NGXLogger } from 'ngx-logger';
|
||||
|
||||
@Injectable({
|
||||
providedIn: 'root',
|
||||
})
|
||||
export class SystemPreferencesService extends GenericService<SystemPreferences> {
|
||||
protected readonly _defaultModelPath = 'app-config';
|
||||
values: SystemPreferences;
|
||||
protected readonly _defaultModelPath = 'app-config';
|
||||
readonly #logger = inject(NGXLogger);
|
||||
|
||||
loadPreferences(): Observable<SystemPreferences> {
|
||||
return this.get<SystemPreferences>().pipe(
|
||||
tap((config: SystemPreferences) => {
|
||||
console.log('[REDACTION] Loaded config: ', config);
|
||||
this.values = config;
|
||||
}),
|
||||
);
|
||||
async loadPreferences() {
|
||||
this.values = await firstValueFrom(this.get<SystemPreferences>());
|
||||
this.#logger.info('[REDACTION] Loaded config: ', this.values);
|
||||
return this.values;
|
||||
}
|
||||
|
||||
update(value: SystemPreferences): Observable<SystemPreferences> {
|
||||
return this._post(value).pipe(switchMap(() => this.loadPreferences()));
|
||||
async update(value: SystemPreferences) {
|
||||
await firstValueFrom(this._post(value));
|
||||
return await this.loadPreferences();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,63 +0,0 @@
|
||||
import { catchError, filter, switchMap, tap } from 'rxjs/operators';
|
||||
import { ConfigService } from '@services/config.service';
|
||||
import { firstValueFrom, map, of, throwError } from 'rxjs';
|
||||
import { KeycloakService } from 'keycloak-angular';
|
||||
import { GeneralSettingsService } from '@services/general-settings.service';
|
||||
import { IqserPermissionsService, KeycloakStatus, KeycloakStatusService, LanguageService } from '@iqser/common-ui';
|
||||
import { UserPreferenceService } from '@users/user-preference.service';
|
||||
import { UserService } from '@users/user.service';
|
||||
import { FeaturesService } from '@services/features.service';
|
||||
import { SystemPreferencesService } from '@services/system-preferences.service';
|
||||
import { LicenseService } from '@services/license.service';
|
||||
import { ROLES } from '@users/roles';
|
||||
|
||||
function lastDossierTemplateRedirect(baseHref: string, userPreferenceService: UserPreferenceService) {
|
||||
const url = window.location.href.split('/').filter(s => s.length > 0);
|
||||
const lastDossierTemplate = userPreferenceService.getLastDossierTemplate();
|
||||
if (lastDossierTemplate && [baseHref, 'main'].includes(url[url.length - 1])) {
|
||||
window.location.href = `/${baseHref}/main/${lastDossierTemplate}`;
|
||||
}
|
||||
}
|
||||
|
||||
export function configurationInitializer(
|
||||
baseHref: string,
|
||||
keycloakStatusService: KeycloakStatusService,
|
||||
keycloakService: KeycloakService,
|
||||
configService: ConfigService,
|
||||
systemPreferencesService: SystemPreferencesService,
|
||||
featuresService: FeaturesService,
|
||||
generalSettingsService: GeneralSettingsService,
|
||||
languageService: LanguageService,
|
||||
userService: UserService,
|
||||
userPreferenceService: UserPreferenceService,
|
||||
licenseService: LicenseService,
|
||||
permissionsService: IqserPermissionsService,
|
||||
) {
|
||||
console.log('BASE HREF: ', baseHref);
|
||||
const setup = keycloakStatusService.keycloakStatus$.pipe(
|
||||
filter(event => event === KeycloakStatus.READY || event === KeycloakStatus.NOT_ACTIVE),
|
||||
map(() => featuresService.loadConfig()),
|
||||
switchMap(() => keycloakService.isLoggedIn()),
|
||||
switchMap(loggedIn => (!loggedIn ? throwError(() => 'Not Logged In') : of({}))),
|
||||
switchMap(() => userService.initialize()),
|
||||
switchMap(() => (!permissionsService.has(ROLES.any) ? throwError(() => 'User has no red roles') : of({}))),
|
||||
switchMap(() => generalSettingsService.getGeneralConfigurations()),
|
||||
tap(configuration => configService.updateDisplayName(configuration.displayName)),
|
||||
switchMap(() => systemPreferencesService.loadPreferences()),
|
||||
switchMap(() => userPreferenceService.reload()),
|
||||
catchError((e: unknown) => {
|
||||
console.log('[Redaction] Initialization error:', e);
|
||||
if (keycloakStatusService.keycloakStatus$.value === KeycloakStatus.READY) {
|
||||
return of(userService.redirectToLogin());
|
||||
}
|
||||
|
||||
return of({});
|
||||
}),
|
||||
tap(() => {
|
||||
lastDossierTemplateRedirect(baseHref.replace('/', ''), userPreferenceService);
|
||||
}),
|
||||
switchMap(() => languageService.setInitialLanguage()),
|
||||
switchMap(() => licenseService.loadLicense()),
|
||||
);
|
||||
return () => firstValueFrom(setup);
|
||||
}
|
||||
@ -1,13 +1,11 @@
|
||||
export * from './sorters/redaction-filter-sorter';
|
||||
export * from './sorters/super-type-sorter';
|
||||
|
||||
export * from './configuration.initializer';
|
||||
export * from './date-inputs-utils';
|
||||
export * from './file-download-utils';
|
||||
export * from './file-drop-utils';
|
||||
export * from './filter-utils';
|
||||
export * from './functions';
|
||||
export * from './global-error-handler.service';
|
||||
export * from './missing-translations-handler';
|
||||
export * from './page-stamper';
|
||||
export * from './router-links';
|
||||
|
||||
57
apps/red-ui/src/app/utils/main.resolver.ts
Normal file
57
apps/red-ui/src/app/utils/main.resolver.ts
Normal file
@ -0,0 +1,57 @@
|
||||
import { inject } from '@angular/core';
|
||||
import { NGXLogger } from 'ngx-logger';
|
||||
import { ConfigService } from '@services/config.service';
|
||||
import { UserService } from '@users/user.service';
|
||||
import { SystemPreferencesService } from '@services/system-preferences.service';
|
||||
import { UserPreferenceService } from '@users/user-preference.service';
|
||||
import { LicenseService } from '@services/license.service';
|
||||
import { BASE_HREF, IqserPermissionsService, LoadingService } from '@iqser/common-ui';
|
||||
import { FeaturesService } from '@services/features.service';
|
||||
import { GeneralSettingsService } from '@services/general-settings.service';
|
||||
import { tap } from 'rxjs/operators';
|
||||
import { firstValueFrom } from 'rxjs';
|
||||
import { ROLES } from '@users/roles';
|
||||
import { DossiersChangesService } from '@services/dossiers/dossier-changes.service';
|
||||
import { ResolveFn } from '@angular/router';
|
||||
|
||||
function lastDossierTemplateRedirect(baseHref: string, userPreferenceService: UserPreferenceService) {
|
||||
const url = window.location.href.split('/').filter(s => s.length > 0);
|
||||
const lastDossierTemplate = userPreferenceService.getLastDossierTemplate();
|
||||
if (lastDossierTemplate && [baseHref, 'main'].includes(url[url.length - 1])) {
|
||||
window.location.href = `/${baseHref}/main/${lastDossierTemplate}`;
|
||||
}
|
||||
}
|
||||
|
||||
export const mainResolver: ResolveFn<void> = async () => {
|
||||
const logger = inject(NGXLogger);
|
||||
logger.info('[ROUTES] Main resolver started...');
|
||||
|
||||
inject(FeaturesService).loadConfig();
|
||||
if (inject(IqserPermissionsService).has(ROLES.dossiers.read)) {
|
||||
inject(DossiersChangesService).initialize();
|
||||
}
|
||||
|
||||
const configService = inject(ConfigService);
|
||||
const userService = inject(UserService);
|
||||
const systemPreferencesService = inject(SystemPreferencesService);
|
||||
const userPreferenceService = inject(UserPreferenceService);
|
||||
const licenseService = inject(LicenseService);
|
||||
const loadingService = inject(LoadingService);
|
||||
const baseHref = inject(BASE_HREF);
|
||||
|
||||
const generalConfig$ = inject(GeneralSettingsService).getGeneralConfigurations();
|
||||
const updatedDisplayName$ = generalConfig$.pipe(tap(config => configService.updateDisplayName(config.displayName)));
|
||||
|
||||
await Promise.all([
|
||||
userService.initialize(),
|
||||
systemPreferencesService.loadPreferences(),
|
||||
userPreferenceService.reload(),
|
||||
firstValueFrom(updatedDisplayName$),
|
||||
licenseService.loadLicenses(),
|
||||
]);
|
||||
|
||||
lastDossierTemplateRedirect(baseHref, userPreferenceService);
|
||||
loadingService.stop();
|
||||
|
||||
logger.info('[ROUTES] Main resolver finished!');
|
||||
};
|
||||
@ -1,7 +1,7 @@
|
||||
{
|
||||
"ADMIN_CONTACT_NAME": null,
|
||||
"ADMIN_CONTACT_URL": null,
|
||||
"API_URL": "https://dom2.iqser.cloud/redaction-gateway-v1",
|
||||
"API_URL": "https://dan.iqser.cloud/redaction-gateway-v1",
|
||||
"APP_NAME": "RedactManager",
|
||||
"AUTO_READ_TIME": 3,
|
||||
"BACKEND_APP_VERSION": "4.4.40",
|
||||
@ -11,7 +11,7 @@
|
||||
"MAX_RETRIES_ON_SERVER_ERROR": 3,
|
||||
"OAUTH_CLIENT_ID": "redaction",
|
||||
"OAUTH_IDP_HINT": null,
|
||||
"OAUTH_URL": "https://dom2.iqser.cloud/auth",
|
||||
"OAUTH_URL": "https://dan.iqser.cloud/auth",
|
||||
"RECENT_PERIOD_IN_HOURS": 24,
|
||||
"SELECTION_MODE": "structural",
|
||||
"MANUAL_BASE_URL": "https://docs.redactmanager.com/preview",
|
||||
|
||||
@ -17,15 +17,14 @@ async function bootstrap(appConfig: AppConfig) {
|
||||
console.log('Started with local config: ', config);
|
||||
const appModule = appModuleFactory(config);
|
||||
|
||||
const ngModuleRef = platformBrowserDynamic().bootstrapModule(appModule, { ngZoneEventCoalescing: true });
|
||||
return ngModuleRef.then(moduleRef => {
|
||||
if (!environment.production) {
|
||||
const applicationRef = moduleRef.injector.get(ApplicationRef);
|
||||
const componentRef = applicationRef.components[0];
|
||||
// allows to run `ng.profiler.timeChangeDetection();`
|
||||
enableDebugTools(componentRef);
|
||||
}
|
||||
});
|
||||
const ngModuleRef = await platformBrowserDynamic().bootstrapModule(appModule, { ngZoneEventCoalescing: true });
|
||||
|
||||
if (!environment.production) {
|
||||
const applicationRef = ngModuleRef.injector.get(ApplicationRef);
|
||||
const componentRef = applicationRef.components[0];
|
||||
// allows to run `ng.profiler.timeChangeDetection();`
|
||||
enableDebugTools(componentRef);
|
||||
}
|
||||
}
|
||||
|
||||
const configPromise = fetch('/ui/assets/config/config.json').then(resp => resp.json());
|
||||
|
||||
@ -1 +1 @@
|
||||
Subproject commit 6c63f851cf09496d425e9af75f18902557021c8e
|
||||
Subproject commit 03084293b2f5e3b96fe44f75252ae1f75ce7039f
|
||||
Loading…
x
Reference in New Issue
Block a user