@sinequa/atomic-angular 1.0.14 → 1.0.16
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/fesm2022/sinequa-atomic-angular.mjs +401 -204
- package/fesm2022/sinequa-atomic-angular.mjs.map +1 -1
- package/index.d.ts +513 -26
- package/package.json +1 -1
|
@@ -1,14 +1,14 @@
|
|
|
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, ChangeDetectionStrategy, resource, ViewContainerRef, viewChildren, numberAttribute, afterRenderEffect, afterEveryRender } from '@angular/core';
|
|
3
|
-
import { BehaviorSubject, Subscription, catchError,
|
|
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, ChangeDetectionStrategy, resource, ViewContainerRef, viewChildren, numberAttribute, afterRenderEffect, afterEveryRender } from '@angular/core';
|
|
3
|
+
import { BehaviorSubject, Subscription, catchError, throwError, firstValueFrom, map, Subject, of, tap, EMPTY, filter, shareReplay, fromEvent, debounceTime, from, switchMap } from 'rxjs';
|
|
4
4
|
import { TranslocoService, TranslocoPipe, provideTranslocoScope } from '@jsverse/transloco';
|
|
5
|
-
import { DropdownComponent, DropdownContentComponent, InputComponent, ButtonComponent, cn, FaIconComponent, EllipsisIcon, ChevronRightIcon, MenuComponent, MenuContentComponent, MenuItemComponent, BadgeComponent, DialogComponent, DialogHeaderComponent, DialogTitleComponent, DialogContentComponent, DialogFooterComponent, ListItemComponent, SwitchComponent, SelectOptionDirective, DialogService, TabsComponent, TabsListComponent, TabComponent, ChevronLeftIconComponent, ChevronsLeftIconComponent, ChevronsRightIconComponent, Separator, SheetCloseDirective, SheetService, DateRangePickerDirective, DatepickerDirective, ButtonGroup, InputGroupInput, InputGroupComponent, InputGroupAddonComponent, SearchIcon, FilterIcon, LoadingCircleIconComponent, CircleCheckIconComponent, PopoverComponent,
|
|
5
|
+
import { DropdownComponent, DropdownContentComponent, InputComponent, ButtonComponent, cn, FaIconComponent, EllipsisIcon, ChevronRightIcon, MenuComponent, MenuContentComponent, MenuItemComponent, CardComponent, CardHeaderComponent, CardContentComponent, BadgeComponent, DialogComponent, DialogHeaderComponent, DialogTitleComponent, DialogContentComponent, DialogFooterComponent, ListItemComponent, SwitchComponent, SelectOptionDirective, DialogService, TabsComponent, TabsListComponent, TabComponent, ChevronLeftIconComponent, ChevronsLeftIconComponent, ChevronsRightIconComponent, Separator, SheetCloseDirective, SheetService, DateRangePickerDirective, DatepickerDirective, ButtonGroup, InputGroupInput, InputGroupComponent, InputGroupAddonComponent, SearchIcon, FilterIcon, LoadingCircleIconComponent, CircleCheckIconComponent, PopoverComponent, CardFooterComponent, BookmarkIcon, PopoverContentComponent, UserIcon, TrashIcon, FolderIcon, VerticalDividerComponent, BreakpointObserverService, HorizontalDividerComponent, FlagEnglishIconComponent, FlagFrenchIconComponent, EditIcon, UndoIcon, AvatarComponent, AvatarFallbackComponent, AvatarImageComponent } from '@sinequa/ui';
|
|
6
6
|
import highlightWords from 'highlight-words';
|
|
7
7
|
import { ActivatedRoute, Router, NavigationEnd, RouterLink, 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 { globalConfig, EngineType, extraColumns, sysLang, getQueryParamsFromUrl, clearSessionTokens, login, info,
|
|
11
|
-
import { HttpClient, HttpParams, httpResource, HttpResponse,
|
|
10
|
+
import { globalConfig, EngineType, extraColumns, sysLang, 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
|
+
import { HttpClient, HttpParams, httpResource, HttpResponse, HttpContextToken, HttpHeaders } from '@angular/common/http';
|
|
12
12
|
import { takeUntilDestroyed, toSignal } from '@angular/core/rxjs-interop';
|
|
13
13
|
import { DatePipe, DATE_PIPE_DEFAULT_TIMEZONE, DATE_PIPE_DEFAULT_OPTIONS, Location, NgTemplateOutlet, NgStyle, NgClass, NgComponentOutlet } from '@angular/common';
|
|
14
14
|
import { Title, DomSanitizer } from '@angular/platform-browser';
|
|
@@ -108,8 +108,9 @@ class AppService {
|
|
|
108
108
|
*
|
|
109
109
|
* @remarks
|
|
110
110
|
* This method constructs an HTTP GET request to fetch the application configuration
|
|
111
|
-
* using the `app` parameter from the global configuration. If the request fails,
|
|
112
|
-
*
|
|
111
|
+
* using the `app` parameter from the global configuration. If the request fails, it logs the
|
|
112
|
+
* error and re-throws a normalized `Error` carrying the server's `errorMessage` when available
|
|
113
|
+
* (e.g. "app not found: '...'"), so callers can surface the reason on the error page.
|
|
113
114
|
*
|
|
114
115
|
* @example
|
|
115
116
|
* ```typescript
|
|
@@ -121,9 +122,19 @@ class AppService {
|
|
|
121
122
|
getApp(appName) {
|
|
122
123
|
const app = appName || globalConfig.app;
|
|
123
124
|
const params = new HttpParams().set('app', app || '');
|
|
124
|
-
return this.http.get(this.API_URL + '/app', { params }).pipe(catchError(
|
|
125
|
-
console.error('AppService.getApp failure - error: ',
|
|
126
|
-
|
|
125
|
+
return this.http.get(this.API_URL + '/app', { params }).pipe(catchError((err) => {
|
|
126
|
+
console.error('AppService.getApp failure - error: ', err);
|
|
127
|
+
// Propagate the failure (previously swallowed with EMPTY, which hid the cause and surfaced a
|
|
128
|
+
// generic "no elements in sequence" downstream). Surface the Sinequa error-envelope message
|
|
129
|
+
// when present — e.g. a 500 with { errorMessage: "app not found: 'mint_rnd-yoyo'" } — so the
|
|
130
|
+
// bootstrap/login flow can route to the error page with the actual reason.
|
|
131
|
+
const e = err;
|
|
132
|
+
const message = e?.error?.errorMessage ??
|
|
133
|
+
e?.error?.message ??
|
|
134
|
+
e?.errorMessage ??
|
|
135
|
+
e?.message ??
|
|
136
|
+
'Failed to fetch application configuration';
|
|
137
|
+
return throwError(() => new Error(message));
|
|
127
138
|
}));
|
|
128
139
|
}
|
|
129
140
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: AppService, deps: [], target: i0.ɵɵFactoryTarget.Injectable });
|
|
@@ -1563,100 +1574,174 @@ function withThemes(app, themes) {
|
|
|
1563
1574
|
}
|
|
1564
1575
|
|
|
1565
1576
|
/**
|
|
1566
|
-
* Signs
|
|
1577
|
+
* Signs the user in according to the resolved {@link globalConfig.authMode}.
|
|
1578
|
+
*
|
|
1579
|
+
* The mode is expected to be resolved beforehand (by `initializeAppConfig`, awaited in
|
|
1580
|
+
* `bootstrapApp`). This function clears any existing session, then:
|
|
1581
|
+
* - `credentials` → redirect to the login form;
|
|
1582
|
+
* - `sso` → reload the page so the browser/proxy performs the handshake;
|
|
1583
|
+
* - `oauth` / `saml` → delegate to `login()`, which redirects to the provider;
|
|
1584
|
+
* - `bearer` → delegate to `login()`;
|
|
1585
|
+
* - `unknown` → `login()` tries SSO silently then resolves to credentials; on failure the login
|
|
1586
|
+
* form is shown.
|
|
1567
1587
|
*
|
|
1568
|
-
*
|
|
1569
|
-
* global configuration to determine whether to use credentials-based authentication or Single Sign-On (SSO). If
|
|
1570
|
-
* credentials are used, it redirects the user to the login page. If SSO is enabled, it reloads the page to trigger
|
|
1571
|
-
* the SSO login process. If neither method is specified, it attempts a standard login and handles any errors that
|
|
1572
|
-
* may occur during the process.
|
|
1573
|
-
* @returns A promise resolving to a boolean indicating the success of the sign-in operation.
|
|
1588
|
+
* @returns A promise resolving to a boolean indicating whether the user is authenticated.
|
|
1574
1589
|
*/
|
|
1575
1590
|
async function signIn() {
|
|
1576
1591
|
assertInInjectionContext(signIn);
|
|
1577
1592
|
const router = inject(Router);
|
|
1578
1593
|
const lastUrlAfterNavigation = inject(NavigationService).urlAfterNavigation;
|
|
1579
|
-
const {
|
|
1594
|
+
const { loginPath, authMode } = globalConfig;
|
|
1580
1595
|
// Always clear authentication tokens first to clear any existing session
|
|
1581
1596
|
clearSessionTokens();
|
|
1582
|
-
//
|
|
1583
|
-
if (
|
|
1597
|
+
// Credentials: show the login form.
|
|
1598
|
+
if (authMode?.kind === "credentials") {
|
|
1584
1599
|
router.navigate([loginPath], { queryParams: { returnUrl: lastUrlAfterNavigation } });
|
|
1585
1600
|
return false; // prevent further execution
|
|
1586
1601
|
}
|
|
1587
|
-
// SSO
|
|
1588
|
-
|
|
1589
|
-
if (useSSO) {
|
|
1590
|
-
// reload the page to trigger SSO login
|
|
1602
|
+
// SSO: the browser/proxy handles authentication — reload to trigger it.
|
|
1603
|
+
if (authMode?.kind === "sso") {
|
|
1591
1604
|
window.location.reload();
|
|
1592
1605
|
return false; // prevent further execution
|
|
1593
1606
|
}
|
|
1594
|
-
//
|
|
1607
|
+
// oauth / saml / bearer / unknown — let login() drive the handshake.
|
|
1595
1608
|
try {
|
|
1596
1609
|
const response = await login();
|
|
1597
1610
|
if (response) {
|
|
1598
1611
|
info("Response from login", response);
|
|
1599
1612
|
return true;
|
|
1600
1613
|
}
|
|
1601
|
-
|
|
1602
|
-
|
|
1614
|
+
// Not authenticated. For provider redirects (oauth/saml) the page is already navigating away,
|
|
1615
|
+
// so we don't touch the router. Otherwise (unknown resolved to credentials, or bearer failed)
|
|
1616
|
+
// show the login form.
|
|
1617
|
+
if (authMode?.kind !== "oauth" && authMode?.kind !== "saml") {
|
|
1618
|
+
router.navigate([loginPath], { queryParams: { returnUrl: lastUrlAfterNavigation } });
|
|
1603
1619
|
}
|
|
1620
|
+
return false;
|
|
1604
1621
|
}
|
|
1605
1622
|
catch (err) {
|
|
1606
1623
|
error("Error during login", err);
|
|
1607
|
-
|
|
1624
|
+
// A 401 is recoverable: the user just needs to authenticate → show the login form.
|
|
1625
|
+
if (err?.status === 401) {
|
|
1608
1626
|
error("Unauthorized access - please check your credentials:", err?.errorMessage);
|
|
1609
|
-
router.navigate([loginPath]);
|
|
1627
|
+
router.navigate([loginPath], { queryParams: { returnUrl: lastUrlAfterNavigation } });
|
|
1628
|
+
return false;
|
|
1610
1629
|
}
|
|
1611
|
-
|
|
1630
|
+
// Any other error (e.g. a misconfigured OAuth/SAML provider → 5xx) is fatal and not something
|
|
1631
|
+
// the user can resolve from the login form. Stop here: navigate straight to the error page with
|
|
1632
|
+
// the reason. We return false (instead of rethrowing) so the application does NOT initialize and
|
|
1633
|
+
// fire further authenticated API calls (e.g. usersettings → 401).
|
|
1634
|
+
const message = (err?.errorMessage ?? err?.message) || undefined;
|
|
1635
|
+
router.navigate(["/error"], { queryParams: { message } });
|
|
1636
|
+
return false;
|
|
1612
1637
|
}
|
|
1613
|
-
return false; // prevent further execution
|
|
1614
1638
|
}
|
|
1615
1639
|
|
|
1616
1640
|
/**
|
|
1617
1641
|
* Bootstraps the application by ensuring the user is authenticated and initializing the application.
|
|
1618
1642
|
*
|
|
1619
|
-
* This function first attempts to sign in the user
|
|
1620
|
-
* it
|
|
1621
|
-
*
|
|
1643
|
+
* This function first attempts to sign in the user via `signIn()`. If authentication is successful,
|
|
1644
|
+
* it initializes the application (stores and, optionally, dynamic routes) through `ApplicationService`
|
|
1645
|
+
* and waits for the initialization to complete before resolving. Any errors during authentication or
|
|
1646
|
+
* initialization are logged to the console, but the returned promise never rejects.
|
|
1647
|
+
*
|
|
1648
|
+
* Note: this function relies on Angular's injection context, so it must be called within one
|
|
1649
|
+
* (e.g. from `provideAppInitializer`).
|
|
1622
1650
|
*
|
|
1623
|
-
* @param
|
|
1624
|
-
* @param
|
|
1625
|
-
* @returns A promise that resolves when the application
|
|
1651
|
+
* @param options - Configuration options for the bootstrap process.
|
|
1652
|
+
* @param options.createRoutes - Whether to create routes during initialization. Defaults to `true`.
|
|
1653
|
+
* @returns A promise that resolves to `true` when the application has been fully initialized,
|
|
1654
|
+
* or `false` when the user is not authenticated or an error occurred.
|
|
1626
1655
|
*/
|
|
1627
|
-
async function
|
|
1628
|
-
|
|
1629
|
-
//
|
|
1656
|
+
async function bootstrapApp({ createRoutes = true } = {}) {
|
|
1657
|
+
assertInInjectionContext(bootstrapApp);
|
|
1658
|
+
// Capture the injector synchronously: Angular's injection context does not survive across an
|
|
1659
|
+
// `await`, so after awaiting `initializeAppConfig()` we must re-enter it to inject services and
|
|
1660
|
+
// run `signIn()` (which use `inject()` internally).
|
|
1661
|
+
const injector = inject(EnvironmentInjector);
|
|
1662
|
+
// set the createRoutes flag early so it is defined even when sign-in fails
|
|
1663
|
+
// (ApplicationService.initialize() sets it again when it runs)
|
|
1630
1664
|
setGlobalConfig({ createRoutes });
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
|
|
1644
|
-
|
|
1645
|
-
|
|
1646
|
-
|
|
1647
|
-
|
|
1648
|
-
|
|
1649
|
-
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1665
|
+
// Resolve the authentication mode (queries the server pre-login when needed) BEFORE signing in.
|
|
1666
|
+
// Doing it here — rather than as a separate, concurrent APP_INITIALIZER — guarantees that
|
|
1667
|
+
// signIn() reads a fully resolved `authMode` and removes the long-standing bootstrap race.
|
|
1668
|
+
// Crucially, this also sets `backendUrl` BEFORE any service/store is constructed, so those that
|
|
1669
|
+
// derive their API URL from `globalConfig.backendUrl` (e.g. PrincipalStore) don't capture
|
|
1670
|
+
// `undefined` (which produced requests to `/undefined/api/v1/...`).
|
|
1671
|
+
try {
|
|
1672
|
+
await initializeAppConfig();
|
|
1673
|
+
}
|
|
1674
|
+
catch (err) {
|
|
1675
|
+
error('Error while initializing application config:', err);
|
|
1676
|
+
// Pre-login failed (e.g. a 500 "app not found: 'mint_rnd-yoyo'" for a bad app name). This is
|
|
1677
|
+
// fatal — backendUrl/authMode can't be resolved — so route to the error page with the reason
|
|
1678
|
+
// instead of continuing to signIn() (which would otherwise fail with a less meaningful error).
|
|
1679
|
+
const message = err?.errorMessage
|
|
1680
|
+
?? err?.message;
|
|
1681
|
+
runInInjectionContext(injector, () => inject(Router).navigate(['/error'], { queryParams: { message } }));
|
|
1682
|
+
return false;
|
|
1683
|
+
}
|
|
1684
|
+
// Inject ApplicationService AFTER initializeAppConfig (re-entering the injection context lost
|
|
1685
|
+
// across the await), so its dependent services/stores are constructed with `backendUrl` set.
|
|
1686
|
+
const applicationService = runInInjectionContext(injector, () => inject(ApplicationService));
|
|
1687
|
+
let authenticated = false;
|
|
1688
|
+
try {
|
|
1689
|
+
// Re-enter the injection context lost across the await above.
|
|
1690
|
+
authenticated = await runInInjectionContext(injector, () => signIn());
|
|
1691
|
+
}
|
|
1692
|
+
catch (err) {
|
|
1693
|
+
error('Error while signing in:', err);
|
|
1694
|
+
return false;
|
|
1695
|
+
}
|
|
1696
|
+
if (!authenticated) {
|
|
1697
|
+
info('User not authenticated, skipping application initialization.');
|
|
1698
|
+
return false;
|
|
1699
|
+
}
|
|
1700
|
+
info('User authenticated, initializing application...');
|
|
1701
|
+
try {
|
|
1702
|
+
await applicationService.initialize(createRoutes);
|
|
1703
|
+
info(`Application initialized successfully (createRoutes: ${createRoutes}).`);
|
|
1704
|
+
return true;
|
|
1705
|
+
}
|
|
1706
|
+
catch (err) {
|
|
1707
|
+
error(`Error initializing application (createRoutes: ${createRoutes}):`, err);
|
|
1708
|
+
// Authenticated, but the application failed to initialize (e.g. fetchApp/usersettings failed).
|
|
1709
|
+
// Route to the error page with the failure reason instead of leaving a half-initialized app.
|
|
1710
|
+
const message = err?.errorMessage
|
|
1711
|
+
?? err?.message;
|
|
1712
|
+
runInInjectionContext(injector, () => inject(Router).navigate(['/error'], { queryParams: { message } }));
|
|
1713
|
+
return false;
|
|
1714
|
+
}
|
|
1715
|
+
}
|
|
1716
|
+
/**
|
|
1717
|
+
* Bootstraps the application by ensuring the user is authenticated and initializing the application.
|
|
1718
|
+
*
|
|
1719
|
+
* @deprecated Use {@link bootstrapApp} instead, and let it inject `ApplicationService` itself.
|
|
1720
|
+
*
|
|
1721
|
+
* Migration — in your `app.config.ts`, replace:
|
|
1722
|
+
* ```ts
|
|
1723
|
+
* // ❌ Deprecated: eagerly injecting ApplicationService in the factory constructs it (and its
|
|
1724
|
+
* // dependent stores/services) BEFORE bootstrapApp resolves the config, so services that build
|
|
1725
|
+
* // their API URL from `globalConfig.backendUrl` capture `undefined` (→ `/undefined/api/v1/...`).
|
|
1726
|
+
* provideAppInitializer(() => withBootstrapApp(inject(ApplicationService), { createRoutes: true })),
|
|
1727
|
+
* ```
|
|
1728
|
+
* with:
|
|
1729
|
+
* ```ts
|
|
1730
|
+
* // ✅ bootstrapApp injects ApplicationService internally, AFTER initializeAppConfig() has set
|
|
1731
|
+
* // `backendUrl` and resolved the auth mode.
|
|
1732
|
+
* provideAppInitializer(() => bootstrapApp({ createRoutes: true })),
|
|
1733
|
+
* ```
|
|
1734
|
+
* (Remove the now-unused `inject` / `ApplicationService` imports.)
|
|
1735
|
+
*
|
|
1736
|
+
* @param applicationService - Ignored; kept for backward compatibility. `ApplicationService` is
|
|
1737
|
+
* provided in root and injected internally by {@link bootstrapApp}, so the instance is the same.
|
|
1738
|
+
* Passing `inject(ApplicationService)` here is discouraged — see the migration note above.
|
|
1739
|
+
* @param options - Configuration options for the bootstrap process.
|
|
1740
|
+
* @param options.createRoutes - Whether to create routes during initialization. Defaults to `true`.
|
|
1741
|
+
* @returns A promise that resolves when the bootstrap process is complete, regardless of success or failure.
|
|
1742
|
+
*/
|
|
1743
|
+
async function withBootstrapApp(_applicationService, { createRoutes = true } = {}) {
|
|
1744
|
+
await bootstrapApp({ createRoutes });
|
|
1660
1745
|
}
|
|
1661
1746
|
|
|
1662
1747
|
/**
|
|
@@ -3415,12 +3500,13 @@ function AuthGuard() {
|
|
|
3415
3500
|
return async (_, state) => {
|
|
3416
3501
|
const router = inject(Router);
|
|
3417
3502
|
const principalStore = inject(PrincipalStore);
|
|
3418
|
-
const { loginPath,
|
|
3503
|
+
const { loginPath, authMode } = globalConfig;
|
|
3419
3504
|
if (state.url.startsWith("/login"))
|
|
3420
3505
|
return true;
|
|
3421
3506
|
// If the user is not authenticated, navigate to the login page.
|
|
3422
3507
|
// The login page handles every authentication method (credentials, OAuth, SAML).
|
|
3423
|
-
|
|
3508
|
+
// In SSO mode the browser/proxy carries the auth, so we let it through.
|
|
3509
|
+
if (!isAuthenticated() && authMode?.kind !== "sso") {
|
|
3424
3510
|
router.navigate([loginPath], { queryParams: { returnUrl: state.url } });
|
|
3425
3511
|
return false;
|
|
3426
3512
|
}
|
|
@@ -3446,7 +3532,7 @@ function AuthGuard() {
|
|
|
3446
3532
|
// check if the password is expired and if the partition is editable
|
|
3447
3533
|
// changing password is only possible when user use credentials to auhtenticate
|
|
3448
3534
|
// only in credentials mode
|
|
3449
|
-
if (
|
|
3535
|
+
if (authMode?.kind === "credentials") {
|
|
3450
3536
|
const exp = passwordExpirationDate;
|
|
3451
3537
|
const editable = !!editablePartition;
|
|
3452
3538
|
if (editable && exp && isExpired(exp)) {
|
|
@@ -5271,101 +5357,160 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
5271
5357
|
}], 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 }] }] } });
|
|
5272
5358
|
|
|
5273
5359
|
class ErrorComponent {
|
|
5360
|
+
route = inject(ActivatedRoute);
|
|
5274
5361
|
router = inject(Router);
|
|
5275
|
-
|
|
5362
|
+
/**
|
|
5363
|
+
* Human-readable error detail shown on the page, taken from the `message` query param.
|
|
5364
|
+
* Callers navigating here can pass it, e.g. on an auth failure:
|
|
5365
|
+
* `router.navigate(["error"], { queryParams: { message: err.message } })`.
|
|
5366
|
+
*/
|
|
5367
|
+
message = (() => {
|
|
5368
|
+
const value = this.route.snapshot.queryParams['message'];
|
|
5369
|
+
return typeof value === 'string' && value.trim().length ? value : undefined;
|
|
5370
|
+
})();
|
|
5371
|
+
/** Navigate home and re-bootstrap the application (fresh authentication attempt). */
|
|
5372
|
+
goHome() {
|
|
5276
5373
|
this.router.navigate(['/']).then(() => window.location.reload());
|
|
5277
5374
|
}
|
|
5278
5375
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: ErrorComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
5279
|
-
static ɵcmp = i0.ɵɵngDeclareComponent({ minVersion: "
|
|
5280
|
-
<
|
|
5281
|
-
|
|
5282
|
-
|
|
5283
|
-
|
|
5284
|
-
|
|
5285
|
-
|
|
5286
|
-
viewBox="0 0 24 24"
|
|
5287
|
-
fill="none"
|
|
5288
|
-
stroke="currentColor"
|
|
5289
|
-
stroke-width="2"
|
|
5290
|
-
stroke-linecap="round"
|
|
5291
|
-
stroke-linejoin="round">
|
|
5292
|
-
<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" />
|
|
5293
|
-
<path d="M12 9v4" />
|
|
5294
|
-
<path d="M12 17h.01" />
|
|
5295
|
-
</svg>
|
|
5296
|
-
<h1 class="mb-4 text-4xl font-bold">Oops! Something went wrong</h1>
|
|
5297
|
-
<p class="text-muted-foreground mb-8 text-xl">We apologize for the inconvenience.</p>
|
|
5298
|
-
<div class="flex space-x-4">
|
|
5299
|
-
<button
|
|
5300
|
-
(click)="reload()"
|
|
5301
|
-
class="btn btn-outline rounded-lg border-neutral-300 text-neutral-700 transition hover:bg-neutral-200 hover:text-neutral-700">
|
|
5376
|
+
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: `
|
|
5377
|
+
<Card
|
|
5378
|
+
hover="no"
|
|
5379
|
+
class="bg-background border-(--popover-border) w-full max-w-md rounded-3xl border shadow-2xl">
|
|
5380
|
+
<CardHeader class="flex flex-col items-center gap-4 pt-8 text-center">
|
|
5381
|
+
<span
|
|
5382
|
+
class="bg-destructive/10 text-destructive inline-flex h-16 w-16 items-center justify-center rounded-full">
|
|
5302
5383
|
<svg
|
|
5303
|
-
class="mr-2 h-4 w-4"
|
|
5304
5384
|
xmlns="http://www.w3.org/2000/svg"
|
|
5305
|
-
|
|
5306
|
-
height="24"
|
|
5385
|
+
class="h-8 w-8"
|
|
5307
5386
|
viewBox="0 0 24 24"
|
|
5308
5387
|
fill="none"
|
|
5309
5388
|
stroke="currentColor"
|
|
5310
5389
|
stroke-width="2"
|
|
5311
5390
|
stroke-linecap="round"
|
|
5312
|
-
stroke-linejoin="round"
|
|
5313
|
-
|
|
5314
|
-
<path d="
|
|
5391
|
+
stroke-linejoin="round"
|
|
5392
|
+
aria-hidden="true">
|
|
5393
|
+
<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" />
|
|
5394
|
+
<path d="M12 9v4" />
|
|
5395
|
+
<path d="M12 17h.01" />
|
|
5315
5396
|
</svg>
|
|
5316
|
-
|
|
5317
|
-
|
|
5318
|
-
|
|
5319
|
-
|
|
5320
|
-
|
|
5397
|
+
</span>
|
|
5398
|
+
|
|
5399
|
+
<div class="grid gap-1.5">
|
|
5400
|
+
<h1 class="text-2xl font-semibold tracking-tight">Something went wrong</h1>
|
|
5401
|
+
<p class="text-muted-foreground text-sm">
|
|
5402
|
+
We're sorry — the application ran into a problem and couldn't continue.
|
|
5403
|
+
</p>
|
|
5404
|
+
</div>
|
|
5405
|
+
</CardHeader>
|
|
5406
|
+
|
|
5407
|
+
<CardContent class="grid gap-5 pb-8">
|
|
5408
|
+
@if (message) {
|
|
5409
|
+
<div class="border-border bg-muted/40 grid gap-1 rounded-lg border p-3 text-left">
|
|
5410
|
+
<span class="text-muted-foreground text-[0.7rem] font-medium uppercase tracking-wider">
|
|
5411
|
+
Details
|
|
5412
|
+
</span>
|
|
5413
|
+
<p class="text-foreground text-sm break-words" role="alert">{{ message }}</p>
|
|
5414
|
+
</div>
|
|
5415
|
+
}
|
|
5416
|
+
|
|
5417
|
+
<div class="flex justify-center">
|
|
5418
|
+
<button variant="primary" (click)="goHome()">
|
|
5419
|
+
<svg
|
|
5420
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
5421
|
+
class="mr-2 h-4 w-4"
|
|
5422
|
+
viewBox="0 0 24 24"
|
|
5423
|
+
fill="none"
|
|
5424
|
+
stroke="currentColor"
|
|
5425
|
+
stroke-width="2"
|
|
5426
|
+
stroke-linecap="round"
|
|
5427
|
+
stroke-linejoin="round"
|
|
5428
|
+
aria-hidden="true">
|
|
5429
|
+
<path d="M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8" />
|
|
5430
|
+
<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" />
|
|
5431
|
+
</svg>
|
|
5432
|
+
Go to homepage
|
|
5433
|
+
</button>
|
|
5434
|
+
</div>
|
|
5435
|
+
</CardContent>
|
|
5436
|
+
</Card>
|
|
5437
|
+
`, isInline: true, dependencies: [{ kind: "directive", type: ButtonComponent, selector: "button", inputs: ["class", "variant", "decoration", "scheme", "iconOnly", "size"] }, { 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"] }] });
|
|
5321
5438
|
}
|
|
5322
5439
|
i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: ErrorComponent, decorators: [{
|
|
5323
5440
|
type: Component,
|
|
5324
5441
|
args: [{
|
|
5325
5442
|
selector: 'error-component, ErrorComponent',
|
|
5326
5443
|
standalone: true,
|
|
5327
|
-
imports: [
|
|
5444
|
+
imports: [
|
|
5445
|
+
ButtonComponent,
|
|
5446
|
+
CardComponent,
|
|
5447
|
+
CardHeaderComponent,
|
|
5448
|
+
CardContentComponent
|
|
5449
|
+
],
|
|
5450
|
+
host: {
|
|
5451
|
+
class: 'bg-background text-foreground grid min-h-dvh w-full place-content-center p-6'
|
|
5452
|
+
},
|
|
5328
5453
|
template: `
|
|
5329
|
-
<
|
|
5330
|
-
|
|
5331
|
-
|
|
5332
|
-
|
|
5333
|
-
|
|
5334
|
-
|
|
5335
|
-
viewBox="0 0 24 24"
|
|
5336
|
-
fill="none"
|
|
5337
|
-
stroke="currentColor"
|
|
5338
|
-
stroke-width="2"
|
|
5339
|
-
stroke-linecap="round"
|
|
5340
|
-
stroke-linejoin="round">
|
|
5341
|
-
<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" />
|
|
5342
|
-
<path d="M12 9v4" />
|
|
5343
|
-
<path d="M12 17h.01" />
|
|
5344
|
-
</svg>
|
|
5345
|
-
<h1 class="mb-4 text-4xl font-bold">Oops! Something went wrong</h1>
|
|
5346
|
-
<p class="text-muted-foreground mb-8 text-xl">We apologize for the inconvenience.</p>
|
|
5347
|
-
<div class="flex space-x-4">
|
|
5348
|
-
<button
|
|
5349
|
-
(click)="reload()"
|
|
5350
|
-
class="btn btn-outline rounded-lg border-neutral-300 text-neutral-700 transition hover:bg-neutral-200 hover:text-neutral-700">
|
|
5454
|
+
<Card
|
|
5455
|
+
hover="no"
|
|
5456
|
+
class="bg-background border-(--popover-border) w-full max-w-md rounded-3xl border shadow-2xl">
|
|
5457
|
+
<CardHeader class="flex flex-col items-center gap-4 pt-8 text-center">
|
|
5458
|
+
<span
|
|
5459
|
+
class="bg-destructive/10 text-destructive inline-flex h-16 w-16 items-center justify-center rounded-full">
|
|
5351
5460
|
<svg
|
|
5352
|
-
class="mr-2 h-4 w-4"
|
|
5353
5461
|
xmlns="http://www.w3.org/2000/svg"
|
|
5354
|
-
|
|
5355
|
-
height="24"
|
|
5462
|
+
class="h-8 w-8"
|
|
5356
5463
|
viewBox="0 0 24 24"
|
|
5357
5464
|
fill="none"
|
|
5358
5465
|
stroke="currentColor"
|
|
5359
5466
|
stroke-width="2"
|
|
5360
5467
|
stroke-linecap="round"
|
|
5361
|
-
stroke-linejoin="round"
|
|
5362
|
-
|
|
5363
|
-
<path d="
|
|
5468
|
+
stroke-linejoin="round"
|
|
5469
|
+
aria-hidden="true">
|
|
5470
|
+
<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" />
|
|
5471
|
+
<path d="M12 9v4" />
|
|
5472
|
+
<path d="M12 17h.01" />
|
|
5364
5473
|
</svg>
|
|
5365
|
-
|
|
5366
|
-
|
|
5367
|
-
|
|
5368
|
-
|
|
5474
|
+
</span>
|
|
5475
|
+
|
|
5476
|
+
<div class="grid gap-1.5">
|
|
5477
|
+
<h1 class="text-2xl font-semibold tracking-tight">Something went wrong</h1>
|
|
5478
|
+
<p class="text-muted-foreground text-sm">
|
|
5479
|
+
We're sorry — the application ran into a problem and couldn't continue.
|
|
5480
|
+
</p>
|
|
5481
|
+
</div>
|
|
5482
|
+
</CardHeader>
|
|
5483
|
+
|
|
5484
|
+
<CardContent class="grid gap-5 pb-8">
|
|
5485
|
+
@if (message) {
|
|
5486
|
+
<div class="border-border bg-muted/40 grid gap-1 rounded-lg border p-3 text-left">
|
|
5487
|
+
<span class="text-muted-foreground text-[0.7rem] font-medium uppercase tracking-wider">
|
|
5488
|
+
Details
|
|
5489
|
+
</span>
|
|
5490
|
+
<p class="text-foreground text-sm break-words" role="alert">{{ message }}</p>
|
|
5491
|
+
</div>
|
|
5492
|
+
}
|
|
5493
|
+
|
|
5494
|
+
<div class="flex justify-center">
|
|
5495
|
+
<button variant="primary" (click)="goHome()">
|
|
5496
|
+
<svg
|
|
5497
|
+
xmlns="http://www.w3.org/2000/svg"
|
|
5498
|
+
class="mr-2 h-4 w-4"
|
|
5499
|
+
viewBox="0 0 24 24"
|
|
5500
|
+
fill="none"
|
|
5501
|
+
stroke="currentColor"
|
|
5502
|
+
stroke-width="2"
|
|
5503
|
+
stroke-linecap="round"
|
|
5504
|
+
stroke-linejoin="round"
|
|
5505
|
+
aria-hidden="true">
|
|
5506
|
+
<path d="M15 21v-8a1 1 0 0 0-1-1h-4a1 1 0 0 0-1 1v8" />
|
|
5507
|
+
<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" />
|
|
5508
|
+
</svg>
|
|
5509
|
+
Go to homepage
|
|
5510
|
+
</button>
|
|
5511
|
+
</div>
|
|
5512
|
+
</CardContent>
|
|
5513
|
+
</Card>
|
|
5369
5514
|
`
|
|
5370
5515
|
}]
|
|
5371
5516
|
}] });
|
|
@@ -7216,6 +7361,7 @@ class NavbarTabsComponent {
|
|
|
7216
7361
|
class = input(...(ngDevMode ? [undefined, { debugName: "class" }] : []));
|
|
7217
7362
|
router = inject(Router);
|
|
7218
7363
|
route = inject(ActivatedRoute);
|
|
7364
|
+
appStore = inject(AppStore);
|
|
7219
7365
|
queryParamsStore = inject(QueryParamsStore);
|
|
7220
7366
|
// Injecting the QueryService to access last search results
|
|
7221
7367
|
queryService = inject(QueryService);
|
|
@@ -7283,6 +7429,37 @@ class NavbarTabsComponent {
|
|
|
7283
7429
|
}, ...(ngDevMode ? [{ debugName: "tabs" }] : []));
|
|
7284
7430
|
moreTabs = computed(() => this.tabs().slice(this.visibleTabCount()), ...(ngDevMode ? [{ debugName: "moreTabs" }] : []));
|
|
7285
7431
|
changeTab() { }
|
|
7432
|
+
// Computed signal to get the persistFiltersAcrossTabs value from AppStore
|
|
7433
|
+
persistFiltersAcrossTabs = computed(() => !!this.appStore.general()?.features?.persistFiltersAcrossTabs, ...(ngDevMode ? [{ debugName: "persistFiltersAcrossTabs" }] : []));
|
|
7434
|
+
// Determine how query params are applied on tab change.
|
|
7435
|
+
// - persist: 'merge' so the tab identity params (n/t/q) are updated while
|
|
7436
|
+
// the filter params (f/sort/id/page) already in the URL are kept.
|
|
7437
|
+
// - default: 'replace' so everything is rewritten from scratch.
|
|
7438
|
+
getQueryParamsHandling() {
|
|
7439
|
+
return this.persistFiltersAcrossTabs() ? 'merge' : 'replace';
|
|
7440
|
+
}
|
|
7441
|
+
// Get query params conditionally
|
|
7442
|
+
getQueryParams(tab) {
|
|
7443
|
+
if (this.persistFiltersAcrossTabs()) {
|
|
7444
|
+
// When preserving filters, still update the tab identity params so the
|
|
7445
|
+
// store rebuilds the query for the new tab. 'merge' keeps f/sort/id/page.
|
|
7446
|
+
return {
|
|
7447
|
+
n: tab.queryName,
|
|
7448
|
+
q: this.searchText(),
|
|
7449
|
+
t: tab.wsQueryTab
|
|
7450
|
+
};
|
|
7451
|
+
}
|
|
7452
|
+
// When replacing, explicitly set the filters to undefined to clear them
|
|
7453
|
+
return {
|
|
7454
|
+
n: tab.queryName,
|
|
7455
|
+
q: this.searchText(),
|
|
7456
|
+
t: tab.wsQueryTab,
|
|
7457
|
+
f: undefined,
|
|
7458
|
+
sort: undefined,
|
|
7459
|
+
id: undefined,
|
|
7460
|
+
page: undefined
|
|
7461
|
+
};
|
|
7462
|
+
}
|
|
7286
7463
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: NavbarTabsComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
7287
7464
|
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: `
|
|
7288
7465
|
<!-- do not display the tabs if there are no tabs -->
|
|
@@ -7302,9 +7479,10 @@ class NavbarTabsComponent {
|
|
|
7302
7479
|
[attr.disabled]="showCount() && tab.count === 0 ? '' : null"
|
|
7303
7480
|
[active]="this.currentPath() === tab.path"
|
|
7304
7481
|
[routerLink]="[tab.routerLink]"
|
|
7305
|
-
[queryParams]="
|
|
7482
|
+
[queryParams]="getQueryParams(tab)"
|
|
7483
|
+
[queryParamsHandling]="getQueryParamsHandling()"
|
|
7306
7484
|
(click)="changeTab()"
|
|
7307
|
-
(keydown.enter)="router.navigate([tab.routerLink], { queryParams:
|
|
7485
|
+
(keydown.enter)="router.navigate([tab.routerLink], { queryParams: getQueryParams(tab), queryParamsHandling: getQueryParamsHandling() })"
|
|
7308
7486
|
>
|
|
7309
7487
|
<div [class]="cn('flex items-center content-start w-full gap-1', !noTruncate() && 'overflow-hidden min-w-0')">
|
|
7310
7488
|
@if (tab.icon) {
|
|
@@ -7338,7 +7516,8 @@ class NavbarTabsComponent {
|
|
|
7338
7516
|
<a
|
|
7339
7517
|
class="inline-block whitespace-nowrap first-letter:capitalize"
|
|
7340
7518
|
[routerLink]="[tab.routerLink]"
|
|
7341
|
-
[queryParams]="
|
|
7519
|
+
[queryParams]="getQueryParams(tab)"
|
|
7520
|
+
[queryParamsHandling]="getQueryParamsHandling()"
|
|
7342
7521
|
[attr.aria-selected]="this.currentPath() === tab.path"
|
|
7343
7522
|
[attr.aria-label]="tab.display | syslang | transloco"
|
|
7344
7523
|
(click)="changeTab()">
|
|
@@ -7396,9 +7575,10 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
7396
7575
|
[attr.disabled]="showCount() && tab.count === 0 ? '' : null"
|
|
7397
7576
|
[active]="this.currentPath() === tab.path"
|
|
7398
7577
|
[routerLink]="[tab.routerLink]"
|
|
7399
|
-
[queryParams]="
|
|
7578
|
+
[queryParams]="getQueryParams(tab)"
|
|
7579
|
+
[queryParamsHandling]="getQueryParamsHandling()"
|
|
7400
7580
|
(click)="changeTab()"
|
|
7401
|
-
(keydown.enter)="router.navigate([tab.routerLink], { queryParams:
|
|
7581
|
+
(keydown.enter)="router.navigate([tab.routerLink], { queryParams: getQueryParams(tab), queryParamsHandling: getQueryParamsHandling() })"
|
|
7402
7582
|
>
|
|
7403
7583
|
<div [class]="cn('flex items-center content-start w-full gap-1', !noTruncate() && 'overflow-hidden min-w-0')">
|
|
7404
7584
|
@if (tab.icon) {
|
|
@@ -7432,7 +7612,8 @@ i0.ɵɵngDeclareClassMetadata({ minVersion: "12.0.0", version: "20.3.18", ngImpo
|
|
|
7432
7612
|
<a
|
|
7433
7613
|
class="inline-block whitespace-nowrap first-letter:capitalize"
|
|
7434
7614
|
[routerLink]="[tab.routerLink]"
|
|
7435
|
-
[queryParams]="
|
|
7615
|
+
[queryParams]="getQueryParams(tab)"
|
|
7616
|
+
[queryParamsHandling]="getQueryParamsHandling()"
|
|
7436
7617
|
[attr.aria-selected]="this.currentPath() === tab.path"
|
|
7437
7618
|
[attr.aria-label]="tab.display | syslang | transloco"
|
|
7438
7619
|
(click)="changeTab()">
|
|
@@ -10764,13 +10945,18 @@ class SignInComponent {
|
|
|
10764
10945
|
config = globalConfig;
|
|
10765
10946
|
/**
|
|
10766
10947
|
* True when authentication is handled outside the credentials form — i.e. by the
|
|
10767
|
-
* browser/proxy (`
|
|
10768
|
-
*
|
|
10769
|
-
*
|
|
10948
|
+
* browser/proxy (`sso`) or by an auto-configured OAuth/SAML provider. In those modes
|
|
10949
|
+
* this screen shows a loader instead of a login form and initiates the handshake
|
|
10950
|
+
* automatically by calling `handleLogin()`.
|
|
10951
|
+
*
|
|
10952
|
+
* Note: the ambiguous `unknown` mode is intentionally excluded — it is resolved upstream
|
|
10953
|
+
* (in `login()`/`signIn()`) to either `sso` or `credentials` before this screen renders,
|
|
10954
|
+
* so reaching here in `unknown` should still show the form, never a dead-end loader.
|
|
10770
10955
|
*/
|
|
10771
|
-
externalAuth =
|
|
10772
|
-
globalConfig.
|
|
10773
|
-
|
|
10956
|
+
externalAuth = (() => {
|
|
10957
|
+
const kind = globalConfig.authMode?.kind;
|
|
10958
|
+
return kind === "sso" || kind === "oauth" || kind === "saml";
|
|
10959
|
+
})();
|
|
10774
10960
|
class = input(...(ngDevMode ? [undefined, { debugName: "class" }] : []));
|
|
10775
10961
|
forgotPassword = output();
|
|
10776
10962
|
username = model("", ...(ngDevMode ? [{ debugName: "username" }] : []));
|
|
@@ -10866,10 +11052,13 @@ class SignInComponent {
|
|
|
10866
11052
|
this.auditService.notifyLogin();
|
|
10867
11053
|
}
|
|
10868
11054
|
return result;
|
|
10869
|
-
}).catch(
|
|
10870
|
-
warn("An error occurred while logging in",
|
|
11055
|
+
}).catch((err) => {
|
|
11056
|
+
warn("An error occurred while logging in", err);
|
|
10871
11057
|
this.auditService.notify({ type: 'Login_Denied' });
|
|
10872
|
-
|
|
11058
|
+
// Surface the failure reason on the error page (e.g. "OAuth provider not found: identity-dev")
|
|
11059
|
+
// so the user/admin knows what to fix, instead of a bare generic error screen.
|
|
11060
|
+
const message = (err?.errorMessage ?? err?.message) || undefined;
|
|
11061
|
+
this.router.navigate(["error"], { queryParams: { message } });
|
|
10873
11062
|
return false;
|
|
10874
11063
|
});
|
|
10875
11064
|
}
|
|
@@ -10884,7 +11073,16 @@ class SignInComponent {
|
|
|
10884
11073
|
}
|
|
10885
11074
|
this.auditService.notifyLogin();
|
|
10886
11075
|
const { createRoutes = false } = globalConfig;
|
|
10887
|
-
|
|
11076
|
+
try {
|
|
11077
|
+
await this.applicationService.initialize(createRoutes);
|
|
11078
|
+
}
|
|
11079
|
+
catch (initErr) {
|
|
11080
|
+
// Authenticated, but the application failed to initialize (e.g. fetchApp failed). Surface the
|
|
11081
|
+
// reason on the error page rather than leaving the user stuck on the login form.
|
|
11082
|
+
const { errorMessage, message } = (initErr ?? {});
|
|
11083
|
+
this.router.navigate(["/error"], { queryParams: { message: errorMessage ?? message } });
|
|
11084
|
+
return;
|
|
11085
|
+
}
|
|
10888
11086
|
this.checkPasswordExpiresSoon();
|
|
10889
11087
|
const url = this.route.snapshot.queryParams["returnUrl"] || "/";
|
|
10890
11088
|
this.router.navigateByUrl(url);
|
|
@@ -11430,49 +11628,29 @@ class OverrideUserDialogComponent {
|
|
|
11430
11628
|
}
|
|
11431
11629
|
}
|
|
11432
11630
|
handleOverrideUser(username, domain) {
|
|
11433
|
-
const {
|
|
11631
|
+
const { createRoutes } = globalConfig;
|
|
11434
11632
|
if (username === undefined || domain === undefined) {
|
|
11435
11633
|
setGlobalConfig({ userOverrideActive: false, userOverride: undefined });
|
|
11436
11634
|
}
|
|
11437
11635
|
else {
|
|
11438
11636
|
setGlobalConfig({ userOverrideActive: true, userOverride: { username, domain } });
|
|
11439
11637
|
}
|
|
11440
|
-
//
|
|
11441
|
-
|
|
11442
|
-
|
|
11443
|
-
|
|
11444
|
-
|
|
11445
|
-
|
|
11446
|
-
|
|
11447
|
-
|
|
11448
|
-
|
|
11449
|
-
|
|
11450
|
-
|
|
11451
|
-
|
|
11452
|
-
|
|
11453
|
-
|
|
11454
|
-
|
|
11455
|
-
|
|
11456
|
-
.then((value) => {
|
|
11457
|
-
if (value) {
|
|
11458
|
-
this.appService
|
|
11459
|
-
.initialize(createRoutes)
|
|
11460
|
-
.then(() => {
|
|
11461
|
-
const fullName = this.principalStore.fullName();
|
|
11462
|
-
notify.success(`Welcome back ${fullName}!`, { duration: 2000 });
|
|
11463
|
-
})
|
|
11464
|
-
.catch((err) => {
|
|
11465
|
-
error("An error occured while overriding (initialize)", err);
|
|
11466
|
-
setGlobalConfig({ userOverrideActive: false, userOverride: undefined });
|
|
11467
|
-
});
|
|
11468
|
-
}
|
|
11469
|
-
})
|
|
11470
|
-
.catch((err) => {
|
|
11471
|
-
error("An error occured while overriding (login)", err);
|
|
11472
|
-
notify.error("An error occured while overriding (login)", { duration: 2000 });
|
|
11473
|
-
setGlobalConfig({ userOverrideActive: false, userOverride: undefined });
|
|
11474
|
-
});
|
|
11475
|
-
}
|
|
11638
|
+
// Impersonation is header-driven: `createHeaders` adds `sinequa-override-user`/`-domain` to every
|
|
11639
|
+
// request while `userOverrideActive`, on top of the current (admin) session. We therefore do NOT
|
|
11640
|
+
// need to re-authenticate — re-initializing the stores refetches the principal/usersettings as the
|
|
11641
|
+
// overridden user. This works in every auth mode, including `credentials` where `login()` (without
|
|
11642
|
+
// credentials) is intentionally a no-op in atomic 2.0 and would otherwise skip initialization.
|
|
11643
|
+
this.appService
|
|
11644
|
+
.initialize(createRoutes)
|
|
11645
|
+
.then(() => {
|
|
11646
|
+
const fullName = this.principalStore.fullName();
|
|
11647
|
+
notify.success(`Welcome back ${fullName}!`, { duration: 2000 });
|
|
11648
|
+
})
|
|
11649
|
+
.catch((err) => {
|
|
11650
|
+
error("An error occurred while overriding (initialize)", err);
|
|
11651
|
+
notify.error("An error occurred while overriding (initialize)", { duration: 2000 });
|
|
11652
|
+
setGlobalConfig({ userOverrideActive: false, userOverride: undefined });
|
|
11653
|
+
});
|
|
11476
11654
|
}
|
|
11477
11655
|
static ɵfac = i0.ɵɵngDeclareFactory({ minVersion: "12.0.0", version: "20.3.18", ngImport: i0, type: OverrideUserDialogComponent, deps: [], target: i0.ɵɵFactoryTarget.Component });
|
|
11478
11656
|
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: `
|
|
@@ -16206,8 +16384,18 @@ const bodyInterceptorFn = (request, next) => {
|
|
|
16206
16384
|
};
|
|
16207
16385
|
|
|
16208
16386
|
/**
|
|
16209
|
-
*
|
|
16210
|
-
*
|
|
16387
|
+
* Marks a request that has already been retried once after a re-authentication attempt.
|
|
16388
|
+
* Carried on the request's HttpContext (not sent to the server) so a second 401 on the retry
|
|
16389
|
+
* propagates instead of triggering another sign-in — which would loop forever.
|
|
16390
|
+
*/
|
|
16391
|
+
const AUTH_RETRIED = new HttpContextToken(() => false);
|
|
16392
|
+
/**
|
|
16393
|
+
* Interceptor function that handles HTTP 401 errors by refreshing authentication and retrying the
|
|
16394
|
+
* original request once. For 403 errors, the error is propagated as a permanent auth failure.
|
|
16395
|
+
*
|
|
16396
|
+
* The retry happens ONLY when `signIn()` reports the user is authenticated, and AT MOST once per
|
|
16397
|
+
* request. Without these two guards a persistent 401 (credentials required, or an endpoint that
|
|
16398
|
+
* keeps rejecting even after a successful CSRF handshake) would retry endlessly.
|
|
16211
16399
|
*
|
|
16212
16400
|
* @param request - The HTTP request object.
|
|
16213
16401
|
* @param next - The HTTP handler function.
|
|
@@ -16220,14 +16408,23 @@ const errorInterceptorFn = (request, next) => {
|
|
|
16220
16408
|
}
|
|
16221
16409
|
return next(request).pipe(catchError$1((err) => {
|
|
16222
16410
|
if (err.status === 401) {
|
|
16223
|
-
|
|
16224
|
-
|
|
16225
|
-
|
|
16226
|
-
|
|
16227
|
-
|
|
16228
|
-
|
|
16229
|
-
|
|
16230
|
-
|
|
16411
|
+
// Already retried once after a re-auth — give up to avoid an infinite loop.
|
|
16412
|
+
if (request.context.get(AUTH_RETRIED)) {
|
|
16413
|
+
error("ErrorInterceptor: 401 again after re-auth retry, giving up");
|
|
16414
|
+
return throwError(() => err);
|
|
16415
|
+
}
|
|
16416
|
+
error("ErrorInterceptor: 401 detected, attempting to refresh auth");
|
|
16417
|
+
return runInInjectionContext(injector, () => from(signIn()).pipe(switchMap$1((authenticated) => {
|
|
16418
|
+
// signIn() could not authenticate (e.g. credentials required → redirected to login,
|
|
16419
|
+
// or a provider redirect is in progress). Do NOT retry — propagate the 401 instead,
|
|
16420
|
+
// otherwise every subsequent 401 re-triggers sign-in and loops.
|
|
16421
|
+
if (!authenticated) {
|
|
16422
|
+
error("ErrorInterceptor: re-auth did not authenticate, not retrying");
|
|
16423
|
+
return throwError(() => err);
|
|
16424
|
+
}
|
|
16425
|
+
error("Auth refreshed, retrying original request once");
|
|
16426
|
+
const headers = new HttpHeaders(createHeaders());
|
|
16427
|
+
return next(request.clone({ headers, context: request.context.set(AUTH_RETRIED, true) }));
|
|
16231
16428
|
}), catchError$1((signInErr) => {
|
|
16232
16429
|
error("Failed to refresh auth, redirecting to login", signInErr);
|
|
16233
16430
|
return throwError(() => signInErr);
|
|
@@ -16310,5 +16507,5 @@ const queryNameResolver = () => {
|
|
|
16310
16507
|
* Generated bundle index. Do not edit.
|
|
16311
16508
|
*/
|
|
16312
16509
|
|
|
16313
|
-
export { AGGREGATIONS_NAMES, AGGREGATIONS_NAMES_PRESET_DEFAULT, APP_FEATURES, AdvancedFiltersComponent, AdvancedSearch, AdvancedSearchComponent, AggregationComponent, AggregationDateComponent, AggregationDateRangeDialogComponent, AggregationListComponent, 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, 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, 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 };
|
|
16510
|
+
export { AGGREGATIONS_NAMES, AGGREGATIONS_NAMES_PRESET_DEFAULT, APP_FEATURES, AdvancedFiltersComponent, AdvancedSearch, AdvancedSearchComponent, AggregationComponent, AggregationDateComponent, AggregationDateRangeDialogComponent, AggregationListComponent, 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, 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, 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 };
|
|
16314
16511
|
//# sourceMappingURL=sinequa-atomic-angular.mjs.map
|