@coolms/core-angular 2.0.0-alpha.4 → 2.0.0-alpha.6
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/CHANGELOG.md +88 -0
- package/README.md +4 -0
- package/fesm2022/coolms-core-angular.mjs +517 -359
- package/fesm2022/coolms-core-angular.mjs.map +1 -1
- package/package.json +2 -2
- package/types/coolms-core-angular.d.ts +232 -41
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@coolms/core-angular",
|
|
3
|
-
"version": "2.0.0-alpha.
|
|
3
|
+
"version": "2.0.0-alpha.6",
|
|
4
4
|
"description": "The CoolMS client runtime for Angular: session and token refresh, boot manifest and app configuration, theme, user preferences, error handling, HTTP interceptors, and the wire types the CoolMS API emits.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": {
|
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
"rxjs": "^7.8.0"
|
|
18
18
|
},
|
|
19
19
|
"engines": {
|
|
20
|
-
"node": "^22.22.3 || ^24.15.0 ||
|
|
20
|
+
"node": "^22.22.3 || ^24.15.0 || ^26.0.0"
|
|
21
21
|
},
|
|
22
22
|
"keywords": [
|
|
23
23
|
"coolms",
|
|
@@ -3,7 +3,7 @@ import * as i0 from "@angular/core";
|
|
|
3
3
|
import { EnvironmentProviders, InjectionToken, Provider, Type } from "@angular/core";
|
|
4
4
|
import { StateContext } from "@ngxs/store";
|
|
5
5
|
import { CanActivateFn, CanMatchFn, Routes } from "@angular/router";
|
|
6
|
-
import { HttpContextToken, HttpInterceptorFn } from "@angular/common/http";
|
|
6
|
+
import { HttpContextToken, HttpInterceptorFn, HttpRequest } from "@angular/common/http";
|
|
7
7
|
export interface AuthApiManifest {
|
|
8
8
|
readonly login: string;
|
|
9
9
|
readonly logout: string;
|
|
@@ -277,6 +277,15 @@ export interface ApiManifest {
|
|
|
277
277
|
}>;
|
|
278
278
|
readonly platformDefaults?: PlatformDefaults;
|
|
279
279
|
}
|
|
280
|
+
/**
|
|
281
|
+
* GET /api/v1/admin/manifest -- the admin's endpoint map, answered to every signed-in account
|
|
282
|
+
* since 2026-10-06 (no host groups: each call it names is decided by its own route). A server
|
|
283
|
+
* from before that answers at /api/v1/console/manifest, to the console group only. Read once
|
|
284
|
+
* signed in.
|
|
285
|
+
*/
|
|
286
|
+
export interface ConsoleManifestResponse {
|
|
287
|
+
readonly manifest: ApiManifest;
|
|
288
|
+
}
|
|
280
289
|
export interface ThemeConfigResponse {
|
|
281
290
|
readonly slug?: string | null;
|
|
282
291
|
readonly feStack?: string | null;
|
|
@@ -536,10 +545,28 @@ export interface CentrifugoSubscriptionTokenDto {
|
|
|
536
545
|
*
|
|
537
546
|
* `ApiService` keeps its identical signatures and delegates here, so nothing
|
|
538
547
|
* calling it had to change.
|
|
548
|
+
*
|
|
549
|
+
* !! Access first, then realtime (Dmitry, 2026-09-28): "The console opens a realtime
|
|
550
|
+
* connection before the access check. Reverse the order ... A refused account opens no
|
|
551
|
+
* socket." The socket cannot open without the connection token, and anything may ask for
|
|
552
|
+
* one early -- the ring subscription is built at app init and asks as soon as a user is
|
|
553
|
+
* set, before the guard's answer is in. So the token waits for the console's answer (the
|
|
554
|
+
* guard's own request, not a second one) and a refused sign-in gets none: no request, no
|
|
555
|
+
* socket. A failure to ask is not a refusal, as at the guard.
|
|
556
|
+
*
|
|
557
|
+
* Since 2026-10-06 the connection token is every signed-in account's (Dmitry: "The connection
|
|
558
|
+
* token only opens a socket; each channel is authorized when it is subscribed to"), and a
|
|
559
|
+
* current server answers the admin manifest to every signed-in account, so no
|
|
560
|
+
* sign-in is refused and every one gets its token; each subscription is then decided by its
|
|
561
|
+
* channel. The wait stays for a server from before that, where a refused sign-in still opens
|
|
562
|
+
* no socket.
|
|
539
563
|
*/
|
|
540
564
|
export declare class RealtimeTokenClient {
|
|
565
|
+
/** The error a refused sign-in's connection-token request ends with. */
|
|
566
|
+
static readonly REFUSED = "no console access: no realtime connection";
|
|
541
567
|
private readonly http;
|
|
542
568
|
private readonly store;
|
|
569
|
+
private readonly injector;
|
|
543
570
|
private get manifest();
|
|
544
571
|
connectionToken(): Observable<CentrifugoConnectionTokenDto>;
|
|
545
572
|
subscriptionToken(channel: string): Observable<CentrifugoSubscriptionTokenDto>;
|
|
@@ -607,6 +634,12 @@ export declare class AuthState {
|
|
|
607
634
|
* will have placed a non-null (but expired) token in state, causing the guard
|
|
608
635
|
* to pass even though the subsequent config/refresh requests will fail and
|
|
609
636
|
* dispatch Logout. Waiting for ready$ guarantees the auth state is settled.
|
|
637
|
+
*
|
|
638
|
+
* Signed in is not enough: the console is granted, and the server says to whom
|
|
639
|
+
* (ConsoleAccessService). An account it refuses -- a customer who registered on the
|
|
640
|
+
* public site -- is signed out of this sign-in, on the server as well as here, and
|
|
641
|
+
* sent to the login page, which says why. A failure to ask is not a refusal: the console opens, and every request it
|
|
642
|
+
* makes is still decided by the server.
|
|
610
643
|
*/
|
|
611
644
|
export declare const authGuard: CanActivateFn;
|
|
612
645
|
/**
|
|
@@ -784,10 +817,15 @@ export interface ElevationState {
|
|
|
784
817
|
lifetimeCeilingSeconds: number;
|
|
785
818
|
/** Whether the CURRENT elevation was confirmed by a second factor; absent or null when not elevated. */
|
|
786
819
|
factorConfirmed?: boolean | null;
|
|
787
|
-
/** Whether a grant made now must carry a code from the
|
|
820
|
+
/** Whether a grant made now must carry a code from the person's own authenticator. */
|
|
788
821
|
mfaRequired: boolean;
|
|
789
|
-
/** What the prompt shows before asking; two of the codes mean
|
|
822
|
+
/** What the prompt shows before asking; two of the codes mean this account cannot elevate yet. */
|
|
790
823
|
warnings: ElevationWarning[];
|
|
824
|
+
/**
|
|
825
|
+
* Whether this account may elevate at all -- a member of the admin group, elevated or not. The one source a
|
|
826
|
+
* client offers "Elevate session" from; absent (an older server) reads as false.
|
|
827
|
+
*/
|
|
828
|
+
mayElevate?: boolean;
|
|
791
829
|
}
|
|
792
830
|
/**
|
|
793
831
|
* `closed` is the beacon (the panel was closed or reloaded), `dropped_by_user`
|
|
@@ -801,8 +839,9 @@ export interface ElevationEnded {
|
|
|
801
839
|
at: string | null;
|
|
802
840
|
}
|
|
803
841
|
/**
|
|
804
|
-
* `elevation.password_not_set` and `elevation.factor_missing` mean
|
|
805
|
-
*
|
|
842
|
+
* `elevation.password_not_set` and `elevation.factor_missing` mean this
|
|
843
|
+
* account cannot elevate (it has no password, or no confirmed factor, of its
|
|
844
|
+
* own) and the message says which;
|
|
806
845
|
* `elevation.mfa_off` is the recommendation carried while the switch is off.
|
|
807
846
|
*/
|
|
808
847
|
export interface ElevationWarning {
|
|
@@ -826,8 +865,8 @@ export type ElevationDropReason = 'dropped_by_user' | 'closed';
|
|
|
826
865
|
*
|
|
827
866
|
* `state` is the server's answer at the moment of asking -- `ended.reason` is
|
|
828
867
|
* rendered first (a person asked for a password again after F5
|
|
829
|
-
* with no explanation reads it as a fault), `warnings` may say
|
|
830
|
-
*
|
|
868
|
+
* with no explanation reads it as a fault), `warnings` may say this
|
|
869
|
+
* account cannot elevate yet, `mfaRequired` decides whether a code field
|
|
831
870
|
* shows from the start.
|
|
832
871
|
*
|
|
833
872
|
* `refusal` is the server's own sentence for the action that was refused
|
|
@@ -837,6 +876,8 @@ export type ElevationDropReason = 'dropped_by_user' | 'closed';
|
|
|
837
876
|
export interface ElevationPromptRequest {
|
|
838
877
|
state: ElevationState;
|
|
839
878
|
refusal?: string;
|
|
879
|
+
/** The refused action in words ("Saving navi nodes"), when a refused request opened the prompt. */
|
|
880
|
+
action?: string;
|
|
840
881
|
}
|
|
841
882
|
/**
|
|
842
883
|
* The prompt itself is a dialog, and dialogs are the kit's -- a layer above
|
|
@@ -858,6 +899,31 @@ export interface ElevationPromptPort {
|
|
|
858
899
|
* prompt to show and loses nothing.
|
|
859
900
|
*/
|
|
860
901
|
export declare const ELEVATION_PROMPT: InjectionToken<ElevationPromptPort>;
|
|
902
|
+
/**
|
|
903
|
+
* Told when an action a person took was refused for want of elevation: a
|
|
904
|
+
* write (not a read, not a background request) answered by a stamped 403.
|
|
905
|
+
*
|
|
906
|
+
* It must not open the prompt. The prompt opens only when the person asks for
|
|
907
|
+
* it -- the application shows the refusal and offers elevation as something to
|
|
908
|
+
* click (a notice with an Elevate button), and the click is what calls
|
|
909
|
+
* {@link ElevationService.offerFor}. A refused action is not repeated by
|
|
910
|
+
* itself after a grant: the person repeats it.
|
|
911
|
+
*
|
|
912
|
+
* `refusal` is the server's sentence for the refused action, already made
|
|
913
|
+
* readable by the error service.
|
|
914
|
+
*/
|
|
915
|
+
export interface ElevationNoticePort {
|
|
916
|
+
/** `action` names what the person did ("Saving navi nodes"); absent from older callers. */
|
|
917
|
+
refused(refusal: string, action?: string): void;
|
|
918
|
+
}
|
|
919
|
+
/**
|
|
920
|
+
* What a write IS, in the person's words, for the elevation prompt and notice when the server refuses it -- set by a
|
|
921
|
+
* caller that knows better than the URL ("Saving the menu entry"). Without it the interceptor names the method's verb
|
|
922
|
+
* and the resource the URL names.
|
|
923
|
+
*/
|
|
924
|
+
export declare const ELEVATION_ACTION: HttpContextToken<string | null>;
|
|
925
|
+
/** Optional, like {@link ELEVATION_PROMPT}: with nothing bound, a refusal is only a 403. */
|
|
926
|
+
export declare const ELEVATION_NOTICE: InjectionToken<ElevationNoticePort>;
|
|
861
927
|
/**
|
|
862
928
|
* Set on a request that must never open the elevation prompt: the elevation
|
|
863
929
|
* endpoint's own calls (a wrong password is a 403 and is not a refused action),
|
|
@@ -900,6 +966,19 @@ export declare const BYPASS_ELEVATION: HttpContextToken<boolean>;
|
|
|
900
966
|
* path (media, document and word templates, content distribution).
|
|
901
967
|
*/
|
|
902
968
|
export declare const ELEVATION_REQUIRED_HEADER = "X-Elevation-Required";
|
|
969
|
+
/**
|
|
970
|
+
* Set on a request no person asked for: a realtime token, a preferences sync, a
|
|
971
|
+
* poll. Its refusal is never announced -- not by a dialog and not by a notice --
|
|
972
|
+
* because nobody is waiting on it and nobody can act on it. The caller still
|
|
973
|
+
* receives the 403 and decides what its own state shows.
|
|
974
|
+
*/
|
|
975
|
+
export declare const BACKGROUND_REQUEST: HttpContextToken<boolean>;
|
|
976
|
+
/**
|
|
977
|
+
* Whether a failed request was refused for want of elevation: a 403 carrying
|
|
978
|
+
* the server's stamp. The one test every reader of a refusal uses -- the
|
|
979
|
+
* interceptor, a page's inline state, the error text -- so they cannot disagree.
|
|
980
|
+
*/
|
|
981
|
+
export declare function isElevationRefusal(err: unknown): boolean;
|
|
903
982
|
export declare class ElevationService {
|
|
904
983
|
private readonly http;
|
|
905
984
|
private readonly store;
|
|
@@ -923,6 +1002,8 @@ export declare class ElevationService {
|
|
|
923
1002
|
readonly state: () => ElevationState | null;
|
|
924
1003
|
/** Convenience over `state`: false until the server has said otherwise. */
|
|
925
1004
|
readonly elevated: () => boolean;
|
|
1005
|
+
/** Whether this account may elevate at all, as the server answered it: what "Elevate session" is offered from. */
|
|
1006
|
+
readonly mayElevate: () => boolean;
|
|
926
1007
|
/** `expiresAt` as a Date, null when not elevated. */
|
|
927
1008
|
readonly expiresAt: () => Date | null;
|
|
928
1009
|
/**
|
|
@@ -937,6 +1018,10 @@ export declare class ElevationService {
|
|
|
937
1018
|
private expiryAnnouncePending;
|
|
938
1019
|
private lastReconcileAt;
|
|
939
1020
|
constructor();
|
|
1021
|
+
/** Whether a prompt can be opened at all: this install offers elevation and a prompt is bound. */
|
|
1022
|
+
get canPrompt(): boolean;
|
|
1023
|
+
/** The manifest's API base, as {@link isThisApi} reads it. */
|
|
1024
|
+
get apiBase(): string;
|
|
940
1025
|
/** The manifest carries the URL; without it the service is inert, not broken. */
|
|
941
1026
|
get available(): boolean;
|
|
942
1027
|
private get url();
|
|
@@ -951,20 +1036,23 @@ export declare class ElevationService {
|
|
|
951
1036
|
/** Ask the server. Applies the answer and announces it when it differs. */
|
|
952
1037
|
refresh(): Observable<ElevationState>;
|
|
953
1038
|
/**
|
|
954
|
-
* Elevate with the
|
|
955
|
-
*
|
|
956
|
-
*
|
|
957
|
-
*
|
|
1039
|
+
* Elevate with the person's own password and, when the switch is on, a
|
|
1040
|
+
* code from their own authenticator. Errors are the dialog's to branch on:
|
|
1041
|
+
* 403 not a member of the admin group, wrong password, or wrong / replayed
|
|
1042
|
+
* code, 428 a code is required, 409 this account cannot elevate yet, 429
|
|
1043
|
+
* throttled. They pass through
|
|
958
1044
|
* untouched, and never open the prompt (BYPASS_ELEVATION).
|
|
959
1045
|
*/
|
|
960
1046
|
elevate(password: string, code?: string): Observable<ElevationState>;
|
|
961
1047
|
/** Drop the elevation now. Idempotent on the server: nothing live is still a 204. */
|
|
962
1048
|
drop(reason?: ElevationDropReason): Observable<ElevationState>;
|
|
963
1049
|
/**
|
|
964
|
-
* What
|
|
965
|
-
*
|
|
966
|
-
*
|
|
967
|
-
*
|
|
1050
|
+
* What a person's explicit request for elevation calls -- an Elevate button,
|
|
1051
|
+
* or a control that already knows its action needs elevation: ask the
|
|
1052
|
+
* server whether this session is elevated, and only when it is not, open
|
|
1053
|
+
* the prompt. Nothing calls it on a response by itself (the interceptor
|
|
1054
|
+
* only tells {@link ELEVATION_NOTICE}). Elevated already is false, no
|
|
1055
|
+
* prompt: elevation would change nothing.
|
|
968
1056
|
*/
|
|
969
1057
|
offerFor(refusal?: string): Observable<boolean>;
|
|
970
1058
|
/**
|
|
@@ -1042,36 +1130,54 @@ export declare class ElevationService {
|
|
|
1042
1130
|
static ɵprov: i0.ɵɵInjectableDeclaration<any>;
|
|
1043
1131
|
}
|
|
1044
1132
|
/**
|
|
1045
|
-
*
|
|
1046
|
-
*
|
|
1047
|
-
*
|
|
1048
|
-
*
|
|
1049
|
-
*
|
|
1050
|
-
*
|
|
1051
|
-
*
|
|
1052
|
-
*
|
|
1053
|
-
*
|
|
1054
|
-
*
|
|
1055
|
-
*
|
|
1056
|
-
*
|
|
1057
|
-
*
|
|
1058
|
-
*
|
|
1059
|
-
*
|
|
1060
|
-
*
|
|
1061
|
-
*
|
|
1062
|
-
*
|
|
1063
|
-
*
|
|
1064
|
-
*
|
|
1065
|
-
*
|
|
1133
|
+
* A refusal for want of elevation is a 403 the server stamps `X-Elevation-Required`
|
|
1134
|
+
* where its gate refused a member who has not elevated ({@link isElevationRefusal}).
|
|
1135
|
+
*
|
|
1136
|
+
* What it does with a stamped 403 (decided 2026-10-20 for a person's write):
|
|
1137
|
+
* - On a write a person made (POST, PUT, PATCH, DELETE without {@link BACKGROUND_REQUEST}):
|
|
1138
|
+
* the prompt opens THERE, above whatever the person is in -- an edit dialog stays open
|
|
1139
|
+
* beneath it with its input -- and the request is HELD. If the person elevates, it is
|
|
1140
|
+
* sent once more and the caller receives that answer, as if the first had been accepted;
|
|
1141
|
+
* if the session turns out to be elevated already (another tab), it is sent once more
|
|
1142
|
+
* without asking. If the person declines, {@link ELEVATION_NOTICE} is told -- "Elevation
|
|
1143
|
+
* required", naming the action, with an Elevate button -- and the caller receives the 403.
|
|
1144
|
+
* The repeat is sent past this interceptor, so a second refusal is a 403, never a second
|
|
1145
|
+
* prompt.
|
|
1146
|
+
* - On a read: nothing. Reads are what a page makes as it loads and what polls make in
|
|
1147
|
+
* the background, and the page shows its own state for a refused one -- an inline
|
|
1148
|
+
* "needs an elevated session" with an Elevate button, or the feature hidden.
|
|
1149
|
+
* - On a background request: nothing. Nobody is waiting on it.
|
|
1150
|
+
*
|
|
1151
|
+
* !! Why reads and background requests still open nothing. Until 2026-10-08 any stamped 403
|
|
1152
|
+
* opened the prompt, so the setup reads of almost every console page, and a realtime token at
|
|
1153
|
+
* every load, opened it for an administrator who had not elevated: 13 of 15 pages on a menu
|
|
1154
|
+
* click, 15 of 15 on a reload (measured). From then until 2026-10-20 a person's write opened
|
|
1155
|
+
* nothing either, only a toast -- which, with a dialog open, rendered under it, so the edit had
|
|
1156
|
+
* to be thrown away to elevate (measured on the served admin, Edit Node). A person's write is
|
|
1157
|
+
* the one refusal somebody is waiting on, which is why it is the one that asks.
|
|
1158
|
+
*
|
|
1159
|
+
* The elevation endpoint's own calls carry {@link BYPASS_ELEVATION}: a wrong password is a
|
|
1160
|
+
* 403 and is not a refused action. A stamped 403 from anywhere but this API is ignored:
|
|
1161
|
+
* the header is the server's word, and only this server's counts.
|
|
1066
1162
|
*/
|
|
1067
1163
|
export declare const elevationInterceptor: HttpInterceptorFn;
|
|
1164
|
+
/**
|
|
1165
|
+
* The action a refused write was, in words for the person: what its caller said it is
|
|
1166
|
+
* ({@link ELEVATION_ACTION}), or else the method's verb and the resource the URL names --
|
|
1167
|
+
* "Saving navi nodes" for `PATCH /api/v1/navi/nodes/{id}`. Identifiers and the API base
|
|
1168
|
+
* are left out; they say nothing to the person who pressed the button.
|
|
1169
|
+
*/
|
|
1170
|
+
export declare function actionOf(req: HttpRequest<unknown>, apiBase?: string): string;
|
|
1068
1171
|
export declare class LoginComponent {
|
|
1069
1172
|
identifier: string;
|
|
1070
1173
|
password: string;
|
|
1071
|
-
loading: boolean
|
|
1072
|
-
error: string | null
|
|
1174
|
+
readonly loading: import("@angular/core").WritableSignal<boolean>;
|
|
1175
|
+
readonly error: import("@angular/core").WritableSignal<string | null>;
|
|
1073
1176
|
private readonly store;
|
|
1074
1177
|
private readonly router;
|
|
1178
|
+
/** Shown when the guard signed an account out because the console is not granted to it. */
|
|
1179
|
+
static readonly NO_CONSOLE_ACCESS = "This account has no access to the console.";
|
|
1180
|
+
constructor();
|
|
1075
1181
|
submit(): void;
|
|
1076
1182
|
static ɵfac: i0.ɵɵFactoryDeclaration<LoginComponent, never>;
|
|
1077
1183
|
static ɵcmp: i0.ɵɵComponentDeclaration<LoginComponent, "coolms-admin-login", never, {}, {}, never, never, true, never>;
|
|
@@ -1113,6 +1219,62 @@ export declare class AppInitService {
|
|
|
1113
1219
|
static ɵfac: i0.ɵɵFactoryDeclaration<AppInitService, never>;
|
|
1114
1220
|
static ɵprov: i0.ɵɵInjectableDeclaration<any>;
|
|
1115
1221
|
}
|
|
1222
|
+
/** What the server said about this sign-in and the console. */
|
|
1223
|
+
export type ConsoleAccess = 'granted' | 'refused' | 'unknown';
|
|
1224
|
+
/**
|
|
1225
|
+
* Whether the signed-in account may use the console, and the console's endpoint
|
|
1226
|
+
* map when it may.
|
|
1227
|
+
*
|
|
1228
|
+
* The server decides: `GET /api/v1/admin/manifest` answers the map. Since 2026-10-06
|
|
1229
|
+
* (Dmitry: "no host groups"; access is decided by module groups alone) it answers every
|
|
1230
|
+
* signed-in account: the map grants nothing, each call it names is decided by its own
|
|
1231
|
+
* route, and an account in no group sees the admin with an empty menu. A server from
|
|
1232
|
+
* before that answers only at `/api/v1/console/manifest`, to the `console` group, and
|
|
1233
|
+
* 403 to anyone else -- there a refused sign-in is still signed out; this asks the new
|
|
1234
|
+
* address first and the old one only on its 404. The public theme config
|
|
1235
|
+
* read before sign-in carries only what the sign-in page needs; the rest arrives
|
|
1236
|
+
* here, once per sign-in, and is merged into the app config key by key: a key the
|
|
1237
|
+
* console's map carries replaces the public one whole, so the console's copy of a
|
|
1238
|
+
* section is always the complete one.
|
|
1239
|
+
*
|
|
1240
|
+
* `unknown` is a failure to ask (the network, a 5xx), not an answer: it is not kept,
|
|
1241
|
+
* so while the endpoint fails every navigation asks again.
|
|
1242
|
+
*
|
|
1243
|
+
* !! An answer belongs to the sign-in it was asked for. A sign-in, a sign-out or a
|
|
1244
|
+
* restored session starts a new generation; an answer that arrives for a generation
|
|
1245
|
+
* that is gone is kept for nobody -- A's late 403 must not sign B out -- and whoever
|
|
1246
|
+
* is waiting on it is answered for the sign-in there is now.
|
|
1247
|
+
*
|
|
1248
|
+
* !! With nobody signed in there is nothing to ask, and nothing is asked. Without
|
|
1249
|
+
* that, the re-ask after a sign-out went out with no token, its 401 made the
|
|
1250
|
+
* interceptor dispatch Logout, which started another generation, which re-asked:
|
|
1251
|
+
* a loop for as long as the tab lived (found in review).
|
|
1252
|
+
*/
|
|
1253
|
+
export declare class ConsoleAccessService {
|
|
1254
|
+
/** The admin manifest's URL: fixed, like the theme config's, because nothing is known before it. */
|
|
1255
|
+
static readonly URL = "/api/v1/admin/manifest";
|
|
1256
|
+
/** The address before 2026-10-06, asked only when a server older than that answers 404 at {@link URL}. */
|
|
1257
|
+
static readonly LEGACY_URL = "/api/v1/console/manifest";
|
|
1258
|
+
/** The login page's query parameter value for "signed out: no console access". */
|
|
1259
|
+
static readonly REFUSED_REASON = "no-console-access";
|
|
1260
|
+
private readonly http;
|
|
1261
|
+
private readonly store;
|
|
1262
|
+
private answer;
|
|
1263
|
+
private pending;
|
|
1264
|
+
private generation;
|
|
1265
|
+
constructor();
|
|
1266
|
+
/** Ask once per sign-in; the manifest is merged into the app config when granted. */
|
|
1267
|
+
ensure(): Promise<ConsoleAccess>;
|
|
1268
|
+
/**
|
|
1269
|
+
* Sign a refused account out: this sign-in ends on the server (its refresh token
|
|
1270
|
+
* with it), as the account menu's sign-out does, and then in this browser --
|
|
1271
|
+
* whatever the server answered, so a refused request cannot leave it signed in.
|
|
1272
|
+
*/
|
|
1273
|
+
signOut(): Observable<void>;
|
|
1274
|
+
private merge;
|
|
1275
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<ConsoleAccessService, never>;
|
|
1276
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<any>;
|
|
1277
|
+
}
|
|
1116
1278
|
/**
|
|
1117
1279
|
* Layout config DTO -- the shape served from
|
|
1118
1280
|
* `GET /api/v1/config/layout/{id}`. Mirrors the YAML at
|
|
@@ -1230,7 +1392,8 @@ export interface DatagridConfig {
|
|
|
1230
1392
|
* Fetches config YAMLs (served as JSON) from GET /api/v1/config/{type}/{id}.
|
|
1231
1393
|
*
|
|
1232
1394
|
* Responses are cached for the lifetime of the service (Map + shareReplay(1))
|
|
1233
|
-
* so repeated calls never re-fetch the same resource.
|
|
1395
|
+
* so repeated calls never re-fetch the same resource. A failed read is dropped
|
|
1396
|
+
* from the cache, so the next call asks the server again.
|
|
1234
1397
|
*
|
|
1235
1398
|
* configBase is read from the API manifest (manifest.configBase) and falls
|
|
1236
1399
|
* back to '/api/v1/config' when the manifest is not yet loaded.
|
|
@@ -1259,6 +1422,8 @@ export interface AppConfigStateModel {
|
|
|
1259
1422
|
}
|
|
1260
1423
|
export declare class AppConfigState {
|
|
1261
1424
|
set(ctx: StateContext<AppConfigStateModel>, { config }: SetAppConfig): void;
|
|
1425
|
+
/** The whole config as last set: the public theme config, with the console's manifest once granted. */
|
|
1426
|
+
static config(state: AppConfigStateModel): ThemeConfigResponse | null;
|
|
1262
1427
|
static manifest(state: AppConfigStateModel): ApiManifest | null;
|
|
1263
1428
|
/**
|
|
1264
1429
|
* The viewer manifest. Null when the backend didn't ship a viewers
|
|
@@ -1658,6 +1823,14 @@ export interface NaviGraphMeta {
|
|
|
1658
1823
|
readonly contributor?: string;
|
|
1659
1824
|
readonly [key: string]: unknown;
|
|
1660
1825
|
}
|
|
1826
|
+
/**
|
|
1827
|
+
* What the server says this account may do with a node, as it answered the graph:
|
|
1828
|
+
* 'allowed' -- its own rights open it; shown as usual;
|
|
1829
|
+
* 'elevate' -- a member of admin, not elevated, whose own rights refuse it and whom elevation would let in;
|
|
1830
|
+
* shown with a lock, and opening it asks for elevation first.
|
|
1831
|
+
* A node anyone else may not open is not in the answer at all. Absent (an older server) reads as 'allowed'.
|
|
1832
|
+
*/
|
|
1833
|
+
export type NaviAccess = 'allowed' | 'elevate';
|
|
1661
1834
|
/** Flat node as returned from GET /navi/trees/{slug}/graph */
|
|
1662
1835
|
export interface NaviGraphNodeFlat {
|
|
1663
1836
|
readonly id: string;
|
|
@@ -1668,7 +1841,10 @@ export interface NaviGraphNodeFlat {
|
|
|
1668
1841
|
readonly isActive: boolean;
|
|
1669
1842
|
readonly isVisible: boolean;
|
|
1670
1843
|
readonly meta: NaviGraphMeta;
|
|
1844
|
+
readonly access?: NaviAccess;
|
|
1671
1845
|
}
|
|
1846
|
+
/** The node's access as the server answered it; 'allowed' when the server said nothing (an older server). */
|
|
1847
|
+
export declare function naviAccessOf(node: Pick<NaviGraphNodeFlat, 'access'>): NaviAccess;
|
|
1672
1848
|
/** Same node enriched with resolved children after client-side tree build */
|
|
1673
1849
|
export interface NaviGraphNode extends NaviGraphNodeFlat {
|
|
1674
1850
|
readonly children: NaviGraphNode[];
|
|
@@ -1691,7 +1867,14 @@ export declare class NaviGraphService {
|
|
|
1691
1867
|
/** Resolved root nodes for navi.admin.topbar. */
|
|
1692
1868
|
readonly topbarNav: import("@angular/core").WritableSignal<NaviGraphNode[]>;
|
|
1693
1869
|
private readonly cache;
|
|
1694
|
-
|
|
1870
|
+
/**
|
|
1871
|
+
* `fresh` asks the server again instead of answering from this service's cache: the answer is per account AND
|
|
1872
|
+
* per elevation state (a node is 'elevate' before a grant and 'allowed' after it), so a change of elevation must
|
|
1873
|
+
* re-ask, or a lock would outlive the grant -- or come back only after a reload.
|
|
1874
|
+
*/
|
|
1875
|
+
loadAdminNav(url: string, options?: {
|
|
1876
|
+
fresh?: boolean;
|
|
1877
|
+
}): Observable<NaviGraphNode[]>;
|
|
1695
1878
|
loadTopbarNav(url: string): Observable<NaviGraphNode[]>;
|
|
1696
1879
|
/**
|
|
1697
1880
|
* Handle a nav-node click.
|
|
@@ -1724,7 +1907,7 @@ export declare class NaviGraphService {
|
|
|
1724
1907
|
* the legacy Hydra key (`hydra:member`).
|
|
1725
1908
|
* Results are cached by URL for the lifetime of the service instance.
|
|
1726
1909
|
*/
|
|
1727
|
-
loadTree(url: string): Observable<NaviGraphNode[]>;
|
|
1910
|
+
loadTree(url: string, fresh?: boolean): Observable<NaviGraphNode[]>;
|
|
1728
1911
|
/**
|
|
1729
1912
|
* Build a nested tree from a flat array of nodes.
|
|
1730
1913
|
* Nodes are sorted by sortOrder at each level.
|
|
@@ -1936,6 +2119,14 @@ export declare class ConsoleActivation {
|
|
|
1936
2119
|
* landed on the dashboard (measured 2026-09-22: no chunk requested for the
|
|
1937
2120
|
* mount, the dashboard's instead). `ready$` is what the auth guard waits on
|
|
1938
2121
|
* too, and it fires whether or not the config fetch succeeded.
|
|
2122
|
+
*
|
|
2123
|
+
* !! Then the console's own manifest, when someone is signed in: `ui.modules` is
|
|
2124
|
+
* the console's, not the public config's, and arrives with it
|
|
2125
|
+
* ({@link ConsoleAccessService}). canMatch runs before the layout's auth guard,
|
|
2126
|
+
* which asks the same question, so answering from the public config alone said
|
|
2127
|
+
* "no" to every module once that config stopped carrying them. Nobody signed in:
|
|
2128
|
+
* nothing is asked, the answer is the public config's, and the layout's guard
|
|
2129
|
+
* sends the person to sign in. A refusal merges nothing, so the answer is no.
|
|
1939
2130
|
*/
|
|
1940
2131
|
export declare function consoleModuleInstalled(module: string): CanMatchFn;
|
|
1941
2132
|
/**
|