@sinequa/atomic-angular 1.5.1 → 1.5.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +28 -0
- package/fesm2022/sinequa-atomic-angular.mjs +406 -205
- package/fesm2022/sinequa-atomic-angular.mjs.map +1 -1
- package/index.d.ts +581 -79
- package/package.json +1 -1
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import * as i0 from '@angular/core';
|
|
2
|
-
import { Injectable, inject, HostBinding, Component, Pipe, InjectionToken, computed, ChangeDetectorRef, DestroyRef, LOCALE_ID, Inject, Optional, input, output, signal, effect, assertInInjectionContext, runInInjectionContext, Injector, EventEmitter, Directive, viewChild, ElementRef, afterNextRender, untracked, linkedSignal, model, TemplateRef, HostListener, Renderer2, contentChildren, contentChild, booleanAttribute, resource, ChangeDetectionStrategy, ViewContainerRef, numberAttribute, viewChildren, afterRenderEffect, afterEveryRender } from '@angular/core';
|
|
2
|
+
import { Injectable, inject, HostBinding, Component, Pipe, InjectionToken, computed, ChangeDetectorRef, DestroyRef, LOCALE_ID, Inject, Optional, input, output, signal, effect, assertInInjectionContext, runInInjectionContext, EnvironmentInjector, Injector, EventEmitter, Directive, viewChild, ElementRef, afterNextRender, untracked, linkedSignal, model, TemplateRef, HostListener, Renderer2, contentChildren, contentChild, booleanAttribute, resource, ChangeDetectionStrategy, ViewContainerRef, numberAttribute, viewChildren, afterRenderEffect, afterEveryRender } from '@angular/core';
|
|
3
3
|
import { BehaviorSubject, Subscription, firstValueFrom, catchError, map, Subject, of, tap, EMPTY, throwError, filter, shareReplay, switchMap, from, fromEvent, debounceTime } from 'rxjs';
|
|
4
4
|
import { TranslocoService, TranslocoPipe, provideTranslocoScope } from '@jsverse/transloco';
|
|
5
|
-
import { DropdownComponent, DropdownContentComponent, InputComponent, ButtonComponent, cn, FaIconComponent, EllipsisIcon, ChevronRightIcon, MenuComponent, MenuContentComponent, MenuItemComponent, LinkComponent, BadgeComponent, DialogComponent, DialogHeaderComponent, DialogTitleComponent, DialogContentComponent, DialogFooterComponent, ListItemComponent, SquareCheckIcon, SquareMinusIcon, SquareIcon, SwitchComponent, SelectOptionDirective, DialogService, XMarkIcon, InboxIcon, SparklesIcon, FileOutputIcon, TabsComponent, TabsListComponent, TabComponent, SidebarMenuComponent, SidebarMenuItemComponent, SidebarMenuButtonComponent, TooltipDirective, FrownIcon, ChevronLeftIcon, ChevronsLeftIcon, ChevronsRightIcon, ArrowUpAzIcon, ArrowDownZaIcon, ArrowUpRightFromSquareIcon, ToggleRightIcon, ToggleLeftIcon, Separator, SheetCloseDirective, ArrowLeftIcon, SheetService, DateRangePickerDirective, DatepickerDirective, FilterIcon, FilterXIcon, IconButtonComponent, HighlighterIcon, TagsIcon, SpinnerIcon, MagnifyingGlassIcon, LoadingCircleIcon, CircleCheckIcon, PopoverComponent, BellIcon, TrashIcon, BarsIcon,
|
|
5
|
+
import { DropdownComponent, DropdownContentComponent, InputComponent, ButtonComponent, cn, FaIconComponent, EllipsisIcon, ChevronRightIcon, MenuComponent, MenuContentComponent, MenuItemComponent, LinkComponent, CardComponent, CardHeaderComponent, CardContentComponent, BadgeComponent, DialogComponent, DialogHeaderComponent, DialogTitleComponent, DialogContentComponent, DialogFooterComponent, ListItemComponent, SquareCheckIcon, SquareMinusIcon, SquareIcon, SwitchComponent, SelectOptionDirective, DialogService, XMarkIcon, InboxIcon, SparklesIcon, FileOutputIcon, TabsComponent, TabsListComponent, TabComponent, SidebarMenuComponent, SidebarMenuItemComponent, SidebarMenuButtonComponent, TooltipDirective, FrownIcon, ChevronLeftIcon, ChevronsLeftIcon, ChevronsRightIcon, ArrowUpAzIcon, ArrowDownZaIcon, ArrowUpRightFromSquareIcon, ToggleRightIcon, ToggleLeftIcon, Separator, SheetCloseDirective, ArrowLeftIcon, SheetService, DateRangePickerDirective, DatepickerDirective, FilterIcon, FilterXIcon, IconButtonComponent, HighlighterIcon, TagsIcon, SpinnerIcon, MagnifyingGlassIcon, LoadingCircleIcon, CircleCheckIcon, PopoverComponent, BellIcon, TrashIcon, BarsIcon, CardFooterComponent, InputGroupInput, InputGroupComponent, InputGroupAddonComponent, EyeSlashIcon, EyeIcon, BookmarkIcon, PopoverContentComponent, UserIcon, FolderIcon, VerticalDividerComponent, CommentIcon, ThumbsUpIcon, ThumbsDownIcon, DropdownDirective, SearchIcon, TriangleAlertIcon, ListFilterIcon, BreakpointObserverService, TrashCanIcon, CircleXIcon, InfoCircleIcon, HorizontalDividerComponent, HistoryIcon, StarIcon, FlagEnglishIcon, FlagFrenchIcon, EditIcon, UndoIcon, SaveIcon, AvatarComponent, AvatarFallbackComponent, AvatarImageComponent } from '@sinequa/ui';
|
|
6
6
|
import highlightWords from 'highlight-words';
|
|
7
7
|
import { ActivatedRoute, Router, NavigationEnd, RouterLink, RouterLinkActive, RouterModule } from '@angular/router';
|
|
8
8
|
import { withDevtools } from '@angular-architects/ngrx-toolkit';
|
|
9
9
|
import { signalStore, signalStoreFeature, withState, withMethods, patchState, getState, withComputed } from '@ngrx/signals';
|
|
10
|
-
import { EngineType, fetchApp, extraColumns, sysLang, globalConfig, getQueryParamsFromUrl, clearSessionTokens, login, info,
|
|
10
|
+
import { EngineType, fetchApp, extraColumns, sysLang, globalConfig, getQueryParamsFromUrl, clearSessionTokens, login, info, error, setGlobalConfig, initializeAppConfig, notify, addConcepts, queryParamsFromUrl, patchUserSettings, deleteUserSettings, fetchUserSettings, warn, buildPathsAndLevels, escapeExpr, isAuthenticated, isExpired, debug, AuditEventType, fetchSuggest, isObject, Audit, getMetadata, bisect, isNotInputEvent, fetchSponsoredLinks, fetchQuery, translateAggregationToDateOptions, aggItemRegex, parseValueAndOperatorFromItem, fetchSuggestField, fetchSimilarDocuments, logout, fetchChangePassword, fetchSendPasswordResetEmail, expiresSoon, suggestionsToTreeAggregationNodes, labels, fetchLabels, guid, getRelativeDate, createUserProfile, deleteUserProfileProperty, patchUserProfile, isJsonable, addAuditAdditionalInfo, getToken, setToken, createHeaders } from '@sinequa/atomic';
|
|
11
11
|
import { takeUntilDestroyed, toObservable, toSignal } from '@angular/core/rxjs-interop';
|
|
12
12
|
import { DatePipe, DATE_PIPE_DEFAULT_TIMEZONE, DATE_PIPE_DEFAULT_OPTIONS, Location, NgTemplateOutlet, NgStyle, NgClass, NgComponentOutlet } from '@angular/common';
|
|
13
|
-
import { HttpClient, HttpParams, httpResource, HttpResponse,
|
|
13
|
+
import { HttpClient, HttpParams, httpResource, HttpResponse, HttpContextToken, HttpHeaders } from '@angular/common/http';
|
|
14
14
|
import { Title, DomSanitizer } from '@angular/platform-browser';
|
|
15
15
|
import { cva } from 'class-variance-authority';
|
|
16
16
|
import * as i1 from '@angular/forms';
|
|
@@ -1525,100 +1525,174 @@ function withThemes(app, themes) {
|
|
|
1525
1525
|
}
|
|
1526
1526
|
|
|
1527
1527
|
/**
|
|
1528
|
-
* Signs
|
|
1528
|
+
* Signs the user in according to the resolved {@link globalConfig.authMode}.
|
|
1529
1529
|
*
|
|
1530
|
-
*
|
|
1531
|
-
*
|
|
1532
|
-
* credentials
|
|
1533
|
-
*
|
|
1534
|
-
*
|
|
1535
|
-
*
|
|
1530
|
+
* The mode is expected to be resolved beforehand (by `initializeAppConfig`, awaited in
|
|
1531
|
+
* `bootstrapApp`). This function clears any existing session, then:
|
|
1532
|
+
* - `credentials` → redirect to the login form;
|
|
1533
|
+
* - `sso` → reload the page so the browser/proxy performs the handshake;
|
|
1534
|
+
* - `oauth` / `saml` → delegate to `login()`, which redirects to the provider;
|
|
1535
|
+
* - `bearer` → delegate to `login()`;
|
|
1536
|
+
* - `unknown` → `login()` tries SSO silently then resolves to credentials; on failure the login
|
|
1537
|
+
* form is shown.
|
|
1538
|
+
*
|
|
1539
|
+
* @returns A promise resolving to a boolean indicating whether the user is authenticated.
|
|
1536
1540
|
*/
|
|
1537
1541
|
async function signIn() {
|
|
1538
1542
|
assertInInjectionContext(signIn);
|
|
1539
1543
|
const router = inject(Router);
|
|
1540
1544
|
const lastUrlAfterNavigation = inject(NavigationService).urlAfterNavigation;
|
|
1541
|
-
const {
|
|
1545
|
+
const { loginPath, authMode } = globalConfig;
|
|
1542
1546
|
// Always clear authentication tokens first to clear any existing session
|
|
1543
1547
|
clearSessionTokens();
|
|
1544
|
-
//
|
|
1545
|
-
if (
|
|
1548
|
+
// Credentials: show the login form.
|
|
1549
|
+
if (authMode?.kind === "credentials") {
|
|
1546
1550
|
router.navigate([loginPath], { queryParams: { returnUrl: lastUrlAfterNavigation } });
|
|
1547
1551
|
return false; // prevent further execution
|
|
1548
1552
|
}
|
|
1549
|
-
// SSO
|
|
1550
|
-
|
|
1551
|
-
if (useSSO) {
|
|
1552
|
-
// reload the page to trigger SSO login
|
|
1553
|
+
// SSO: the browser/proxy handles authentication — reload to trigger it.
|
|
1554
|
+
if (authMode?.kind === "sso") {
|
|
1553
1555
|
window.location.reload();
|
|
1554
1556
|
return false; // prevent further execution
|
|
1555
1557
|
}
|
|
1556
|
-
//
|
|
1558
|
+
// oauth / saml / bearer / unknown — let login() drive the handshake.
|
|
1557
1559
|
try {
|
|
1558
1560
|
const response = await login();
|
|
1559
1561
|
if (response) {
|
|
1560
1562
|
info("Response from login", response);
|
|
1561
1563
|
return true;
|
|
1562
1564
|
}
|
|
1563
|
-
|
|
1564
|
-
|
|
1565
|
+
// Not authenticated. For provider redirects (oauth/saml) the page is already navigating away,
|
|
1566
|
+
// so we don't touch the router. Otherwise (unknown resolved to credentials, or bearer failed)
|
|
1567
|
+
// show the login form.
|
|
1568
|
+
if (authMode?.kind !== "oauth" && authMode?.kind !== "saml") {
|
|
1569
|
+
router.navigate([loginPath], { queryParams: { returnUrl: lastUrlAfterNavigation } });
|
|
1565
1570
|
}
|
|
1571
|
+
return false;
|
|
1566
1572
|
}
|
|
1567
1573
|
catch (err) {
|
|
1568
1574
|
error("Error during login", err);
|
|
1569
|
-
|
|
1575
|
+
// A 401 is recoverable: the user just needs to authenticate → show the login form.
|
|
1576
|
+
if (err?.status === 401) {
|
|
1570
1577
|
error("Unauthorized access - please check your credentials:", err?.errorMessage);
|
|
1571
|
-
router.navigate([loginPath]);
|
|
1578
|
+
router.navigate([loginPath], { queryParams: { returnUrl: lastUrlAfterNavigation } });
|
|
1579
|
+
return false;
|
|
1572
1580
|
}
|
|
1573
|
-
|
|
1581
|
+
// Any other error (e.g. a misconfigured OAuth/SAML provider → 5xx) is fatal and not something
|
|
1582
|
+
// the user can resolve from the login form. Stop here: navigate straight to the error page with
|
|
1583
|
+
// the reason. We return false (instead of rethrowing) so the application does NOT initialize and
|
|
1584
|
+
// fire further authenticated API calls (e.g. usersettings → 401).
|
|
1585
|
+
const message = (err?.errorMessage ?? err?.message) || undefined;
|
|
1586
|
+
router.navigate(["/error"], { queryParams: { message } });
|
|
1587
|
+
return false;
|
|
1574
1588
|
}
|
|
1575
|
-
return false; // prevent further execution
|
|
1576
1589
|
}
|
|
1577
1590
|
|
|
1578
1591
|
/**
|
|
1579
1592
|
* Bootstraps the application by ensuring the user is authenticated and initializing the application.
|
|
1580
1593
|
*
|
|
1581
|
-
* This function first attempts to sign in the user
|
|
1582
|
-
* it
|
|
1583
|
-
*
|
|
1594
|
+
* This function first attempts to sign in the user via `signIn()`. If authentication is successful,
|
|
1595
|
+
* it initializes the application (stores and, optionally, dynamic routes) through `ApplicationService`
|
|
1596
|
+
* and waits for the initialization to complete before resolving. Any errors during authentication or
|
|
1597
|
+
* initialization are logged to the console, but the returned promise never rejects.
|
|
1584
1598
|
*
|
|
1585
|
-
*
|
|
1586
|
-
*
|
|
1587
|
-
*
|
|
1599
|
+
* Note: this function relies on Angular's injection context, so it must be called within one
|
|
1600
|
+
* (e.g. from `provideAppInitializer`).
|
|
1601
|
+
*
|
|
1602
|
+
* @param options - Configuration options for the bootstrap process.
|
|
1603
|
+
* @param options.createRoutes - Whether to create routes during initialization. Defaults to `true`.
|
|
1604
|
+
* @returns A promise that resolves to `true` when the application has been fully initialized,
|
|
1605
|
+
* or `false` when the user is not authenticated or an error occurred.
|
|
1588
1606
|
*/
|
|
1589
|
-
async function
|
|
1590
|
-
|
|
1591
|
-
//
|
|
1607
|
+
async function bootstrapApp({ createRoutes = true } = {}) {
|
|
1608
|
+
assertInInjectionContext(bootstrapApp);
|
|
1609
|
+
// Capture the injector synchronously: Angular's injection context does not survive across an
|
|
1610
|
+
// `await`, so after awaiting `initializeAppConfig()` we must re-enter it to inject services and
|
|
1611
|
+
// run `signIn()` (which use `inject()` internally).
|
|
1612
|
+
const injector = inject(EnvironmentInjector);
|
|
1613
|
+
// set the createRoutes flag early so it is defined even when sign-in fails
|
|
1614
|
+
// (ApplicationService.initialize() sets it again when it runs)
|
|
1592
1615
|
setGlobalConfig({ createRoutes });
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
|
|
1609
|
-
|
|
1610
|
-
|
|
1611
|
-
|
|
1612
|
-
|
|
1613
|
-
|
|
1614
|
-
|
|
1615
|
-
|
|
1616
|
-
|
|
1617
|
-
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1616
|
+
// Resolve the authentication mode (queries the server pre-login when needed) BEFORE signing in.
|
|
1617
|
+
// Doing it here — rather than as a separate, concurrent APP_INITIALIZER — guarantees that
|
|
1618
|
+
// signIn() reads a fully resolved `authMode` and removes the long-standing bootstrap race.
|
|
1619
|
+
// Crucially, this also sets `backendUrl` BEFORE any service/store is constructed, so those that
|
|
1620
|
+
// derive their API URL from `globalConfig.backendUrl` (e.g. PrincipalStore) don't capture
|
|
1621
|
+
// `undefined` (which produced requests to `/undefined/api/v1/...`).
|
|
1622
|
+
try {
|
|
1623
|
+
await initializeAppConfig();
|
|
1624
|
+
}
|
|
1625
|
+
catch (err) {
|
|
1626
|
+
error('Error while initializing application config:', err);
|
|
1627
|
+
// Pre-login failed (e.g. a 500 "app not found: 'mint_rnd-yoyo'" for a bad app name). This is
|
|
1628
|
+
// fatal — backendUrl/authMode can't be resolved — so route to the error page with the reason
|
|
1629
|
+
// instead of continuing to signIn() (which would otherwise fail with a less meaningful error).
|
|
1630
|
+
const message = err?.errorMessage
|
|
1631
|
+
?? err?.message;
|
|
1632
|
+
runInInjectionContext(injector, () => inject(Router).navigate(['/error'], { queryParams: { message } }));
|
|
1633
|
+
return false;
|
|
1634
|
+
}
|
|
1635
|
+
// Inject ApplicationService AFTER initializeAppConfig (re-entering the injection context lost
|
|
1636
|
+
// across the await), so its dependent services/stores are constructed with `backendUrl` set.
|
|
1637
|
+
const applicationService = runInInjectionContext(injector, () => inject(ApplicationService));
|
|
1638
|
+
let authenticated = false;
|
|
1639
|
+
try {
|
|
1640
|
+
// Re-enter the injection context lost across the await above.
|
|
1641
|
+
authenticated = await runInInjectionContext(injector, () => signIn());
|
|
1642
|
+
}
|
|
1643
|
+
catch (err) {
|
|
1644
|
+
error('Error while signing in:', err);
|
|
1645
|
+
return false;
|
|
1646
|
+
}
|
|
1647
|
+
if (!authenticated) {
|
|
1648
|
+
info('User not authenticated, skipping application initialization.');
|
|
1649
|
+
return false;
|
|
1650
|
+
}
|
|
1651
|
+
info('User authenticated, initializing application...');
|
|
1652
|
+
try {
|
|
1653
|
+
await applicationService.initialize(createRoutes);
|
|
1654
|
+
info(`Application initialized successfully (createRoutes: ${createRoutes}).`);
|
|
1655
|
+
return true;
|
|
1656
|
+
}
|
|
1657
|
+
catch (err) {
|
|
1658
|
+
error(`Error initializing application (createRoutes: ${createRoutes}):`, err);
|
|
1659
|
+
// Authenticated, but the application failed to initialize (e.g. fetchApp/usersettings failed).
|
|
1660
|
+
// Route to the error page with the failure reason instead of leaving a half-initialized app.
|
|
1661
|
+
const message = err?.errorMessage
|
|
1662
|
+
?? err?.message;
|
|
1663
|
+
runInInjectionContext(injector, () => inject(Router).navigate(['/error'], { queryParams: { message } }));
|
|
1664
|
+
return false;
|
|
1665
|
+
}
|
|
1666
|
+
}
|
|
1667
|
+
/**
|
|
1668
|
+
* Bootstraps the application by ensuring the user is authenticated and initializing the application.
|
|
1669
|
+
*
|
|
1670
|
+
* @deprecated Use {@link bootstrapApp} instead, and let it inject `ApplicationService` itself.
|
|
1671
|
+
*
|
|
1672
|
+
* Migration — in your `app.config.ts`, replace:
|
|
1673
|
+
* ```ts
|
|
1674
|
+
* // ❌ Deprecated: eagerly injecting ApplicationService in the factory constructs it (and its
|
|
1675
|
+
* // dependent stores/services) BEFORE bootstrapApp resolves the config, so services that build
|
|
1676
|
+
* // their API URL from `globalConfig.backendUrl` capture `undefined` (→ `/undefined/api/v1/...`).
|
|
1677
|
+
* provideAppInitializer(() => withBootstrapApp(inject(ApplicationService), { createRoutes: true })),
|
|
1678
|
+
* ```
|
|
1679
|
+
* with:
|
|
1680
|
+
* ```ts
|
|
1681
|
+
* // ✅ bootstrapApp injects ApplicationService internally, AFTER initializeAppConfig() has set
|
|
1682
|
+
* // `backendUrl` and resolved the auth mode.
|
|
1683
|
+
* provideAppInitializer(() => bootstrapApp({ createRoutes: true })),
|
|
1684
|
+
* ```
|
|
1685
|
+
* (Remove the now-unused `inject` / `ApplicationService` imports.)
|
|
1686
|
+
*
|
|
1687
|
+
* @param applicationService - Ignored; kept for backward compatibility. `ApplicationService` is
|
|
1688
|
+
* provided in root and injected internally by {@link bootstrapApp}, so the instance is the same.
|
|
1689
|
+
* Passing `inject(ApplicationService)` here is discouraged — see the migration note above.
|
|
1690
|
+
* @param options - Configuration options for the bootstrap process.
|
|
1691
|
+
* @param options.createRoutes - Whether to create routes during initialization. Defaults to `true`.
|
|
1692
|
+
* @returns A promise that resolves when the bootstrap process is complete, regardless of success or failure.
|
|
1693
|
+
*/
|
|
1694
|
+
async function withBootstrapApp(_applicationService, { createRoutes = true } = {}) {
|
|
1695
|
+
await bootstrapApp({ createRoutes });
|
|
1622
1696
|
}
|
|
1623
1697
|
|
|
1624
1698
|
/**
|
|
@@ -3384,8 +3458,9 @@ class AppService {
|
|
|
3384
3458
|
*
|
|
3385
3459
|
* @remarks
|
|
3386
3460
|
* This method constructs an HTTP GET request to fetch the application configuration
|
|
3387
|
-
* using the `app` parameter from the global configuration. If the request fails,
|
|
3388
|
-
*
|
|
3461
|
+
* using the `app` parameter from the global configuration. If the request fails, it logs the
|
|
3462
|
+
* error and re-throws a normalized `Error` carrying the server's `errorMessage` when available
|
|
3463
|
+
* (e.g. "app not found: '...'"), so callers can surface the reason on the error page.
|
|
3389
3464
|
*
|
|
3390
3465
|
* @example
|
|
3391
3466
|
* ```typescript
|
|
@@ -3397,9 +3472,19 @@ class AppService {
|
|
|
3397
3472
|
getApp(appName) {
|
|
3398
3473
|
const app = appName || globalConfig.app;
|
|
3399
3474
|
const params = new HttpParams().set('app', app || '');
|
|
3400
|
-
return this.http.get(this.API_URL + '/app', { params }).pipe(catchError(
|
|
3401
|
-
console.error('AppService.getApp failure - error: ',
|
|
3402
|
-
|
|
3475
|
+
return this.http.get(this.API_URL + '/app', { params }).pipe(catchError((err) => {
|
|
3476
|
+
console.error('AppService.getApp failure - error: ', err);
|
|
3477
|
+
// Propagate the failure (previously swallowed with EMPTY, which hid the cause and surfaced a
|
|
3478
|
+
// generic "no elements in sequence" downstream). Surface the Sinequa error-envelope message
|
|
3479
|
+
// when present — e.g. a 500 with { errorMessage: "app not found: 'mint_rnd-yoyo'" } — so the
|
|
3480
|
+
// bootstrap/login flow can route to the error page with the actual reason.
|
|
3481
|
+
const e = err;
|
|
3482
|
+
const message = e?.error?.errorMessage ??
|
|
3483
|
+
e?.error?.message ??
|
|
3484
|
+
e?.errorMessage ??
|
|
3485
|
+
e?.message ??
|
|
3486
|
+
'Failed to fetch application configuration';
|
|
3487
|
+
return throwError(() => new Error(message));
|
|
3403
3488
|
}));
|
|
3404
3489
|
}
|
|
3405
3490
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: AppService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
@@ -3421,12 +3506,13 @@ function AuthGuard() {
|
|
|
3421
3506
|
return async (_, state) => {
|
|
3422
3507
|
const router = inject(Router);
|
|
3423
3508
|
const principalStore = inject(PrincipalStore);
|
|
3424
|
-
const { loginPath,
|
|
3509
|
+
const { loginPath, authMode } = globalConfig;
|
|
3425
3510
|
if (state.url.startsWith("/login"))
|
|
3426
3511
|
return true;
|
|
3427
3512
|
// If the user is not authenticated, navigate to the login page.
|
|
3428
3513
|
// The login page handles every authentication method (credentials, OAuth, SAML).
|
|
3429
|
-
|
|
3514
|
+
// In SSO mode the browser/proxy carries the auth, so we let it through.
|
|
3515
|
+
if (!isAuthenticated() && authMode?.kind !== "sso") {
|
|
3430
3516
|
router.navigate([loginPath], { queryParams: { returnUrl: state.url } });
|
|
3431
3517
|
return false;
|
|
3432
3518
|
}
|
|
@@ -3452,7 +3538,7 @@ function AuthGuard() {
|
|
|
3452
3538
|
// check if the password is expired and if the partition is editable
|
|
3453
3539
|
// changing password is only possible when user use credentials to auhtenticate
|
|
3454
3540
|
// only in credentials mode
|
|
3455
|
-
if (
|
|
3541
|
+
if (authMode?.kind === "credentials") {
|
|
3456
3542
|
const exp = passwordExpirationDate;
|
|
3457
3543
|
const editable = !!editablePartition;
|
|
3458
3544
|
if (editable && exp && isExpired(exp)) {
|
|
@@ -5278,101 +5364,160 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
5278
5364
|
}], ctorParameters: () => [], propDecorators: { article: [{ type: i0.Input, args: [{ isSignal: true, alias: "article", required: true }] }], aggregation: [{ type: i0.Input, args: [{ isSignal: true, alias: "aggregation", required: true }] }], shadow: [{ type: i0.ViewChild, args: ["shadowRender", { ...{ read: ElementRef }, isSignal: true }] }], client: [{ type: i0.ViewChild, args: ["documentLocator", { ...{ read: ElementRef }, isSignal: true }] }] } });
|
|
5279
5365
|
|
|
5280
5366
|
class ErrorComponent {
|
|
5367
|
+
route = inject(ActivatedRoute);
|
|
5281
5368
|
router = inject(Router);
|
|
5282
|
-
|
|
5369
|
+
/**
|
|
5370
|
+
* Human-readable error detail shown on the page, taken from the `message` query param.
|
|
5371
|
+
* Callers navigating here can pass it, e.g. on an auth failure:
|
|
5372
|
+
* `router.navigate(["error"], { queryParams: { message: err.message } })`.
|
|
5373
|
+
*/
|
|
5374
|
+
message = (() => {
|
|
5375
|
+
const value = this.route.snapshot.queryParams['message'];
|
|
5376
|
+
return typeof value === 'string' && value.trim().length ? value : undefined;
|
|
5377
|
+
})();
|
|
5378
|
+
/** Navigate home and re-bootstrap the application (fresh authentication attempt). */
|
|
5379
|
+
goHome() {
|
|
5283
5380
|
this.router.navigate(['/']).then(() => window.location.reload());
|
|
5284
5381
|
}
|
|
5285
5382
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: ErrorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5286
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "
|
|
5287
|
-
<
|
|
5288
|
-
|
|
5289
|
-
|
|
5290
|
-
|
|
5291
|
-
|
|
5292
|
-
|
|
5293
|
-
viewBox="0 0 24 24"
|
|
5294
|
-
fill="none"
|
|
5295
|
-
stroke="currentColor"
|
|
5296
|
-
stroke-width="2"
|
|
5297
|
-
stroke-linecap="round"
|
|
5298
|
-
stroke-linejoin="round">
|
|
5299
|
-
<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" />
|
|
5300
|
-
<path d="M12 9v4" />
|
|
5301
|
-
<path d="M12 17h.01" />
|
|
5302
|
-
</svg>
|
|
5303
|
-
<h1 class="mb-4 text-4xl font-bold">Oops! Something went wrong</h1>
|
|
5304
|
-
<p class="text-muted-foreground mb-8 text-xl">We apologize for the inconvenience.</p>
|
|
5305
|
-
<div class="flex space-x-4">
|
|
5306
|
-
<button
|
|
5307
|
-
(click)="reload()"
|
|
5308
|
-
class="btn btn-outline rounded-lg border-neutral-300 text-neutral-700 transition hover:bg-neutral-200 hover:text-neutral-700">
|
|
5383
|
+
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", type: ErrorComponent, isStandalone: true, selector: "error-component, ErrorComponent", host: { classAttribute: "bg-background text-foreground grid min-h-dvh w-full place-content-center p-6" }, ngImport: i0, template: `
|
|
5384
|
+
<Card
|
|
5385
|
+
hover="no"
|
|
5386
|
+
class="bg-background border-(--popover-border) w-full max-w-md rounded-3xl border shadow-2xl">
|
|
5387
|
+
<CardHeader class="flex flex-col items-center gap-4 pt-8 text-center">
|
|
5388
|
+
<span
|
|
5389
|
+
class="bg-destructive/10 text-destructive inline-flex h-16 w-16 items-center justify-center rounded-full">
|
|
5309
5390
|
<svg
|
|
5310
|
-
class="mr-2 h-4 w-4"
|
|
5311
5391
|
xmlns="http://www.w3.org/2000/svg"
|
|
5312
|
-
|
|
5313
|
-
height="24"
|
|
5392
|
+
class="h-8 w-8"
|
|
5314
5393
|
viewBox="0 0 24 24"
|
|
5315
5394
|
fill="none"
|
|
5316
5395
|
stroke="currentColor"
|
|
5317
5396
|
stroke-width="2"
|
|
5318
5397
|
stroke-linecap="round"
|
|
5319
|
-
stroke-linejoin="round"
|
|
5320
|
-
|
|
5321
|
-
<path d="
|
|
5398
|
+
stroke-linejoin="round"
|
|
5399
|
+
aria-hidden="true">
|
|
5400
|
+
<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" />
|
|
5401
|
+
<path d="M12 9v4" />
|
|
5402
|
+
<path d="M12 17h.01" />
|
|
5322
5403
|
</svg>
|
|
5323
|
-
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
|
|
5404
|
+
</span>
|
|
5405
|
+
|
|
5406
|
+
<div class="grid gap-1.5">
|
|
5407
|
+
<h1 class="text-2xl font-semibold tracking-tight">Something went wrong</h1>
|
|
5408
|
+
<p class="text-muted-foreground text-sm">
|
|
5409
|
+
We're sorry — the application ran into a problem and couldn't continue.
|
|
5410
|
+
</p>
|
|
5411
|
+
</div>
|
|
5412
|
+
</CardHeader>
|
|
5413
|
+
|
|
5414
|
+
<CardContent class="grid gap-5 pb-8">
|
|
5415
|
+
@if (message) {
|
|
5416
|
+
<div class="border-border bg-muted/40 grid gap-1 rounded-lg border p-3 text-left">
|
|
5417
|
+
<span class="text-muted-foreground text-[0.7rem] font-medium uppercase tracking-wider">
|
|
5418
|
+
Details
|
|
5419
|
+
</span>
|
|
5420
|
+
<p class="text-foreground text-sm break-words" role="alert">{{ message }}</p>
|
|
5421
|
+
</div>
|
|
5422
|
+
}
|
|
5423
|
+
|
|
5424
|
+
<div class="flex justify-center">
|
|
5425
|
+
<button variant="primary" (click)="goHome()">
|
|
5426
|
+
<svg
|
|
5427
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
5428
|
+
class="mr-2 h-4 w-4"
|
|
5429
|
+
viewBox="0 0 24 24"
|
|
5430
|
+
fill="none"
|
|
5431
|
+
stroke="currentColor"
|
|
5432
|
+
stroke-width="2"
|
|
5433
|
+
stroke-linecap="round"
|
|
5434
|
+
stroke-linejoin="round"
|
|
5435
|
+
aria-hidden="true">
|
|
5436
|
+
<path d="M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8" />
|
|
5437
|
+
<path d="M3 10a2 2 0 0 1 .709-1.528l7-5.999a2 2 0 0 1 2.582 0l7 5.999A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
|
|
5438
|
+
</svg>
|
|
5439
|
+
Go to homepage
|
|
5440
|
+
</button>
|
|
5441
|
+
</div>
|
|
5442
|
+
</CardContent>
|
|
5443
|
+
</Card>
|
|
5444
|
+
`, isInline: true, dependencies: [{ kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "scheme", "iconOnly", "size", "solid"] }, { kind: "directive", type: CardComponent, selector: ".card, card, Card", inputs: ["class", "variant", "hover"] }, { kind: "directive", type: CardHeaderComponent, selector: ".card-header, card-header, CardHeader, cardheader", inputs: ["class"] }, { kind: "directive", type: CardContentComponent, selector: ".card-content, card-content, CardContent, cardcontent", inputs: ["class"] }] });
|
|
5328
5445
|
}
|
|
5329
5446
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: ErrorComponent, decorators: [{
|
|
5330
5447
|
type: Component,
|
|
5331
5448
|
args: [{
|
|
5332
5449
|
selector: 'error-component, ErrorComponent',
|
|
5333
5450
|
standalone: true,
|
|
5334
|
-
imports: [
|
|
5451
|
+
imports: [
|
|
5452
|
+
ButtonComponent,
|
|
5453
|
+
CardComponent,
|
|
5454
|
+
CardHeaderComponent,
|
|
5455
|
+
CardContentComponent
|
|
5456
|
+
],
|
|
5457
|
+
host: {
|
|
5458
|
+
class: 'bg-background text-foreground grid min-h-dvh w-full place-content-center p-6'
|
|
5459
|
+
},
|
|
5335
5460
|
template: `
|
|
5336
|
-
<
|
|
5337
|
-
|
|
5338
|
-
|
|
5339
|
-
|
|
5340
|
-
|
|
5341
|
-
|
|
5342
|
-
viewBox="0 0 24 24"
|
|
5343
|
-
fill="none"
|
|
5344
|
-
stroke="currentColor"
|
|
5345
|
-
stroke-width="2"
|
|
5346
|
-
stroke-linecap="round"
|
|
5347
|
-
stroke-linejoin="round">
|
|
5348
|
-
<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" />
|
|
5349
|
-
<path d="M12 9v4" />
|
|
5350
|
-
<path d="M12 17h.01" />
|
|
5351
|
-
</svg>
|
|
5352
|
-
<h1 class="mb-4 text-4xl font-bold">Oops! Something went wrong</h1>
|
|
5353
|
-
<p class="text-muted-foreground mb-8 text-xl">We apologize for the inconvenience.</p>
|
|
5354
|
-
<div class="flex space-x-4">
|
|
5355
|
-
<button
|
|
5356
|
-
(click)="reload()"
|
|
5357
|
-
class="btn btn-outline rounded-lg border-neutral-300 text-neutral-700 transition hover:bg-neutral-200 hover:text-neutral-700">
|
|
5461
|
+
<Card
|
|
5462
|
+
hover="no"
|
|
5463
|
+
class="bg-background border-(--popover-border) w-full max-w-md rounded-3xl border shadow-2xl">
|
|
5464
|
+
<CardHeader class="flex flex-col items-center gap-4 pt-8 text-center">
|
|
5465
|
+
<span
|
|
5466
|
+
class="bg-destructive/10 text-destructive inline-flex h-16 w-16 items-center justify-center rounded-full">
|
|
5358
5467
|
<svg
|
|
5359
|
-
class="mr-2 h-4 w-4"
|
|
5360
5468
|
xmlns="http://www.w3.org/2000/svg"
|
|
5361
|
-
|
|
5362
|
-
height="24"
|
|
5469
|
+
class="h-8 w-8"
|
|
5363
5470
|
viewBox="0 0 24 24"
|
|
5364
5471
|
fill="none"
|
|
5365
5472
|
stroke="currentColor"
|
|
5366
5473
|
stroke-width="2"
|
|
5367
5474
|
stroke-linecap="round"
|
|
5368
|
-
stroke-linejoin="round"
|
|
5369
|
-
|
|
5370
|
-
<path d="
|
|
5475
|
+
stroke-linejoin="round"
|
|
5476
|
+
aria-hidden="true">
|
|
5477
|
+
<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3" />
|
|
5478
|
+
<path d="M12 9v4" />
|
|
5479
|
+
<path d="M12 17h.01" />
|
|
5371
5480
|
</svg>
|
|
5372
|
-
|
|
5373
|
-
|
|
5374
|
-
|
|
5375
|
-
|
|
5481
|
+
</span>
|
|
5482
|
+
|
|
5483
|
+
<div class="grid gap-1.5">
|
|
5484
|
+
<h1 class="text-2xl font-semibold tracking-tight">Something went wrong</h1>
|
|
5485
|
+
<p class="text-muted-foreground text-sm">
|
|
5486
|
+
We're sorry — the application ran into a problem and couldn't continue.
|
|
5487
|
+
</p>
|
|
5488
|
+
</div>
|
|
5489
|
+
</CardHeader>
|
|
5490
|
+
|
|
5491
|
+
<CardContent class="grid gap-5 pb-8">
|
|
5492
|
+
@if (message) {
|
|
5493
|
+
<div class="border-border bg-muted/40 grid gap-1 rounded-lg border p-3 text-left">
|
|
5494
|
+
<span class="text-muted-foreground text-[0.7rem] font-medium uppercase tracking-wider">
|
|
5495
|
+
Details
|
|
5496
|
+
</span>
|
|
5497
|
+
<p class="text-foreground text-sm break-words" role="alert">{{ message }}</p>
|
|
5498
|
+
</div>
|
|
5499
|
+
}
|
|
5500
|
+
|
|
5501
|
+
<div class="flex justify-center">
|
|
5502
|
+
<button variant="primary" (click)="goHome()">
|
|
5503
|
+
<svg
|
|
5504
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
5505
|
+
class="mr-2 h-4 w-4"
|
|
5506
|
+
viewBox="0 0 24 24"
|
|
5507
|
+
fill="none"
|
|
5508
|
+
stroke="currentColor"
|
|
5509
|
+
stroke-width="2"
|
|
5510
|
+
stroke-linecap="round"
|
|
5511
|
+
stroke-linejoin="round"
|
|
5512
|
+
aria-hidden="true">
|
|
5513
|
+
<path d="M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8" />
|
|
5514
|
+
<path d="M3 10a2 2 0 0 1 .709-1.528l7-5.999a2 2 0 0 1 2.582 0l7 5.999A2 2 0 0 1 21 10v9a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z" />
|
|
5515
|
+
</svg>
|
|
5516
|
+
Go to homepage
|
|
5517
|
+
</button>
|
|
5518
|
+
</div>
|
|
5519
|
+
</CardContent>
|
|
5520
|
+
</Card>
|
|
5376
5521
|
`
|
|
5377
5522
|
}]
|
|
5378
5523
|
}] });
|
|
@@ -7266,6 +7411,7 @@ class NavbarTabsComponent {
|
|
|
7266
7411
|
cn = cn;
|
|
7267
7412
|
class = input(...(ngDevMode ? [undefined, { debugName: "class" }] : []));
|
|
7268
7413
|
router = inject(Router);
|
|
7414
|
+
appStore = inject(AppStore);
|
|
7269
7415
|
showCount = input(true, ...(ngDevMode ? [{ debugName: "showCount", transform: booleanAttribute }] : [{ transform: booleanAttribute }]));
|
|
7270
7416
|
/**
|
|
7271
7417
|
* When enabled (default), tab labels are never clipped: tabs that do not fit
|
|
@@ -7295,6 +7441,42 @@ class NavbarTabsComponent {
|
|
|
7295
7441
|
return undefined;
|
|
7296
7442
|
return hasIcon ? "calc(1.5rem + 16px)" : "calc(1.5rem + 4ch)";
|
|
7297
7443
|
}
|
|
7444
|
+
persistFiltersAcrossTabs = computed(() => {
|
|
7445
|
+
const general = this.appStore.general?.();
|
|
7446
|
+
const appFeature = general?.features?.persistFiltersAcrossTabs;
|
|
7447
|
+
if (appFeature !== undefined)
|
|
7448
|
+
return appFeature;
|
|
7449
|
+
return false;
|
|
7450
|
+
}, ...(ngDevMode ? [{ debugName: "persistFiltersAcrossTabs" }] : []));
|
|
7451
|
+
// Determine how query params are applied on tab change.
|
|
7452
|
+
// - persist: 'merge' so the tab identity params (n/t/q) are updated while
|
|
7453
|
+
// the filter params (f/sort/id/page) already in the URL are kept.
|
|
7454
|
+
// - default: 'replace' so everything is rewritten from scratch.
|
|
7455
|
+
getQueryParamsHandling() {
|
|
7456
|
+
return this.persistFiltersAcrossTabs() ? 'merge' : 'replace';
|
|
7457
|
+
}
|
|
7458
|
+
// Get query params conditionally
|
|
7459
|
+
getQueryParams(tab) {
|
|
7460
|
+
if (this.persistFiltersAcrossTabs()) {
|
|
7461
|
+
// When preserving filters, still update the tab identity params so the
|
|
7462
|
+
// store rebuilds the query for the new tab. 'merge' keeps f/sort/id/page.
|
|
7463
|
+
return {
|
|
7464
|
+
n: tab.queryName,
|
|
7465
|
+
q: this.nav.searchText(),
|
|
7466
|
+
t: tab.wsQueryTab
|
|
7467
|
+
};
|
|
7468
|
+
}
|
|
7469
|
+
// When replacing, explicitly set the filters to undefined to clear them
|
|
7470
|
+
return {
|
|
7471
|
+
n: tab.queryName,
|
|
7472
|
+
q: this.nav.searchText(),
|
|
7473
|
+
t: tab.wsQueryTab,
|
|
7474
|
+
f: undefined,
|
|
7475
|
+
sort: undefined,
|
|
7476
|
+
id: undefined,
|
|
7477
|
+
page: undefined
|
|
7478
|
+
};
|
|
7479
|
+
}
|
|
7298
7480
|
changeTab() { }
|
|
7299
7481
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: NavbarTabsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
7300
7482
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.0.0", version: "20.3.18", type: NavbarTabsComponent, isStandalone: true, selector: "navbar-tabs", inputs: { class: { classPropertyName: "class", publicName: "class", isSignal: true, isRequired: false, transformFunction: null }, showCount: { classPropertyName: "showCount", publicName: "showCount", isSignal: true, isRequired: false, transformFunction: null }, noTruncate: { classPropertyName: "noTruncate", publicName: "noTruncate", isSignal: true, isRequired: false, transformFunction: null }, minTabWidth: { classPropertyName: "minTabWidth", publicName: "minTabWidth", isSignal: true, isRequired: false, transformFunction: null }, path: { classPropertyName: "path", publicName: "path", isSignal: true, isRequired: false, transformFunction: null } }, host: { properties: { "class": "cn('block', class())" } }, ngImport: i0, template: `
|
|
@@ -7315,9 +7497,10 @@ class NavbarTabsComponent {
|
|
|
7315
7497
|
[attr.disabled]="showCount() && tab.count === 0 ? '' : null"
|
|
7316
7498
|
[active]="nav.currentPath() === tab.path"
|
|
7317
7499
|
[routerLink]="[tab.routerLink]"
|
|
7318
|
-
[queryParams]="
|
|
7500
|
+
[queryParams]="getQueryParams(tab)"
|
|
7501
|
+
[queryParamsHandling]="getQueryParamsHandling()"
|
|
7319
7502
|
(click)="changeTab()"
|
|
7320
|
-
(keydown.enter)="router.navigate([tab.routerLink], { queryParams:
|
|
7503
|
+
(keydown.enter)="router.navigate([tab.routerLink], { queryParams: getQueryParams(tab), queryParamsHandling: getQueryParamsHandling() })"
|
|
7321
7504
|
>
|
|
7322
7505
|
<div [class]="cn('flex items-center content-start w-full gap-1', !noTruncate() && '@container overflow-hidden min-w-0')">
|
|
7323
7506
|
@if (tab.icon) {
|
|
@@ -7355,7 +7538,8 @@ class NavbarTabsComponent {
|
|
|
7355
7538
|
<MenuItem>
|
|
7356
7539
|
<a class="inline-flex items-center gap-1 whitespace-nowrap first-letter:capitalize"
|
|
7357
7540
|
[routerLink]="[tab.routerLink]"
|
|
7358
|
-
[queryParams]="
|
|
7541
|
+
[queryParams]="getQueryParams(tab)"
|
|
7542
|
+
[queryParamsHandling]="getQueryParamsHandling()"
|
|
7359
7543
|
[attr.aria-selected]="nav.currentPath() === tab.path"
|
|
7360
7544
|
[attr.aria-label]="tab.display | syslang | transloco"
|
|
7361
7545
|
(click)="changeTab()">
|
|
@@ -7413,9 +7597,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
7413
7597
|
[attr.disabled]="showCount() && tab.count === 0 ? '' : null"
|
|
7414
7598
|
[active]="nav.currentPath() === tab.path"
|
|
7415
7599
|
[routerLink]="[tab.routerLink]"
|
|
7416
|
-
[queryParams]="
|
|
7600
|
+
[queryParams]="getQueryParams(tab)"
|
|
7601
|
+
[queryParamsHandling]="getQueryParamsHandling()"
|
|
7417
7602
|
(click)="changeTab()"
|
|
7418
|
-
(keydown.enter)="router.navigate([tab.routerLink], { queryParams:
|
|
7603
|
+
(keydown.enter)="router.navigate([tab.routerLink], { queryParams: getQueryParams(tab), queryParamsHandling: getQueryParamsHandling() })"
|
|
7419
7604
|
>
|
|
7420
7605
|
<div [class]="cn('flex items-center content-start w-full gap-1', !noTruncate() && '@container overflow-hidden min-w-0')">
|
|
7421
7606
|
@if (tab.icon) {
|
|
@@ -7453,7 +7638,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
7453
7638
|
<MenuItem>
|
|
7454
7639
|
<a class="inline-flex items-center gap-1 whitespace-nowrap first-letter:capitalize"
|
|
7455
7640
|
[routerLink]="[tab.routerLink]"
|
|
7456
|
-
[queryParams]="
|
|
7641
|
+
[queryParams]="getQueryParams(tab)"
|
|
7642
|
+
[queryParamsHandling]="getQueryParamsHandling()"
|
|
7457
7643
|
[attr.aria-selected]="nav.currentPath() === tab.path"
|
|
7458
7644
|
[attr.aria-label]="tab.display | syslang | transloco"
|
|
7459
7645
|
(click)="changeTab()">
|
|
@@ -10557,13 +10743,18 @@ class SignInComponent {
|
|
|
10557
10743
|
config = globalConfig;
|
|
10558
10744
|
/**
|
|
10559
10745
|
* True when authentication is handled outside the credentials form — i.e. by the
|
|
10560
|
-
* browser/proxy (`
|
|
10561
|
-
*
|
|
10562
|
-
*
|
|
10563
|
-
|
|
10564
|
-
|
|
10565
|
-
|
|
10566
|
-
|
|
10746
|
+
* browser/proxy (`sso`) or by an auto-configured OAuth/SAML provider. In those modes
|
|
10747
|
+
* this screen shows a loader instead of a login form and initiates the handshake
|
|
10748
|
+
* automatically by calling `handleLogin()`.
|
|
10749
|
+
*
|
|
10750
|
+
* Note: the ambiguous `unknown` mode is intentionally excluded — it is resolved upstream
|
|
10751
|
+
* (in `login()`/`signIn()`) to either `sso` or `credentials` before this screen renders,
|
|
10752
|
+
* so reaching here in `unknown` should still show the form, never a dead-end loader.
|
|
10753
|
+
*/
|
|
10754
|
+
externalAuth = (() => {
|
|
10755
|
+
const kind = globalConfig.authMode?.kind;
|
|
10756
|
+
return kind === "sso" || kind === "oauth" || kind === "saml";
|
|
10757
|
+
})();
|
|
10567
10758
|
class = input(...(ngDevMode ? [undefined, { debugName: "class" }] : []));
|
|
10568
10759
|
forgotPassword = output();
|
|
10569
10760
|
username = model("", ...(ngDevMode ? [{ debugName: "username" }] : []));
|
|
@@ -10659,10 +10850,13 @@ class SignInComponent {
|
|
|
10659
10850
|
this.auditService.notifyLogin();
|
|
10660
10851
|
}
|
|
10661
10852
|
return result;
|
|
10662
|
-
}).catch(
|
|
10663
|
-
warn("An error occurred while logging in",
|
|
10853
|
+
}).catch((err) => {
|
|
10854
|
+
warn("An error occurred while logging in", err);
|
|
10664
10855
|
this.auditService.notify({ type: 'Login_Denied' });
|
|
10665
|
-
|
|
10856
|
+
// Surface the failure reason on the error page (e.g. "OAuth provider not found: identity-dev")
|
|
10857
|
+
// so the user/admin knows what to fix, instead of a bare generic error screen.
|
|
10858
|
+
const message = (err?.errorMessage ?? err?.message) || undefined;
|
|
10859
|
+
this.router.navigate(["error"], { queryParams: { message } });
|
|
10666
10860
|
return false;
|
|
10667
10861
|
});
|
|
10668
10862
|
}
|
|
@@ -10677,7 +10871,16 @@ class SignInComponent {
|
|
|
10677
10871
|
}
|
|
10678
10872
|
this.auditService.notifyLogin();
|
|
10679
10873
|
const { createRoutes = false } = globalConfig;
|
|
10680
|
-
|
|
10874
|
+
try {
|
|
10875
|
+
await this.applicationService.initialize(createRoutes);
|
|
10876
|
+
}
|
|
10877
|
+
catch (initErr) {
|
|
10878
|
+
// Authenticated, but the application failed to initialize (e.g. fetchApp failed). Surface the
|
|
10879
|
+
// reason on the error page rather than leaving the user stuck on the login form.
|
|
10880
|
+
const { errorMessage, message } = (initErr ?? {});
|
|
10881
|
+
this.router.navigate(["/error"], { queryParams: { message: errorMessage ?? message } });
|
|
10882
|
+
return;
|
|
10883
|
+
}
|
|
10681
10884
|
this.checkPasswordExpiresSoon();
|
|
10682
10885
|
const url = this.route.snapshot.queryParams["returnUrl"] || "/";
|
|
10683
10886
|
this.router.navigateByUrl(url);
|
|
@@ -11231,50 +11434,29 @@ class OverrideUserDialogComponent {
|
|
|
11231
11434
|
}
|
|
11232
11435
|
}
|
|
11233
11436
|
handleOverrideUser(username, domain) {
|
|
11234
|
-
const {
|
|
11437
|
+
const { createRoutes } = globalConfig;
|
|
11235
11438
|
if (username === undefined || domain === undefined) {
|
|
11236
11439
|
setGlobalConfig({ userOverrideActive: false, userOverride: undefined });
|
|
11237
11440
|
}
|
|
11238
11441
|
else {
|
|
11239
11442
|
setGlobalConfig({ userOverrideActive: true, userOverride: { username, domain } });
|
|
11240
11443
|
}
|
|
11241
|
-
//
|
|
11242
|
-
|
|
11243
|
-
|
|
11244
|
-
|
|
11245
|
-
|
|
11246
|
-
|
|
11247
|
-
|
|
11248
|
-
|
|
11249
|
-
|
|
11250
|
-
|
|
11251
|
-
|
|
11252
|
-
|
|
11253
|
-
|
|
11254
|
-
|
|
11255
|
-
|
|
11256
|
-
|
|
11257
|
-
.then((value) => {
|
|
11258
|
-
if (value) {
|
|
11259
|
-
this.appService
|
|
11260
|
-
.initialize(createRoutes)
|
|
11261
|
-
.then(() => {
|
|
11262
|
-
const fullName = this.principalStore.fullName();
|
|
11263
|
-
notify.success(`Welcome back ${fullName}!`, { duration: 2000 });
|
|
11264
|
-
})
|
|
11265
|
-
.catch((err) => {
|
|
11266
|
-
error("An error occured while overriding (initialize)", err);
|
|
11267
|
-
notify.error(err.message, { duration: 2000 });
|
|
11268
|
-
setGlobalConfig({ userOverrideActive: false, userOverride: undefined });
|
|
11269
|
-
});
|
|
11270
|
-
}
|
|
11271
|
-
})
|
|
11272
|
-
.catch((err) => {
|
|
11273
|
-
error("An error occured while overriding (login)", err);
|
|
11274
|
-
notify.error("An error occured while overriding (login)", { duration: 2000 });
|
|
11275
|
-
setGlobalConfig({ userOverrideActive: false, userOverride: undefined });
|
|
11276
|
-
});
|
|
11277
|
-
}
|
|
11444
|
+
// Impersonation is header-driven: `createHeaders` adds `sinequa-override-user`/`-domain` to every
|
|
11445
|
+
// request while `userOverrideActive`, on top of the current (admin) session. We therefore do NOT
|
|
11446
|
+
// need to re-authenticate — re-initializing the stores refetches the principal/usersettings as the
|
|
11447
|
+
// overridden user. This works in every auth mode, including `credentials` where `login()` (without
|
|
11448
|
+
// credentials) is intentionally a no-op in atomic 2.0 and would otherwise skip initialization.
|
|
11449
|
+
this.appService
|
|
11450
|
+
.initialize(createRoutes)
|
|
11451
|
+
.then(() => {
|
|
11452
|
+
const fullName = this.principalStore.fullName();
|
|
11453
|
+
notify.success(`Welcome back ${fullName}!`, { duration: 2000 });
|
|
11454
|
+
})
|
|
11455
|
+
.catch((err) => {
|
|
11456
|
+
error("An error occurred while overriding (initialize)", err);
|
|
11457
|
+
notify.error("An error occurred while overriding (initialize)", { duration: 2000 });
|
|
11458
|
+
setGlobalConfig({ userOverrideActive: false, userOverride: undefined });
|
|
11459
|
+
});
|
|
11278
11460
|
}
|
|
11279
11461
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: OverrideUserDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
11280
11462
|
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "17.2.0", version: "20.3.18", type: OverrideUserDialogComponent, isStandalone: true, selector: "override-user-dialog", inputs: { overrideUser: { classPropertyName: "overrideUser", publicName: "overrideUser", isSignal: true, isRequired: false, transformFunction: null } }, outputs: { overrideUser: "overrideUserChange" }, providers: [provideTranslocoScope("dialogs")], viewQueries: [{ propertyName: "dialog", first: true, predicate: DialogComponent, descendants: true, isSignal: true }], ngImport: i0, template: `
|
|
@@ -15965,8 +16147,18 @@ const bodyInterceptorFn = (request, next) => {
|
|
|
15965
16147
|
};
|
|
15966
16148
|
|
|
15967
16149
|
/**
|
|
15968
|
-
*
|
|
15969
|
-
*
|
|
16150
|
+
* Marks a request that has already been retried once after a re-authentication attempt.
|
|
16151
|
+
* Carried on the request's HttpContext (not sent to the server) so a second 401 on the retry
|
|
16152
|
+
* propagates instead of triggering another sign-in — which would loop forever.
|
|
16153
|
+
*/
|
|
16154
|
+
const AUTH_RETRIED = new HttpContextToken(() => false);
|
|
16155
|
+
/**
|
|
16156
|
+
* Interceptor function that handles HTTP 401 errors by refreshing authentication and retrying the
|
|
16157
|
+
* original request once. For 403 errors, the error is propagated as a permanent auth failure.
|
|
16158
|
+
*
|
|
16159
|
+
* The retry happens ONLY when `signIn()` reports the user is authenticated, and AT MOST once per
|
|
16160
|
+
* request. Without these two guards a persistent 401 (credentials required, or an endpoint that
|
|
16161
|
+
* keeps rejecting even after a successful CSRF handshake) would retry endlessly.
|
|
15970
16162
|
*
|
|
15971
16163
|
* @param request - The HTTP request object.
|
|
15972
16164
|
* @param next - The HTTP handler function.
|
|
@@ -15979,14 +16171,23 @@ const errorInterceptorFn = (request, next) => {
|
|
|
15979
16171
|
}
|
|
15980
16172
|
return next(request).pipe(catchError$1((err) => {
|
|
15981
16173
|
if (err.status === 401) {
|
|
15982
|
-
|
|
15983
|
-
|
|
15984
|
-
|
|
15985
|
-
|
|
15986
|
-
|
|
15987
|
-
|
|
15988
|
-
|
|
15989
|
-
|
|
16174
|
+
// Already retried once after a re-auth — give up to avoid an infinite loop.
|
|
16175
|
+
if (request.context.get(AUTH_RETRIED)) {
|
|
16176
|
+
error("ErrorInterceptor: 401 again after re-auth retry, giving up");
|
|
16177
|
+
return throwError(() => err);
|
|
16178
|
+
}
|
|
16179
|
+
error("ErrorInterceptor: 401 detected, attempting to refresh auth");
|
|
16180
|
+
return runInInjectionContext(injector, () => from(signIn()).pipe(switchMap$1((authenticated) => {
|
|
16181
|
+
// signIn() could not authenticate (e.g. credentials required → redirected to login,
|
|
16182
|
+
// or a provider redirect is in progress). Do NOT retry — propagate the 401 instead,
|
|
16183
|
+
// otherwise every subsequent 401 re-triggers sign-in and loops.
|
|
16184
|
+
if (!authenticated) {
|
|
16185
|
+
error("ErrorInterceptor: re-auth did not authenticate, not retrying");
|
|
16186
|
+
return throwError(() => err);
|
|
16187
|
+
}
|
|
16188
|
+
error("Auth refreshed, retrying original request once");
|
|
16189
|
+
const headers = new HttpHeaders(createHeaders());
|
|
16190
|
+
return next(request.clone({ headers, context: request.context.set(AUTH_RETRIED, true) }));
|
|
15990
16191
|
}), catchError$1((signInErr) => {
|
|
15991
16192
|
error("Failed to refresh auth, redirecting to login", signInErr);
|
|
15992
16193
|
return throwError(() => signInErr);
|
|
@@ -16069,5 +16270,5 @@ const queryNameResolver = () => {
|
|
|
16069
16270
|
* Generated bundle index. Do not edit.
|
|
16070
16271
|
*/
|
|
16071
16272
|
|
|
16072
|
-
export { AGGREGATIONS_NAMES, AGGREGATIONS_NAMES_PRESET_DEFAULT, APP_FEATURES, AdvancedFiltersComponent, AdvancedSearch, AdvancedSearchComponent, AggregationComponent, AggregationDateComponent, AggregationDateRangeDialogComponent, AggregationListComponent, AggregationPanelComponent, AggregationTreeComponent, AggregationsService, AggregationsStore, Alert, AlertDialog, AlertsComponent, AppService, AppStore, ApplicationService, ApplicationStore, ArticleEntities, ArticleExtracts, ArticleLabels, ArticleSimilarDocuments, AsideFiltersComponent, AuditFeedbackType, AuditService, AuthGuard, AuthPageComponent, AutocompleteService, BOOKMARKS_CONFIG, BOOKMARKS_OPTIONS, BackdropComponent, BackdropService, BookmarkButtonComponent, BookmarksComponent, COLLECTIONS_CONFIG, COLLECTIONS_OPTIONS, COMPONENTS_FOR_DOCUMENT_TYPE, ChangePasswordComponent, ChildMarkerDirective, CollectionsComponent, CollectionsDialog, DRAWER_COMPONENT, DRAWER_STACK_MAX_COUNT, DateComponent, DeleteCollectionDialog, DidYouMeanComponent, DocumentLocatorComponent, DrawerAdvancedFiltersComponent, DrawerComponent, DrawerNavbarComponent, DrawerPreviewComponent, DrawerService, DrawerStackComponent, DrawerStackService, DropdownInputComponent, DropdownListComponent, ErrorComponent, ExportDialog, ExportService, FILTERS_BREAKPOINT, FILTER_DATE_ALLOW_CUSTOM_RANGE, FeedbackDialogComponent, FileSizePipe, FilterButtonComponent, FiltersBarComponent, HIGHLIGHTS, HighlightWordPipe, InfinityScrollDirective, InlineWorker, JsonMethodPluginService, KeyboardNavigatorDirective, LabelService, LabelsEditDialog, LoadingComponent, MetadataComponent, MissingTermsComponent, MoreButtonComponent, MoreComponent, MultiSelectLabelsComponent, MultiSelectionToolbarComponent, NON_SEARCHABLE_COLUMNS, NON_SEARCHABLE_DEFAULTS, NavbarTabsComponent, NavigationService, NoResultComponent, OpenArticleOnCtrlEnterDirective, OperatorPipe, OverflowItemDirective, OverflowManagerDirective, OverflowStopDirective, OverrideUserDialogComponent, PREVIEW_CONFIG, PagerComponent, PreviewNavigator, PreviewService, PrincipalService, PrincipalStore, QueryParamsStore, QueryService, RECENT_SEARCHES_CONFIG, RECENT_SEARCHES_OPTIONS, ROUTE_COMPONENTS, RecentSearchesComponent, ResetUserSettingsDialogComponent, SAVED_SEARCHES_CONFIG, SAVED_SEARCHES_OPTIONS, SavedSearchDialog, SavedSearchesComponent, SavedSearchesService, SearchFeedbackComponent, SearchInputFooter, SearchService, SelectArticleDirective, SelectArticleOnClickDirective, SelectionHistoryService, SelectionService, SelectionStore, ShowBookmarkDirective, SidebarNavComponent, SignInComponent, SortSelectorComponent, SourceComponent, SourceIconPipe, SponsoredResultsComponent, SyslangPipe, THEMES, TextChunkService, ThemeProviderDirective, ThemeSelectorComponent, ThemeStore, ThemeToggleComponent, TranslocoDateImpurePipe, UserProfileDialog, UserProfileFormComponent, UserProfileService, UserSettingsStore, applyThemeToNativeElement, auditInterceptorFn, authInterceptorFn, bodyInterceptorFn, buildQuery, debouncedSignal, errorInterceptorFn, getCurrentPath, getCurrentQueryName, getQueryNameFromRoute, injectRouteNavigation, processCssVars, queryNameResolver, signIn, themeColorNameToCssVariable, themeColorsToCssVariables, toastInterceptorFn, withAggregationsFeatures, withAlertsFeatures, withAppFeatures, withApplicationFeatures, withAssistantFeatures, withBasketsFeatures, withBookmarkFeatures, withBootstrapApp, withExtractsFeatures, withFetch, withMultiSelectionFeatures, withPrincipalFeatures, withQueryParamsFeatures, withRecentSearchesFeatures, withSavedSearchesFeatures, withSelectionFeatures, withThemeBodyHook, withThemes, withThemesFeatures, withUserSettingsFeatures };
|
|
16273
|
+
export { AGGREGATIONS_NAMES, AGGREGATIONS_NAMES_PRESET_DEFAULT, APP_FEATURES, AdvancedFiltersComponent, AdvancedSearch, AdvancedSearchComponent, AggregationComponent, AggregationDateComponent, AggregationDateRangeDialogComponent, AggregationListComponent, AggregationPanelComponent, AggregationTreeComponent, AggregationsService, AggregationsStore, Alert, AlertDialog, AlertsComponent, AppService, AppStore, ApplicationService, ApplicationStore, ArticleEntities, ArticleExtracts, ArticleLabels, ArticleSimilarDocuments, AsideFiltersComponent, AuditFeedbackType, AuditService, AuthGuard, AuthPageComponent, AutocompleteService, BOOKMARKS_CONFIG, BOOKMARKS_OPTIONS, BackdropComponent, BackdropService, BookmarkButtonComponent, BookmarksComponent, COLLECTIONS_CONFIG, COLLECTIONS_OPTIONS, COMPONENTS_FOR_DOCUMENT_TYPE, ChangePasswordComponent, ChildMarkerDirective, CollectionsComponent, CollectionsDialog, DRAWER_COMPONENT, DRAWER_STACK_MAX_COUNT, DateComponent, DeleteCollectionDialog, DidYouMeanComponent, DocumentLocatorComponent, DrawerAdvancedFiltersComponent, DrawerComponent, DrawerNavbarComponent, DrawerPreviewComponent, DrawerService, DrawerStackComponent, DrawerStackService, DropdownInputComponent, DropdownListComponent, ErrorComponent, ExportDialog, ExportService, FILTERS_BREAKPOINT, FILTER_DATE_ALLOW_CUSTOM_RANGE, FeedbackDialogComponent, FileSizePipe, FilterButtonComponent, FiltersBarComponent, HIGHLIGHTS, HighlightWordPipe, InfinityScrollDirective, InlineWorker, JsonMethodPluginService, KeyboardNavigatorDirective, LabelService, LabelsEditDialog, LoadingComponent, MetadataComponent, MissingTermsComponent, MoreButtonComponent, MoreComponent, MultiSelectLabelsComponent, MultiSelectionToolbarComponent, NON_SEARCHABLE_COLUMNS, NON_SEARCHABLE_DEFAULTS, NavbarTabsComponent, NavigationService, NoResultComponent, OpenArticleOnCtrlEnterDirective, OperatorPipe, OverflowItemDirective, OverflowManagerDirective, OverflowStopDirective, OverrideUserDialogComponent, PREVIEW_CONFIG, PagerComponent, PreviewNavigator, PreviewService, PrincipalService, PrincipalStore, QueryParamsStore, QueryService, RECENT_SEARCHES_CONFIG, RECENT_SEARCHES_OPTIONS, ROUTE_COMPONENTS, RecentSearchesComponent, ResetUserSettingsDialogComponent, SAVED_SEARCHES_CONFIG, SAVED_SEARCHES_OPTIONS, SavedSearchDialog, SavedSearchesComponent, SavedSearchesService, SearchFeedbackComponent, SearchInputFooter, SearchService, SelectArticleDirective, SelectArticleOnClickDirective, SelectionHistoryService, SelectionService, SelectionStore, ShowBookmarkDirective, SidebarNavComponent, SignInComponent, SortSelectorComponent, SourceComponent, SourceIconPipe, SponsoredResultsComponent, SyslangPipe, THEMES, TextChunkService, ThemeProviderDirective, ThemeSelectorComponent, ThemeStore, ThemeToggleComponent, TranslocoDateImpurePipe, UserProfileDialog, UserProfileFormComponent, UserProfileService, UserSettingsStore, applyThemeToNativeElement, auditInterceptorFn, authInterceptorFn, bodyInterceptorFn, bootstrapApp, buildQuery, debouncedSignal, errorInterceptorFn, getCurrentPath, getCurrentQueryName, getQueryNameFromRoute, injectRouteNavigation, processCssVars, queryNameResolver, signIn, themeColorNameToCssVariable, themeColorsToCssVariables, toastInterceptorFn, withAggregationsFeatures, withAlertsFeatures, withAppFeatures, withApplicationFeatures, withAssistantFeatures, withBasketsFeatures, withBookmarkFeatures, withBootstrapApp, withExtractsFeatures, withFetch, withMultiSelectionFeatures, withPrincipalFeatures, withQueryParamsFeatures, withRecentSearchesFeatures, withSavedSearchesFeatures, withSelectionFeatures, withThemeBodyHook, withThemes, withThemesFeatures, withUserSettingsFeatures };
|
|
16073
16274
|
//# sourceMappingURL=sinequa-atomic-angular.mjs.map
|