@coolms/core-angular 2.0.0-alpha.4 → 2.0.0-alpha.5
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 +46 -0
- package/fesm2022/coolms-core-angular.mjs +2998 -1956
- package/fesm2022/coolms-core-angular.mjs.map +1 -1
- package/package.json +1 -1
- package/types/coolms-core-angular.d.ts +1514 -1350
|
@@ -1,164 +1,166 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
1
|
+
import * as rxjs from 'rxjs';
|
|
2
|
+
import { Observable, Subject } from 'rxjs';
|
|
3
|
+
import * as i0 from '@angular/core';
|
|
4
|
+
import { InjectionToken, Type, Provider, EnvironmentProviders } from '@angular/core';
|
|
5
|
+
import { StateContext } from '@ngxs/store';
|
|
6
|
+
import { CanActivateFn, Routes, CanMatchFn } from '@angular/router';
|
|
7
|
+
import { HttpContextToken, HttpInterceptorFn } from '@angular/common/http';
|
|
8
|
+
|
|
9
|
+
interface AuthApiManifest {
|
|
10
|
+
readonly login: string;
|
|
11
|
+
readonly logout: string;
|
|
12
|
+
readonly refresh: string;
|
|
13
|
+
readonly register: string;
|
|
14
|
+
readonly me: string;
|
|
15
|
+
readonly usersApi?: string;
|
|
16
|
+
readonly groupsApi?: string;
|
|
17
|
+
readonly preferencesUrl?: string;
|
|
18
|
+
readonly groupFormId?: string | null;
|
|
19
|
+
readonly userFormId?: string | null;
|
|
20
|
+
}
|
|
21
|
+
interface SectionApiManifest {
|
|
22
|
+
readonly list: string;
|
|
23
|
+
readonly create: string;
|
|
24
|
+
readonly item: string;
|
|
25
|
+
readonly update: string;
|
|
26
|
+
readonly delete: string;
|
|
27
|
+
readonly formId?: string;
|
|
28
|
+
readonly apply?: string;
|
|
29
|
+
}
|
|
30
|
+
interface NaviApiManifest {
|
|
31
|
+
readonly treesList: string;
|
|
32
|
+
readonly treesCreate: string;
|
|
33
|
+
readonly treesItem: string;
|
|
34
|
+
readonly nodesList: string;
|
|
35
|
+
readonly nodesCreate: string;
|
|
36
|
+
readonly nodesItem: string;
|
|
37
|
+
readonly nodesReorder: string;
|
|
38
|
+
readonly nodesMove: string;
|
|
39
|
+
readonly nodesByTree: string;
|
|
40
|
+
readonly treesFormId?: string;
|
|
41
|
+
readonly nodesFormId?: string;
|
|
42
|
+
readonly adminNavGraph?: string;
|
|
43
|
+
readonly topbarNavGraph?: string;
|
|
44
|
+
readonly toolbarVfsGraph?: string;
|
|
45
|
+
readonly graphBySlug?: string;
|
|
46
|
+
}
|
|
47
|
+
interface ContentApiManifest {
|
|
48
|
+
readonly variantPublishUrl: string;
|
|
49
|
+
readonly pageSpacesUrl?: string;
|
|
50
|
+
readonly pageTypesUrl?: string;
|
|
51
|
+
}
|
|
52
|
+
interface DataGridApiManifest {
|
|
53
|
+
readonly configBase: string;
|
|
54
|
+
}
|
|
55
|
+
interface TerminalApiManifest {
|
|
56
|
+
readonly executeUrl: string;
|
|
57
|
+
readonly completeUrl: string;
|
|
58
|
+
}
|
|
59
|
+
interface MediaApiManifest {
|
|
60
|
+
readonly listUrl: string;
|
|
61
|
+
readonly itemUrl: string;
|
|
62
|
+
readonly permissionsUrl: string;
|
|
63
|
+
readonly regenerateUrl: string;
|
|
64
|
+
readonly resizeUrl: string;
|
|
65
|
+
readonly collectionsUrl: string;
|
|
66
|
+
readonly collectionsCreateUrl: string;
|
|
67
|
+
readonly collectionPermissionsUrl: string;
|
|
68
|
+
readonly moveUrl?: string;
|
|
69
|
+
readonly spacesUrl?: string;
|
|
70
|
+
}
|
|
71
|
+
interface VfsApiManifest {
|
|
72
|
+
readonly fileContentUrl: string;
|
|
73
|
+
readonly binaryWriteUrl: string;
|
|
72
74
|
}
|
|
73
75
|
/**
|
|
74
76
|
* Document module URL section. Today the only entry is `spacesUrl`;
|
|
75
77
|
* the struct is reserved so future Document-module URLs can land
|
|
76
78
|
* here without a new manifest section.
|
|
77
79
|
*/
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
80
|
+
interface DocumentApiManifest {
|
|
81
|
+
readonly spacesUrl?: string;
|
|
82
|
+
/**
|
|
83
|
+
* !! Carried here rather than derived from `spacesUrl`. Concatenating
|
|
84
|
+
* `/available` onto that would couple the client to a URL shape the router
|
|
85
|
+
* owns, and it would keep working until the route moved -- then fail as a
|
|
86
|
+
* 404 the UI reports as "no sites available", which is indistinguishable
|
|
87
|
+
* from the true empty answer.
|
|
88
|
+
*
|
|
89
|
+
* !! ABSENT ARRIVES AS `''`, NOT `undefined`. The producer declares these
|
|
90
|
+
* as non-nullable strings defaulting to empty, so an installation that has
|
|
91
|
+
* not set them sends an empty string and the `?` here never fires.
|
|
92
|
+
*
|
|
93
|
+
* Guard on FALSINESS -- `!url`, `Boolean(url)`. The dangerous rewrite is
|
|
94
|
+
* `url === undefined` (or `!== undefined`), which reads as more precise and
|
|
95
|
+
* treats `''` as present, handing a caller a request to the empty string.
|
|
96
|
+
*
|
|
97
|
+
* !! A `?? fallback` is NOT the hazard, though it looks like the obvious
|
|
98
|
+
* one: `'' ?? x` is `''`, so a fallback never fires on the value this
|
|
99
|
+
* producer actually sends. Measured by mutation -- adding one changed
|
|
100
|
+
* nothing, changing the guard broke three assertions.
|
|
101
|
+
*/
|
|
102
|
+
readonly spacesAvailableUrl?: string;
|
|
103
|
+
readonly spaceEnablementUrl?: string;
|
|
104
|
+
/**
|
|
105
|
+
* FQCN of the entity a filter-mode audience can be built from.
|
|
106
|
+
*
|
|
107
|
+
* !! The SERVER owns this and the client must not know it. The generation
|
|
108
|
+
* wizard offers Filter mode only when a template's context schema
|
|
109
|
+
* references this type, and it used to compare against a constant compiled
|
|
110
|
+
* into this bundle -- which shipped the consuming application's class name
|
|
111
|
+
* inside a published package, and tied that package to an installation
|
|
112
|
+
* having that exact class.
|
|
113
|
+
*
|
|
114
|
+
* !! Absent arrives as `''`, like the urls above. Treat empty as **filter
|
|
115
|
+
* mode unavailable**, never as a reason to fall back to a remembered
|
|
116
|
+
* value: the server is the thing that rejects every other type, so a guess
|
|
117
|
+
* here offers a mode the server will refuse.
|
|
118
|
+
*/
|
|
119
|
+
readonly filterAudienceEntity?: string;
|
|
120
|
+
}
|
|
121
|
+
interface DynamicEntityApiManifest {
|
|
122
|
+
readonly typesUrl: string;
|
|
123
|
+
readonly fieldDefinitionsUrl: string;
|
|
124
|
+
readonly fieldDefinitionUrl: string;
|
|
125
|
+
readonly fieldDefinitionsReorderUrl: string;
|
|
126
|
+
readonly toolbarNaviGraphUrl: string;
|
|
127
|
+
readonly constraintsUrl: string;
|
|
128
|
+
readonly recordsUrl: string;
|
|
129
|
+
readonly recordUrl: string;
|
|
130
|
+
readonly recordsByTypeUrl: string;
|
|
131
|
+
readonly typeUrl: string;
|
|
132
|
+
readonly typesCreateUrl: string;
|
|
133
|
+
readonly formTypesUrl?: string;
|
|
134
|
+
readonly typeByAliasUrl?: string;
|
|
135
|
+
}
|
|
136
|
+
interface DomainExplorerApiManifest {
|
|
137
|
+
readonly entitiesUrl: string;
|
|
138
|
+
readonly entityUrl: string;
|
|
139
|
+
readonly toolbarNaviGraphUrl: string;
|
|
140
|
+
readonly domainExplorerToolbarUrl: string;
|
|
141
|
+
readonly entityFieldsDataGridUrl?: string;
|
|
142
|
+
readonly typeFieldsDataGridUrl?: string;
|
|
143
|
+
}
|
|
144
|
+
interface IdentityApiManifest {
|
|
145
|
+
readonly usersUrl: string;
|
|
146
|
+
readonly userUrl: string;
|
|
147
|
+
readonly groupsUrl: string;
|
|
148
|
+
readonly groupUrl: string;
|
|
149
|
+
readonly meUrl: string;
|
|
150
|
+
readonly assignGroupsUrl: string;
|
|
151
|
+
readonly avatarUploadUrl: string;
|
|
152
|
+
readonly settingsUrl: string;
|
|
153
|
+
readonly settingsSectionsUrl: string;
|
|
154
|
+
readonly settingsSectionUrl: string;
|
|
155
|
+
readonly colorUrl: string;
|
|
156
|
+
readonly rolesUrl?: string;
|
|
157
|
+
readonly elevationUrl?: string;
|
|
158
|
+
readonly deletionsUrl?: string;
|
|
159
|
+
readonly footprintsUrl?: string;
|
|
160
|
+
readonly userDeletionUrl?: string;
|
|
161
|
+
readonly userLegalHoldsUrl?: string;
|
|
162
|
+
readonly holdsSettingsBlock?: string;
|
|
163
|
+
readonly legalHoldFormId?: string | null;
|
|
162
164
|
}
|
|
163
165
|
/**
|
|
164
166
|
* Editor sub-manifest emitted by the backend's Editor module. The
|
|
@@ -170,27 +172,27 @@ export interface IdentityApiManifest {
|
|
|
170
172
|
* pre-resolved contributor list AND the storage allow-list a sanitizer
|
|
171
173
|
* enforces on save.
|
|
172
174
|
*/
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
}
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
}
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
175
|
+
interface EditorApiManifestEntry {
|
|
176
|
+
readonly id: string;
|
|
177
|
+
readonly group: string;
|
|
178
|
+
readonly priority: number;
|
|
179
|
+
readonly icon: string;
|
|
180
|
+
readonly label: string;
|
|
181
|
+
readonly actionType: string;
|
|
182
|
+
readonly actionParams: Readonly<Record<string, unknown>>;
|
|
183
|
+
readonly extensions: ReadonlyArray<string>;
|
|
184
|
+
readonly stateKeys: ReadonlyArray<string>;
|
|
185
|
+
readonly shortcut?: string | null;
|
|
186
|
+
readonly slashable?: boolean;
|
|
187
|
+
readonly keywords?: ReadonlyArray<string>;
|
|
188
|
+
}
|
|
189
|
+
interface EditorApiManifestProfile {
|
|
190
|
+
readonly contributors: ReadonlyArray<EditorApiManifestEntry>;
|
|
191
|
+
readonly allowedWidgets: ReadonlyArray<string>;
|
|
192
|
+
}
|
|
193
|
+
interface EditorApiManifest {
|
|
194
|
+
readonly schemaVersion: number;
|
|
195
|
+
readonly profiles: Readonly<Record<string, EditorApiManifestProfile>>;
|
|
194
196
|
}
|
|
195
197
|
/**
|
|
196
198
|
* Viewer manifest emitted by the backend's Document module.
|
|
@@ -199,20 +201,20 @@ export interface EditorApiManifest {
|
|
|
199
201
|
* profile. Profile config is open-ended -- every viewer interprets the
|
|
200
202
|
* keys it understands and ignores the rest.
|
|
201
203
|
*/
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
204
|
+
interface ViewerProfileApiManifest {
|
|
205
|
+
readonly key: string;
|
|
206
|
+
readonly config: Readonly<Record<string, unknown>>;
|
|
205
207
|
}
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
208
|
+
interface ViewerDefinitionApiManifest {
|
|
209
|
+
readonly key: string;
|
|
210
|
+
readonly mimeTypes: ReadonlyArray<string>;
|
|
211
|
+
readonly extensions: ReadonlyArray<string>;
|
|
212
|
+
readonly component: string;
|
|
213
|
+
readonly profiles: Readonly<Record<string, ViewerProfileApiManifest>>;
|
|
212
214
|
}
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
215
|
+
interface ViewerApiManifest {
|
|
216
|
+
readonly schemaVersion: number;
|
|
217
|
+
readonly viewers: Readonly<Record<string, ViewerDefinitionApiManifest>>;
|
|
216
218
|
}
|
|
217
219
|
/**
|
|
218
220
|
* Platform-wide default user-facing settings. The
|
|
@@ -221,17 +223,17 @@ export interface ViewerApiManifest {
|
|
|
221
223
|
* config, not an FE-hardcoded Western default. `locale` is the default
|
|
222
224
|
* locale, previously absent from the manifest.
|
|
223
225
|
*/
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
226
|
+
interface PlatformDefaults {
|
|
227
|
+
readonly locale: string;
|
|
228
|
+
readonly timezone: string;
|
|
229
|
+
readonly dateFormat: string;
|
|
230
|
+
readonly timeFormat: '12h' | '24h';
|
|
231
|
+
readonly weekStart: 'monday' | 'sunday';
|
|
232
|
+
/**
|
|
233
|
+
* Deployment brand accent as `#rrggbb`, or null/absent to keep the
|
|
234
|
+
* stylesheet's own. Sits BELOW a user's personal accentColor.
|
|
235
|
+
*/
|
|
236
|
+
readonly accentColor?: string | null;
|
|
235
237
|
}
|
|
236
238
|
/**
|
|
237
239
|
* The host contracts in force here (the platform rule: hosts implement
|
|
@@ -240,49 +242,58 @@ export interface PlatformDefaults {
|
|
|
240
242
|
* entries the installer matched against it. A host mounts a module only when
|
|
241
243
|
* it is listed.
|
|
242
244
|
*/
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
245
|
+
interface UiApiManifest {
|
|
246
|
+
/** The contracts in force, name -> MAJOR.MINOR (`{ console: '1.0' }`): one installed theme implements each. */
|
|
247
|
+
readonly contracts: Readonly<Record<string, string>>;
|
|
248
|
+
/** Which installed theme implements each contract, name -> theme slug. */
|
|
249
|
+
readonly hosts?: Readonly<Record<string, string>>;
|
|
250
|
+
readonly modules: ReadonlyArray<UiModuleEntryManifest>;
|
|
251
|
+
}
|
|
252
|
+
interface UiModuleEntryManifest {
|
|
253
|
+
readonly module: string;
|
|
254
|
+
readonly contract: string;
|
|
255
|
+
readonly range: string;
|
|
256
|
+
readonly framework: string;
|
|
257
|
+
}
|
|
258
|
+
interface ApiManifest {
|
|
259
|
+
readonly apiBase: string;
|
|
260
|
+
readonly ui?: UiApiManifest;
|
|
261
|
+
readonly configBase?: string;
|
|
262
|
+
readonly auth?: AuthApiManifest;
|
|
263
|
+
readonly identity?: IdentityApiManifest;
|
|
264
|
+
readonly sections?: SectionApiManifest;
|
|
265
|
+
readonly navi?: NaviApiManifest;
|
|
266
|
+
readonly content?: ContentApiManifest;
|
|
267
|
+
readonly dataGrid?: DataGridApiManifest;
|
|
268
|
+
readonly terminal?: TerminalApiManifest;
|
|
269
|
+
readonly media?: MediaApiManifest;
|
|
270
|
+
readonly document?: DocumentApiManifest;
|
|
271
|
+
readonly vfs?: VfsApiManifest;
|
|
272
|
+
readonly dynamicEntity?: DynamicEntityApiManifest;
|
|
273
|
+
readonly domainExplorer?: DomainExplorerApiManifest;
|
|
274
|
+
readonly editor?: EditorApiManifest;
|
|
275
|
+
readonly viewers?: ViewerApiManifest;
|
|
276
|
+
readonly supportedLocales?: Array<{
|
|
277
|
+
code: string;
|
|
278
|
+
label: string;
|
|
279
|
+
}>;
|
|
280
|
+
readonly platformDefaults?: PlatformDefaults;
|
|
281
|
+
}
|
|
282
|
+
/**
|
|
283
|
+
* GET /api/v1/admin/manifest -- the admin's endpoint map, answered to every signed-in account
|
|
284
|
+
* since 2026-10-06 (no host groups: each call it names is decided by its own route). A server
|
|
285
|
+
* from before that answers at /api/v1/console/manifest, to the console group only. Read once
|
|
286
|
+
* signed in.
|
|
287
|
+
*/
|
|
288
|
+
interface ConsoleManifestResponse {
|
|
289
|
+
readonly manifest: ApiManifest;
|
|
279
290
|
}
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
291
|
+
interface ThemeConfigResponse {
|
|
292
|
+
readonly slug?: string | null;
|
|
293
|
+
readonly feStack?: string | null;
|
|
294
|
+
readonly spaFramework?: string | null;
|
|
295
|
+
readonly assetsUrl?: string | null;
|
|
296
|
+
readonly manifest: ApiManifest;
|
|
286
297
|
}
|
|
287
298
|
/**
|
|
288
299
|
* Replaces {param} tokens in a URL pattern with encoded values.
|
|
@@ -291,7 +302,8 @@ export interface ThemeConfigResponse {
|
|
|
291
302
|
* resolvePattern(manifest.sections!.item, { id: '123' })
|
|
292
303
|
* // -> '/api/v1/sections/123'
|
|
293
304
|
*/
|
|
294
|
-
|
|
305
|
+
declare function resolvePattern(pattern: string, params: Record<string, string>): string;
|
|
306
|
+
|
|
295
307
|
/**
|
|
296
308
|
* Shapes the user-preference store persists.
|
|
297
309
|
*
|
|
@@ -304,11 +316,12 @@ export declare function resolvePattern(pattern: string, params: Record<string, s
|
|
|
304
316
|
* keeps its own local path and nothing that reads a grid preference had to
|
|
305
317
|
* change.
|
|
306
318
|
*/
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
319
|
+
interface DataGridPreference {
|
|
320
|
+
columns?: string[];
|
|
321
|
+
columnWidths?: Record<string, number>;
|
|
310
322
|
}
|
|
311
|
-
|
|
323
|
+
type DataGridPreferences = Record<string, DataGridPreference>;
|
|
324
|
+
|
|
312
325
|
/**
|
|
313
326
|
* Session and current-user DTOs.
|
|
314
327
|
*
|
|
@@ -320,121 +333,123 @@ export type DataGridPreferences = Record<string, DataGridPreference>;
|
|
|
320
333
|
* `ApiService` re-exports them from its own module, so the feature files that
|
|
321
334
|
* import `UserDto` from there are untouched.
|
|
322
335
|
*/
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
}
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
}
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
336
|
+
interface TokenResponse {
|
|
337
|
+
accessToken: string;
|
|
338
|
+
refreshToken: string;
|
|
339
|
+
expiresAt: string;
|
|
340
|
+
user?: UserDto;
|
|
341
|
+
}
|
|
342
|
+
interface UserDto {
|
|
343
|
+
id: string;
|
|
344
|
+
email: string;
|
|
345
|
+
identifier?: string;
|
|
346
|
+
roles: string[];
|
|
347
|
+
uiPrefs?: DataGridPreferences;
|
|
348
|
+
avatarUrl?: string | null;
|
|
349
|
+
firstName?: string | null;
|
|
350
|
+
lastName?: string | null;
|
|
351
|
+
fullName?: string;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
type FieldType = 'text' | 'email' | 'password' | 'number' | 'textarea' | 'select' | 'toggle' | 'date' | 'time' | 'hidden' | 'relation' | 'subform' | 'token-pattern' | 'richtext' | 'localizedText' | 'localizedTextarea' | 'optionsEditor' | 'intlPhone' | 'color';
|
|
355
|
+
type DataSourceType = 'static' | 'enum' | 'api' | 'repo';
|
|
356
|
+
type VisibilityOperator = 'eq' | 'ne' | 'in' | 'notIn' | 'empty' | 'notEmpty';
|
|
357
|
+
interface VisibilityCondition {
|
|
358
|
+
readonly field: string;
|
|
359
|
+
readonly operator: VisibilityOperator;
|
|
360
|
+
readonly value?: unknown;
|
|
361
|
+
}
|
|
362
|
+
type DataSourceWidget = 'select' | 'select-search' | 'select-tree' | 'token-input' | 'media-picker';
|
|
363
|
+
type DataSourceLoading = 'eager' | 'lazy';
|
|
364
|
+
interface DataSourceOption {
|
|
365
|
+
readonly value: unknown;
|
|
366
|
+
readonly label: string;
|
|
367
|
+
readonly parentId?: string | null;
|
|
368
|
+
}
|
|
369
|
+
interface DataSourceDefinition {
|
|
370
|
+
readonly type: DataSourceType;
|
|
371
|
+
readonly bindValue: string;
|
|
372
|
+
readonly bindLabel: string;
|
|
373
|
+
readonly multiple: boolean;
|
|
374
|
+
readonly maxItems?: number;
|
|
375
|
+
readonly options?: ReadonlyArray<DataSourceOption>;
|
|
376
|
+
readonly url?: string;
|
|
377
|
+
readonly widget: DataSourceWidget;
|
|
378
|
+
readonly loading: DataSourceLoading;
|
|
379
|
+
/** Free-form widget-specific configuration block populated from YAML
|
|
380
|
+
* `dataSource.widgetOptions`. Used by media-picker for bindTarget /
|
|
381
|
+
* display / accept / recentlyUsed / hoverPreview, and reserved for
|
|
382
|
+
* future custom widgets. Kept separate from `options` (which is the
|
|
383
|
+
* static-option list) to avoid key collisions. */
|
|
384
|
+
readonly widgetOptions?: Readonly<Record<string, unknown>>;
|
|
385
|
+
}
|
|
386
|
+
interface ValidatorDefinition {
|
|
387
|
+
readonly type: string;
|
|
388
|
+
readonly value?: unknown;
|
|
389
|
+
readonly message?: string;
|
|
390
|
+
}
|
|
391
|
+
interface RelationDefinition {
|
|
392
|
+
readonly cardinality: 'one' | 'many';
|
|
393
|
+
readonly maxItems?: number;
|
|
394
|
+
readonly targetFormId?: string;
|
|
395
|
+
readonly dataSource?: DataSourceDefinition;
|
|
396
|
+
}
|
|
397
|
+
interface SubFormDefinition {
|
|
398
|
+
readonly formId: string;
|
|
399
|
+
readonly relation: 'one' | 'many';
|
|
400
|
+
readonly maxItems?: number;
|
|
401
|
+
}
|
|
402
|
+
interface FieldSecurityPolicy {
|
|
403
|
+
readonly read: ReadonlyArray<string>;
|
|
404
|
+
readonly write: ReadonlyArray<string>;
|
|
405
|
+
}
|
|
406
|
+
interface FieldItem {
|
|
407
|
+
readonly alias: string;
|
|
408
|
+
readonly type: FieldType;
|
|
409
|
+
readonly label: string;
|
|
410
|
+
readonly placeholder?: string;
|
|
411
|
+
readonly hint?: string;
|
|
412
|
+
readonly required: boolean;
|
|
413
|
+
readonly readonly: boolean;
|
|
414
|
+
readonly locked: boolean;
|
|
415
|
+
readonly private: boolean;
|
|
416
|
+
readonly validators: ReadonlyArray<ValidatorDefinition>;
|
|
417
|
+
readonly dataSource?: DataSourceDefinition;
|
|
418
|
+
readonly relation?: RelationDefinition;
|
|
419
|
+
readonly subForm?: SubFormDefinition;
|
|
420
|
+
readonly separators?: ReadonlyArray<string>;
|
|
421
|
+
readonly security?: FieldSecurityPolicy | null;
|
|
422
|
+
readonly autocomplete?: string;
|
|
423
|
+
}
|
|
424
|
+
interface LayoutNode {
|
|
425
|
+
readonly type: 'group' | 'grid' | 'column' | 'tabs' | 'tab' | (string & {});
|
|
426
|
+
readonly name?: string;
|
|
427
|
+
readonly description?: string;
|
|
428
|
+
readonly compact?: boolean;
|
|
429
|
+
readonly width?: number;
|
|
430
|
+
readonly children?: ReadonlyArray<LayoutNode>;
|
|
431
|
+
readonly showWhen?: VisibilityCondition;
|
|
432
|
+
}
|
|
433
|
+
type FormActionType = 'submit' | 'button' | 'reset';
|
|
434
|
+
type FormActionVariant = 'primary' | 'secondary' | 'danger' | 'link';
|
|
421
435
|
/** Terminal form button (Submit / Cancel / ...) -- mirrors PHP FormAction.
|
|
422
436
|
* Resolved by the backend (declared `formOptions.actions`, else lifted from
|
|
423
437
|
* legacy submit fields, else a default Submit). `label` is already translated. */
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
}
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
}
|
|
438
|
+
interface FormAction {
|
|
439
|
+
readonly type: FormActionType;
|
|
440
|
+
readonly label: string;
|
|
441
|
+
readonly name: string;
|
|
442
|
+
readonly variant: FormActionVariant;
|
|
443
|
+
}
|
|
444
|
+
interface FormRenderDefinition {
|
|
445
|
+
readonly id: string;
|
|
446
|
+
readonly context: 'create' | 'edit';
|
|
447
|
+
readonly layout: ReadonlyArray<LayoutNode>;
|
|
448
|
+
readonly items: ReadonlyArray<FieldItem>;
|
|
449
|
+
readonly actions: ReadonlyArray<FormAction>;
|
|
450
|
+
readonly action?: string;
|
|
451
|
+
}
|
|
452
|
+
|
|
438
453
|
/**
|
|
439
454
|
* The collection envelope API Platform wraps every list response in.
|
|
440
455
|
*
|
|
@@ -455,17 +470,18 @@ export interface FormRenderDefinition {
|
|
|
455
470
|
* crashes if it does. Settle it by calling the endpoint, not by merging the
|
|
456
471
|
* types.
|
|
457
472
|
*/
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
}
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
}
|
|
473
|
+
interface HydraView {
|
|
474
|
+
'@id': string;
|
|
475
|
+
'next'?: string;
|
|
476
|
+
'previous'?: string;
|
|
477
|
+
'last'?: string;
|
|
478
|
+
}
|
|
479
|
+
interface HydraCollection<T> {
|
|
480
|
+
'member': T[];
|
|
481
|
+
'totalItems': number;
|
|
482
|
+
'view'?: HydraView;
|
|
483
|
+
}
|
|
484
|
+
|
|
469
485
|
/**
|
|
470
486
|
* The identity endpoints core owns: the session lifecycle, plus the settings
|
|
471
487
|
* read at boot.
|
|
@@ -482,46 +498,47 @@ export interface HydraCollection<T> {
|
|
|
482
498
|
* `ApiService` keeps its own identical signatures and delegates here, so none
|
|
483
499
|
* of the 94 feature files calling it changed.
|
|
484
500
|
*/
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
}
|
|
501
|
+
declare class IdentityApiClient {
|
|
502
|
+
private readonly http;
|
|
503
|
+
private readonly store;
|
|
504
|
+
/** URLs come from the boot manifest, which core already owns. */
|
|
505
|
+
private get manifest();
|
|
506
|
+
login(identifier: string, password: string): Observable<TokenResponse>;
|
|
507
|
+
refresh(refreshToken: string): Observable<TokenResponse>;
|
|
508
|
+
logout(): Observable<void>;
|
|
509
|
+
me(): Observable<UserDto>;
|
|
510
|
+
getSettings(): Observable<Record<string, Record<string, unknown>>>;
|
|
511
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<IdentityApiClient, never>;
|
|
512
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<any>;
|
|
513
|
+
}
|
|
514
|
+
|
|
498
515
|
/**
|
|
499
516
|
* Response of `POST /centrifugo/connection-token`: the JWT the realtime SDK
|
|
500
517
|
* opens its socket with, and what it needs to refresh before expiry.
|
|
501
518
|
*/
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
519
|
+
interface CentrifugoConnectionTokenDto {
|
|
520
|
+
token: string;
|
|
521
|
+
expiresAt: number;
|
|
522
|
+
ttl: number;
|
|
523
|
+
wsUrl: string;
|
|
524
|
+
/**
|
|
525
|
+
* The code the server closes this connection with when the caller's session
|
|
526
|
+
* ended (a sign-out everywhere, a password change, a deactivation). On it the
|
|
527
|
+
* client signs out and ends its calls at once, without reconnecting or waiting
|
|
528
|
+
* for a 401 (2026-09-26). Absent from a server older than that.
|
|
529
|
+
*/
|
|
530
|
+
sessionEndedCode?: number;
|
|
514
531
|
}
|
|
515
532
|
/**
|
|
516
533
|
* Response of `POST /centrifugo/subscription-token`: a per-channel JWT. The
|
|
517
534
|
* SDK asks for one before subscribing to a private namespace, so this is
|
|
518
535
|
* fetched per subscription rather than once per connection.
|
|
519
536
|
*/
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
537
|
+
interface CentrifugoSubscriptionTokenDto {
|
|
538
|
+
channel: string;
|
|
539
|
+
token: string;
|
|
540
|
+
expiresAt: number;
|
|
541
|
+
ttl: number;
|
|
525
542
|
}
|
|
526
543
|
/**
|
|
527
544
|
* The two realtime endpoints, carved out for the same reason the identity ones
|
|
@@ -536,68 +553,89 @@ export interface CentrifugoSubscriptionTokenDto {
|
|
|
536
553
|
*
|
|
537
554
|
* `ApiService` keeps its identical signatures and delegates here, so nothing
|
|
538
555
|
* calling it had to change.
|
|
556
|
+
*
|
|
557
|
+
* !! Access first, then realtime (Dmitry, 2026-09-28): "The console opens a realtime
|
|
558
|
+
* connection before the access check. Reverse the order ... A refused account opens no
|
|
559
|
+
* socket." The socket cannot open without the connection token, and anything may ask for
|
|
560
|
+
* one early -- the ring subscription is built at app init and asks as soon as a user is
|
|
561
|
+
* set, before the guard's answer is in. So the token waits for the console's answer (the
|
|
562
|
+
* guard's own request, not a second one) and a refused sign-in gets none: no request, no
|
|
563
|
+
* socket. A failure to ask is not a refusal, as at the guard.
|
|
564
|
+
*
|
|
565
|
+
* Since 2026-10-06 the connection token is every signed-in account's (Dmitry: "The connection
|
|
566
|
+
* token only opens a socket; each channel is authorized when it is subscribed to"), and a
|
|
567
|
+
* current server answers the admin manifest to every signed-in account, so no
|
|
568
|
+
* sign-in is refused and every one gets its token; each subscription is then decided by its
|
|
569
|
+
* channel. The wait stays for a server from before that, where a refused sign-in still opens
|
|
570
|
+
* no socket.
|
|
539
571
|
*/
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
572
|
+
declare class RealtimeTokenClient {
|
|
573
|
+
/** The error a refused sign-in's connection-token request ends with. */
|
|
574
|
+
static readonly REFUSED = "no console access: no realtime connection";
|
|
575
|
+
private readonly http;
|
|
576
|
+
private readonly store;
|
|
577
|
+
private readonly injector;
|
|
578
|
+
private get manifest();
|
|
579
|
+
connectionToken(): Observable<CentrifugoConnectionTokenDto>;
|
|
580
|
+
subscriptionToken(channel: string): Observable<CentrifugoSubscriptionTokenDto>;
|
|
581
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<RealtimeTokenClient, never>;
|
|
582
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<any>;
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
declare class Login {
|
|
586
|
+
identifier: string;
|
|
587
|
+
password: string;
|
|
588
|
+
static readonly type = "[Auth] Login";
|
|
589
|
+
constructor(identifier: string, password: string);
|
|
590
|
+
}
|
|
591
|
+
declare class Logout {
|
|
592
|
+
static readonly type = "[Auth] Logout";
|
|
593
|
+
}
|
|
594
|
+
declare class RestoreSession {
|
|
595
|
+
static readonly type = "[Auth] Restore Session";
|
|
596
|
+
}
|
|
597
|
+
declare class SetTokens {
|
|
598
|
+
readonly response: TokenResponse;
|
|
599
|
+
static readonly type = "[Auth] Set Tokens";
|
|
600
|
+
constructor(response: TokenResponse);
|
|
565
601
|
}
|
|
566
602
|
/** Merge a partial update into the currently-stored user (e.g. after avatar change). */
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
603
|
+
declare class PatchCurrentUser {
|
|
604
|
+
readonly patch: Partial<UserDto>;
|
|
605
|
+
static readonly type = "[Auth] Patch Current User";
|
|
606
|
+
constructor(patch: Partial<UserDto>);
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
interface AuthStateModel {
|
|
610
|
+
accessToken: string | null;
|
|
611
|
+
refreshToken: string | null;
|
|
612
|
+
expiresAt: string | null;
|
|
613
|
+
user: UserDto | null;
|
|
614
|
+
}
|
|
615
|
+
declare class AuthState {
|
|
616
|
+
private readonly api;
|
|
617
|
+
private readonly prefs;
|
|
618
|
+
login(ctx: StateContext<AuthStateModel>, { identifier, password }: Login): rxjs.Observable<UserDto | null>;
|
|
619
|
+
logout(ctx: StateContext<AuthStateModel>): void;
|
|
620
|
+
restore(ctx: StateContext<AuthStateModel>): void;
|
|
621
|
+
setTokens(ctx: StateContext<AuthStateModel>, { response }: SetTokens): void;
|
|
622
|
+
patchCurrentUser(ctx: StateContext<AuthStateModel>, { patch }: PatchCurrentUser): void;
|
|
623
|
+
static isAuthenticated(state: AuthStateModel): boolean;
|
|
624
|
+
static accessToken(state: AuthStateModel): string | null;
|
|
625
|
+
static refreshToken(state: AuthStateModel): string | null;
|
|
626
|
+
/**
|
|
627
|
+
* Sub-phase Y -- expose the access-token expiry timestamp so
|
|
628
|
+
* preemptive-refresh callers (e.g., Centrifuge getToken
|
|
629
|
+
* callbacks) can decide whether to rotate before issuing the
|
|
630
|
+
* next protected request, instead of relying on the
|
|
631
|
+
* interceptor's reactive 401-retry path.
|
|
632
|
+
*/
|
|
633
|
+
static expiresAt(state: AuthStateModel): string | null;
|
|
634
|
+
static currentUser(state: AuthStateModel): UserDto | null;
|
|
635
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<AuthState, never>;
|
|
636
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<any>;
|
|
637
|
+
}
|
|
638
|
+
|
|
601
639
|
/**
|
|
602
640
|
* Protects routes that require authentication.
|
|
603
641
|
*
|
|
@@ -607,8 +645,15 @@ export declare class AuthState {
|
|
|
607
645
|
* will have placed a non-null (but expired) token in state, causing the guard
|
|
608
646
|
* to pass even though the subsequent config/refresh requests will fail and
|
|
609
647
|
* dispatch Logout. Waiting for ready$ guarantees the auth state is settled.
|
|
648
|
+
*
|
|
649
|
+
* Signed in is not enough: the console is granted, and the server says to whom
|
|
650
|
+
* (ConsoleAccessService). An account it refuses -- a customer who registered on the
|
|
651
|
+
* public site -- is signed out of this sign-in, on the server as well as here, and
|
|
652
|
+
* sent to the login page, which says why. A failure to ask is not a refusal: the console opens, and every request it
|
|
653
|
+
* makes is still decided by the server.
|
|
610
654
|
*/
|
|
611
|
-
|
|
655
|
+
declare const authGuard: CanActivateFn;
|
|
656
|
+
|
|
612
657
|
/**
|
|
613
658
|
* Guards the `/login` route -- redirects already-authenticated
|
|
614
659
|
* callers to admin home so a stale tab parked on `/login` cannot
|
|
@@ -624,9 +669,11 @@ export declare const authGuard: CanActivateFn;
|
|
|
624
669
|
* as anonymous, allow `/login`, and only then have the token
|
|
625
670
|
* populated.
|
|
626
671
|
*/
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
672
|
+
declare const loginPageGuard: CanActivateFn;
|
|
673
|
+
|
|
674
|
+
declare const BYPASS_AUTH: HttpContextToken<boolean>;
|
|
675
|
+
declare const authInterceptor: HttpInterceptorFn;
|
|
676
|
+
|
|
630
677
|
/**
|
|
631
678
|
* Two-layer refresh coordinator: an intra-tab `shareReplay(1)`
|
|
632
679
|
* mutex dedupes concurrent callers within one tab, and a
|
|
@@ -671,62 +718,63 @@ export declare const authInterceptor: HttpInterceptorFn;
|
|
|
671
718
|
* made is never replayed with the token it consumed. A 401 there
|
|
672
719
|
* is the session's end: `Logout`.
|
|
673
720
|
*/
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
}
|
|
721
|
+
declare class AuthRefreshCoordinator {
|
|
722
|
+
private readonly api;
|
|
723
|
+
private readonly store;
|
|
724
|
+
private readonly router;
|
|
725
|
+
private inFlight;
|
|
726
|
+
/**
|
|
727
|
+
* Return the in-flight refresh Observable, or spawn a new one
|
|
728
|
+
* with the supplied refresh-token value.
|
|
729
|
+
*
|
|
730
|
+
* `refusedAccessToken`: the access token the server just answered
|
|
731
|
+
* 401 to, when the caller is answering one. With it, the lock body
|
|
732
|
+
* refreshes unless the shared copy already holds another token;
|
|
733
|
+
* without it (a preemptive refresh), a fresh-looking cached token
|
|
734
|
+
* is handed back as before.
|
|
735
|
+
*
|
|
736
|
+
* Callers subscribe via `switchMap` / `firstValueFrom` to retry
|
|
737
|
+
* their original request with the resolved access token.
|
|
738
|
+
*/
|
|
739
|
+
refresh(refreshToken: string, refusedAccessToken?: string | null): Observable<string>;
|
|
740
|
+
/**
|
|
741
|
+
* Run the refresh body inside `navigator.locks` when the API is
|
|
742
|
+
* available; degrade to direct invocation otherwise. The lock
|
|
743
|
+
* body checks for state already updated by a sibling tab before
|
|
744
|
+
* issuing its own HTTP call.
|
|
745
|
+
*/
|
|
746
|
+
private refreshWithCrossTabLock;
|
|
747
|
+
/**
|
|
748
|
+
* Lock body. Re-reads the shared copy first (see the class note).
|
|
749
|
+
*
|
|
750
|
+
* Answering a refusal: a shared access token other than the refused
|
|
751
|
+
* one is a rotation already made -- taken, and mirrored into this
|
|
752
|
+
* tab's state if its `storage` event has not arrived yet. Otherwise,
|
|
753
|
+
* or for a preemptive caller whose cached token no longer looks
|
|
754
|
+
* fresh, the HTTP refresh, with the newest refresh token: shared,
|
|
755
|
+
* then state, then the caller's.
|
|
756
|
+
*/
|
|
757
|
+
private executeRefresh;
|
|
758
|
+
/**
|
|
759
|
+
* The session as the tabs share it, or null when there is none to
|
|
760
|
+
* read: no storage, nothing stored (signed out), or a value that is
|
|
761
|
+
* not a complete token set.
|
|
762
|
+
*/
|
|
763
|
+
private readShared;
|
|
764
|
+
/**
|
|
765
|
+
* For a preemptive caller only: return the current access token when
|
|
766
|
+
* its expiry is more than `FRESH_TOKEN_GRACE_MS` milliseconds away --
|
|
767
|
+
* signalling that some other tab refreshed for us. The shared copy
|
|
768
|
+
* first, which a sibling's rotation reaches before this tab's state
|
|
769
|
+
* does (mirrored here when it is newer); this tab's state when there
|
|
770
|
+
* is none. Null otherwise. Never asked for a token the server
|
|
771
|
+
* refused: a refused token's expiry looks just as fresh.
|
|
772
|
+
*/
|
|
773
|
+
private tryConsumeFreshState;
|
|
774
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<AuthRefreshCoordinator, never>;
|
|
775
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<any>;
|
|
776
|
+
}
|
|
777
|
+
|
|
730
778
|
/**
|
|
731
779
|
* Bridges shared `localStorage` mutations into the NGXS auth
|
|
732
780
|
* state so a refresh executed in one tab silently propagates to
|
|
@@ -751,14 +799,15 @@ export declare class AuthRefreshCoordinator {
|
|
|
751
799
|
* Started from `AppInitService.load()` so the listener is in
|
|
752
800
|
* place before any protected HTTP fires.
|
|
753
801
|
*/
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
}
|
|
802
|
+
declare class CrossTabAuthSyncService {
|
|
803
|
+
private readonly store;
|
|
804
|
+
private listenerAttached;
|
|
805
|
+
start(): void;
|
|
806
|
+
private readonly handleStorageEvent;
|
|
807
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<CrossTabAuthSyncService, never>;
|
|
808
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<any>;
|
|
809
|
+
}
|
|
810
|
+
|
|
762
811
|
/**
|
|
763
812
|
* The elevation state of the signed-in session: the wire shape of
|
|
764
813
|
* `GET /auth/elevation`, returned again by `POST` on a grant.
|
|
@@ -769,74 +818,76 @@ export declare class CrossTabAuthSyncService {
|
|
|
769
818
|
* capability flags are the two readings of ONE server-side decision, and the
|
|
770
819
|
* client keeps no third.
|
|
771
820
|
*/
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
|
|
787
|
-
|
|
788
|
-
|
|
789
|
-
|
|
790
|
-
|
|
821
|
+
interface ElevationState {
|
|
822
|
+
/** Whether this session is elevated right now. */
|
|
823
|
+
elevated: boolean;
|
|
824
|
+
/** When the current elevation ends (ISO 8601); absent or null when not elevated. */
|
|
825
|
+
expiresAt?: string | null;
|
|
826
|
+
/** When the current elevation was granted (ISO 8601); absent or null when not elevated. */
|
|
827
|
+
grantedAt?: string | null;
|
|
828
|
+
/** How the LAST elevation of this session ended. Rendered by the prompt before it asks. */
|
|
829
|
+
ended: ElevationEnded;
|
|
830
|
+
/** The lifetime a grant made now would get, in seconds (the setting, clamped to the ceiling). */
|
|
831
|
+
lifetimeSeconds: number;
|
|
832
|
+
/** The most any elevation can last, fixed in code. */
|
|
833
|
+
lifetimeCeilingSeconds: number;
|
|
834
|
+
/** Whether the CURRENT elevation was confirmed by a second factor; absent or null when not elevated. */
|
|
835
|
+
factorConfirmed?: boolean | null;
|
|
836
|
+
/** Whether a grant made now must carry a code from the person's own authenticator. */
|
|
837
|
+
mfaRequired: boolean;
|
|
838
|
+
/** What the prompt shows before asking; two of the codes mean this account cannot elevate yet. */
|
|
839
|
+
warnings: ElevationWarning[];
|
|
791
840
|
}
|
|
792
841
|
/**
|
|
793
842
|
* `closed` is the beacon (the panel was closed or reloaded), `dropped_by_user`
|
|
794
843
|
* the explicit drop, `refresh_from_other_pair` the server's refresh tripwire,
|
|
795
844
|
* `never` a session that has not elevated yet.
|
|
796
845
|
*/
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
846
|
+
type ElevationEndedReason = 'expired' | 'closed' | 'dropped_by_user' | 'refresh_from_other_pair' | 'never';
|
|
847
|
+
interface ElevationEnded {
|
|
848
|
+
reason: ElevationEndedReason;
|
|
849
|
+
/** ISO 8601, null for `never`. */
|
|
850
|
+
at: string | null;
|
|
802
851
|
}
|
|
803
852
|
/**
|
|
804
|
-
* `elevation.password_not_set` and `elevation.factor_missing` mean
|
|
805
|
-
*
|
|
853
|
+
* `elevation.password_not_set` and `elevation.factor_missing` mean this
|
|
854
|
+
* account cannot elevate (it has no password, or no confirmed factor, of its
|
|
855
|
+
* own) and the message says which;
|
|
806
856
|
* `elevation.mfa_off` is the recommendation carried while the switch is off.
|
|
807
857
|
*/
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
}
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
858
|
+
interface ElevationWarning {
|
|
859
|
+
/** The three the server documents today; the client renders any it does not know as text. */
|
|
860
|
+
code: string;
|
|
861
|
+
message: string;
|
|
862
|
+
}
|
|
863
|
+
declare const ELEVATION_WARNING_PASSWORD_NOT_SET = "elevation.password_not_set";
|
|
864
|
+
declare const ELEVATION_WARNING_FACTOR_MISSING = "elevation.factor_missing";
|
|
865
|
+
declare const ELEVATION_WARNING_MFA_OFF = "elevation.mfa_off";
|
|
816
866
|
/** Why the elevation of a session changed, for whoever refetches on it. */
|
|
817
|
-
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
867
|
+
type ElevationChangeKind = 'granted' | 'dropped' | 'expired' | 'observed';
|
|
868
|
+
interface ElevationChange {
|
|
869
|
+
kind: ElevationChangeKind;
|
|
870
|
+
state: ElevationState;
|
|
821
871
|
}
|
|
822
872
|
/** The two reasons the client sends when it drops an elevation itself. */
|
|
823
|
-
|
|
873
|
+
type ElevationDropReason = 'dropped_by_user' | 'closed';
|
|
874
|
+
|
|
824
875
|
/**
|
|
825
876
|
* What the elevation prompt is told when it opens.
|
|
826
877
|
*
|
|
827
878
|
* `state` is the server's answer at the moment of asking -- `ended.reason` is
|
|
828
879
|
* rendered first (a person asked for a password again after F5
|
|
829
|
-
* with no explanation reads it as a fault), `warnings` may say
|
|
830
|
-
*
|
|
880
|
+
* with no explanation reads it as a fault), `warnings` may say this
|
|
881
|
+
* account cannot elevate yet, `mfaRequired` decides whether a code field
|
|
831
882
|
* shows from the start.
|
|
832
883
|
*
|
|
833
884
|
* `refusal` is the server's own sentence for the action that was refused
|
|
834
885
|
* ("Permission denied: cannot write '/docs/...'"), when a 403 opened the
|
|
835
886
|
* prompt; absent when a disabled control offered elevation instead.
|
|
836
887
|
*/
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
888
|
+
interface ElevationPromptRequest {
|
|
889
|
+
state: ElevationState;
|
|
890
|
+
refusal?: string;
|
|
840
891
|
}
|
|
841
892
|
/**
|
|
842
893
|
* The prompt itself is a dialog, and dialogs are the kit's -- a layer above
|
|
@@ -849,21 +900,40 @@ export interface ElevationPromptRequest {
|
|
|
849
900
|
* false when the person declined or could not. The port does not retry the
|
|
850
901
|
* refused action; the interceptor does, on true.
|
|
851
902
|
*/
|
|
852
|
-
|
|
853
|
-
|
|
903
|
+
interface ElevationPromptPort {
|
|
904
|
+
open(request: ElevationPromptRequest): Observable<boolean>;
|
|
854
905
|
}
|
|
855
906
|
/**
|
|
856
907
|
* Optional by design: with nothing bound, a 403 stays a 403 and the state
|
|
857
908
|
* service still answers -- an application without an admin panel has no
|
|
858
909
|
* prompt to show and loses nothing.
|
|
859
910
|
*/
|
|
860
|
-
|
|
911
|
+
declare const ELEVATION_PROMPT: InjectionToken<ElevationPromptPort>;
|
|
912
|
+
/**
|
|
913
|
+
* Told when an action a person took was refused for want of elevation: a
|
|
914
|
+
* write (not a read, not a background request) answered by a stamped 403.
|
|
915
|
+
*
|
|
916
|
+
* It must not open the prompt. The prompt opens only when the person asks for
|
|
917
|
+
* it -- the application shows the refusal and offers elevation as something to
|
|
918
|
+
* click (a notice with an Elevate button), and the click is what calls
|
|
919
|
+
* {@link ElevationService.offerFor}. A refused action is not repeated by
|
|
920
|
+
* itself after a grant: the person repeats it.
|
|
921
|
+
*
|
|
922
|
+
* `refusal` is the server's sentence for the refused action, already made
|
|
923
|
+
* readable by the error service.
|
|
924
|
+
*/
|
|
925
|
+
interface ElevationNoticePort {
|
|
926
|
+
refused(refusal: string): void;
|
|
927
|
+
}
|
|
928
|
+
/** Optional, like {@link ELEVATION_PROMPT}: with nothing bound, a refusal is only a 403. */
|
|
929
|
+
declare const ELEVATION_NOTICE: InjectionToken<ElevationNoticePort>;
|
|
930
|
+
|
|
861
931
|
/**
|
|
862
932
|
* Set on a request that must never open the elevation prompt: the elevation
|
|
863
933
|
* endpoint's own calls (a wrong password is a 403 and is not a refused action),
|
|
864
934
|
* and any caller that would rather receive its 403 and decide for itself.
|
|
865
935
|
*/
|
|
866
|
-
|
|
936
|
+
declare const BYPASS_ELEVATION: HttpContextToken<boolean>;
|
|
867
937
|
/**
|
|
868
938
|
* The elevation state of this session, read from ONE place: the server.
|
|
869
939
|
*
|
|
@@ -899,220 +969,302 @@ export declare const BYPASS_ELEVATION: HttpContextToken<boolean>;
|
|
|
899
969
|
* whose refusal came from the VFS permission service under another module's
|
|
900
970
|
* path (media, document and word templates, content distribution).
|
|
901
971
|
*/
|
|
902
|
-
|
|
903
|
-
export declare class ElevationService {
|
|
904
|
-
private readonly http;
|
|
905
|
-
private readonly store;
|
|
906
|
-
private readonly prompt;
|
|
907
|
-
private readonly destroyRef;
|
|
908
|
-
/** The last answer the server gave, exactly as it gave it. */
|
|
909
|
-
private readonly served;
|
|
910
|
-
/**
|
|
911
|
-
* The state as it stands NOW; null until the first read from the server.
|
|
912
|
-
*
|
|
913
|
-
* Deliberately NOT a `computed`. A cached derivation is only recomputed
|
|
914
|
-
* when one of its dependencies changes, and nothing changes when an
|
|
915
|
-
* elevation simply runs out -- so it would keep answering `elevated: true`
|
|
916
|
-
* past the moment the server itself named, for as long as the machine
|
|
917
|
-
* happened to be asleep.
|
|
918
|
-
*
|
|
919
|
-
* Reading past `expiresAt` also demotes the STORED answer. The value
|
|
920
|
-
* returned here is already correct without that write; the write is only
|
|
921
|
-
* how cached readers (a `computed`, an OnPush template) find out.
|
|
922
|
-
*/
|
|
923
|
-
readonly state: () => ElevationState | null;
|
|
924
|
-
/** Convenience over `state`: false until the server has said otherwise. */
|
|
925
|
-
readonly elevated: () => boolean;
|
|
926
|
-
/** `expiresAt` as a Date, null when not elevated. */
|
|
927
|
-
readonly expiresAt: () => Date | null;
|
|
928
|
-
/**
|
|
929
|
-
* Every change of elevation state: a grant, a drop (by this person, by the
|
|
930
|
-
* beacon, by the tripwire), an expiry, or an observed change of a live
|
|
931
|
-
* grant's `expiresAt`. A refresh that reports the same state emits nothing,
|
|
932
|
-
* so subscribers that refetch a listing on it never refetch for no reason.
|
|
933
|
-
*/
|
|
934
|
-
readonly changes$: Subject<ElevationChange>;
|
|
935
|
-
private expiryTimer;
|
|
936
|
-
private pendingPrompt;
|
|
937
|
-
private expiryAnnouncePending;
|
|
938
|
-
private lastReconcileAt;
|
|
939
|
-
constructor();
|
|
940
|
-
/** The manifest carries the URL; without it the service is inert, not broken. */
|
|
941
|
-
get available(): boolean;
|
|
942
|
-
private get url();
|
|
943
|
-
/**
|
|
944
|
-
* Whether a URL belongs to THIS API: a relative one under the manifest's
|
|
945
|
-
* `apiBase`, or an absolute one on this origin under it. The interceptor
|
|
946
|
-
* acts on a stamped refusal from here and from nowhere else -- a third
|
|
947
|
-
* party answering 403 with a header of the same name is not an invitation
|
|
948
|
-
* to elevate this session.
|
|
949
|
-
*/
|
|
950
|
-
isThisApi(url: string): boolean;
|
|
951
|
-
/** Ask the server. Applies the answer and announces it when it differs. */
|
|
952
|
-
refresh(): Observable<ElevationState>;
|
|
953
|
-
/**
|
|
954
|
-
* Elevate with the admin password and, when the switch is on, a code from
|
|
955
|
-
* the admin authenticator. Errors are the dialog's to branch on: 403 wrong
|
|
956
|
-
* password or wrong / replayed code, 428 a code is required, 409 the
|
|
957
|
-
* installation cannot elevate yet, 429 throttled. They pass through
|
|
958
|
-
* untouched, and never open the prompt (BYPASS_ELEVATION).
|
|
959
|
-
*/
|
|
960
|
-
elevate(password: string, code?: string): Observable<ElevationState>;
|
|
961
|
-
/** Drop the elevation now. Idempotent on the server: nothing live is still a 204. */
|
|
962
|
-
drop(reason?: ElevationDropReason): Observable<ElevationState>;
|
|
963
|
-
/**
|
|
964
|
-
* What the interceptor calls on a 403: ask the server whether this session
|
|
965
|
-
* is elevated, and only when it is not, offer the prompt. A 403 while
|
|
966
|
-
* elevated is a real refusal -- elevation would not change it -- and is
|
|
967
|
-
* handed back as such (false, no prompt).
|
|
968
|
-
*/
|
|
969
|
-
offerFor(refusal?: string): Observable<boolean>;
|
|
970
|
-
/**
|
|
971
|
-
* Open the prompt, or join the one already open. Resolves to whether the
|
|
972
|
-
* session is elevated once the dialog closes.
|
|
973
|
-
*/
|
|
974
|
-
requestElevation(request: ElevationPromptRequest): Observable<boolean>;
|
|
975
|
-
private options;
|
|
976
|
-
private inert;
|
|
977
|
-
/**
|
|
978
|
-
* Store the answer, schedule the expiry read, announce a change.
|
|
979
|
-
*
|
|
980
|
-
* `explicit` names the change when the caller knows it (a grant). For a
|
|
981
|
-
* plain refresh the kind is derived from the two states: elevated -> not
|
|
982
|
-
* is `expired` when the server says so and `dropped` otherwise (the person,
|
|
983
|
-
* another tab, the beacon, the tripwire); not -> elevated is a grant seen
|
|
984
|
-
* from another tab; a live grant whose `expiresAt` moved is `observed`.
|
|
985
|
-
*
|
|
986
|
-
* `prev` is read RAW rather than through `state()`: the demotion is not
|
|
987
|
-
* wanted here (this answer supersedes the stored one either way), and a
|
|
988
|
-
* read that demoted would queue an expiry announcement the server's own
|
|
989
|
-
* answer is about to make.
|
|
990
|
-
*/
|
|
991
|
-
private apply;
|
|
992
|
-
private kindOf;
|
|
993
|
-
/**
|
|
994
|
-
* Tell the listings that this session's elevation ran out, once, and NOT
|
|
995
|
-
* from inside the read that noticed: a subscriber refetches on this, and a
|
|
996
|
-
* refetch started in the middle of a change detection pass is a write
|
|
997
|
-
* during a read. The microtask runs at the end of the current task, before
|
|
998
|
-
* anything can paint.
|
|
999
|
-
*
|
|
1000
|
-
* A server answer arriving first clears the flag, so the announcement is
|
|
1001
|
-
* made by whichever of the two got there first, never by both. Other tabs
|
|
1002
|
-
* are NOT told: each one reaches the same conclusion from the same
|
|
1003
|
-
* `expiresAt` on its own read, so the storage write would only buy a
|
|
1004
|
-
* thundering herd of GETs.
|
|
1005
|
-
*/
|
|
1006
|
-
private announceExpiryLater;
|
|
1007
|
-
/**
|
|
1008
|
-
* Ask the server again at `expiresAt` -- an OPTIMISATION, not the
|
|
1009
|
-
* mechanism. `state()` already reports an elapsed grant as expired without
|
|
1010
|
-
* being told, so a timer that a sleeping machine swallows, or that a
|
|
1011
|
-
* background tab throttles to once a minute, now costs freshness rather
|
|
1012
|
-
* than correctness. What it buys is the listing refetch happening at the
|
|
1013
|
-
* moment of expiry instead of at the next read.
|
|
1014
|
-
*
|
|
1015
|
-
* Do not flip a local flag here: the lifetime counts from the grant on the
|
|
1016
|
-
* server's clock, and if the server still says elevated at that moment
|
|
1017
|
-
* (skew), the honest thing is to ask again shortly.
|
|
1018
|
-
*/
|
|
1019
|
-
private schedule;
|
|
1020
|
-
/**
|
|
1021
|
-
* One more attempt, then stop. Giving up is affordable now and was not
|
|
1022
|
-
* before: the state a failed read leaves behind is still read through the
|
|
1023
|
-
* arithmetic above, so an unreachable server cannot leave this tab
|
|
1024
|
-
* believing it is elevated. What is lost is only news from elsewhere -- a
|
|
1025
|
-
* grant or a drop in another session -- and that is what the tab picks up
|
|
1026
|
-
* when it next comes into view.
|
|
1027
|
-
*/
|
|
1028
|
-
private scheduleRetry;
|
|
1029
|
-
private clearTimer;
|
|
1030
|
-
private announceToOtherTabs;
|
|
1031
|
-
private readonly onStorage;
|
|
1032
|
-
private readonly onFocus;
|
|
1033
|
-
private readonly onVisibilityChange;
|
|
1034
|
-
/**
|
|
1035
|
-
* The tab is back. Read first -- that alone ends an elevation that ran out
|
|
1036
|
-
* while it was away, with no request and no network to depend on -- then
|
|
1037
|
-
* ask the server for what the read cannot know: a grant or a drop made
|
|
1038
|
-
* elsewhere, or a lifetime the server moved.
|
|
1039
|
-
*/
|
|
1040
|
-
private reconcile;
|
|
1041
|
-
static ɵfac: i0.ɵɵFactoryDeclaration<ElevationService, never>;
|
|
1042
|
-
static ɵprov: i0.ɵɵInjectableDeclaration<any>;
|
|
1043
|
-
}
|
|
972
|
+
declare const ELEVATION_REQUIRED_HEADER = "X-Elevation-Required";
|
|
1044
973
|
/**
|
|
1045
|
-
*
|
|
1046
|
-
*
|
|
1047
|
-
*
|
|
1048
|
-
*
|
|
1049
|
-
* Any route of this API can carry it -- the VFS, a media asset's
|
|
1050
|
-
* permissions, a document template, an account's deletion -- so nothing here
|
|
1051
|
-
* lists URLs; the list that stood here lagged the server by every route whose
|
|
1052
|
-
* refusal came from the VFS permission service under another module's path.
|
|
1053
|
-
*
|
|
1054
|
-
* On the stamp: ask the server whether this session is elevated, and only when
|
|
1055
|
-
* it is NOT open the prompt; on a grant send the refused request again, once.
|
|
1056
|
-
* A 403 without the stamp is a refusal for another reason and is handed back
|
|
1057
|
-
* as it came; so are a 403 while elevated, a declined prompt, and an
|
|
1058
|
-
* installation that cannot elevate -- nothing here swallows a refusal. A
|
|
1059
|
-
* stamped 403 from anywhere but this API is ignored: the header is the
|
|
1060
|
-
* server's word, and only this server's counts.
|
|
1061
|
-
*
|
|
1062
|
-
* Order in the chain: BEFORE the auth interceptor, so the retry re-enters it
|
|
1063
|
-
* and carries the token current at that moment (the prompt may have outlived
|
|
1064
|
-
* a refresh). The elevation endpoint's own calls carry BYPASS_ELEVATION: a
|
|
1065
|
-
* wrong password is a 403 and is not a refused action.
|
|
974
|
+
* Set on a request no person asked for: a realtime token, a preferences sync, a
|
|
975
|
+
* poll. Its refusal is never announced -- not by a dialog and not by a notice --
|
|
976
|
+
* because nobody is waiting on it and nobody can act on it. The caller still
|
|
977
|
+
* receives the 403 and decides what its own state shows.
|
|
1066
978
|
*/
|
|
1067
|
-
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1078
|
-
|
|
1079
|
-
|
|
1080
|
-
|
|
1081
|
-
|
|
1082
|
-
|
|
1083
|
-
|
|
1084
|
-
|
|
1085
|
-
|
|
1086
|
-
|
|
1087
|
-
|
|
1088
|
-
|
|
1089
|
-
|
|
1090
|
-
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1100
|
-
|
|
1101
|
-
|
|
1102
|
-
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1109
|
-
|
|
1110
|
-
|
|
1111
|
-
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1115
|
-
|
|
979
|
+
declare const BACKGROUND_REQUEST: HttpContextToken<boolean>;
|
|
980
|
+
/**
|
|
981
|
+
* Whether a failed request was refused for want of elevation: a 403 carrying
|
|
982
|
+
* the server's stamp. The one test every reader of a refusal uses -- the
|
|
983
|
+
* interceptor, a page's inline state, the error text -- so they cannot disagree.
|
|
984
|
+
*/
|
|
985
|
+
declare function isElevationRefusal(err: unknown): boolean;
|
|
986
|
+
declare class ElevationService {
|
|
987
|
+
private readonly http;
|
|
988
|
+
private readonly store;
|
|
989
|
+
private readonly prompt;
|
|
990
|
+
private readonly destroyRef;
|
|
991
|
+
/** The last answer the server gave, exactly as it gave it. */
|
|
992
|
+
private readonly served;
|
|
993
|
+
/**
|
|
994
|
+
* The state as it stands NOW; null until the first read from the server.
|
|
995
|
+
*
|
|
996
|
+
* Deliberately NOT a `computed`. A cached derivation is only recomputed
|
|
997
|
+
* when one of its dependencies changes, and nothing changes when an
|
|
998
|
+
* elevation simply runs out -- so it would keep answering `elevated: true`
|
|
999
|
+
* past the moment the server itself named, for as long as the machine
|
|
1000
|
+
* happened to be asleep.
|
|
1001
|
+
*
|
|
1002
|
+
* Reading past `expiresAt` also demotes the STORED answer. The value
|
|
1003
|
+
* returned here is already correct without that write; the write is only
|
|
1004
|
+
* how cached readers (a `computed`, an OnPush template) find out.
|
|
1005
|
+
*/
|
|
1006
|
+
readonly state: () => ElevationState | null;
|
|
1007
|
+
/** Convenience over `state`: false until the server has said otherwise. */
|
|
1008
|
+
readonly elevated: () => boolean;
|
|
1009
|
+
/** `expiresAt` as a Date, null when not elevated. */
|
|
1010
|
+
readonly expiresAt: () => Date | null;
|
|
1011
|
+
/**
|
|
1012
|
+
* Every change of elevation state: a grant, a drop (by this person, by the
|
|
1013
|
+
* beacon, by the tripwire), an expiry, or an observed change of a live
|
|
1014
|
+
* grant's `expiresAt`. A refresh that reports the same state emits nothing,
|
|
1015
|
+
* so subscribers that refetch a listing on it never refetch for no reason.
|
|
1016
|
+
*/
|
|
1017
|
+
readonly changes$: Subject<ElevationChange>;
|
|
1018
|
+
private expiryTimer;
|
|
1019
|
+
private pendingPrompt;
|
|
1020
|
+
private expiryAnnouncePending;
|
|
1021
|
+
private lastReconcileAt;
|
|
1022
|
+
constructor();
|
|
1023
|
+
/** The manifest carries the URL; without it the service is inert, not broken. */
|
|
1024
|
+
get available(): boolean;
|
|
1025
|
+
private get url();
|
|
1026
|
+
/**
|
|
1027
|
+
* Whether a URL belongs to THIS API: a relative one under the manifest's
|
|
1028
|
+
* `apiBase`, or an absolute one on this origin under it. The interceptor
|
|
1029
|
+
* acts on a stamped refusal from here and from nowhere else -- a third
|
|
1030
|
+
* party answering 403 with a header of the same name is not an invitation
|
|
1031
|
+
* to elevate this session.
|
|
1032
|
+
*/
|
|
1033
|
+
isThisApi(url: string): boolean;
|
|
1034
|
+
/** Ask the server. Applies the answer and announces it when it differs. */
|
|
1035
|
+
refresh(): Observable<ElevationState>;
|
|
1036
|
+
/**
|
|
1037
|
+
* Elevate with the person's own password and, when the switch is on, a
|
|
1038
|
+
* code from their own authenticator. Errors are the dialog's to branch on:
|
|
1039
|
+
* 403 not a member of the admin group, wrong password, or wrong / replayed
|
|
1040
|
+
* code, 428 a code is required, 409 this account cannot elevate yet, 429
|
|
1041
|
+
* throttled. They pass through
|
|
1042
|
+
* untouched, and never open the prompt (BYPASS_ELEVATION).
|
|
1043
|
+
*/
|
|
1044
|
+
elevate(password: string, code?: string): Observable<ElevationState>;
|
|
1045
|
+
/** Drop the elevation now. Idempotent on the server: nothing live is still a 204. */
|
|
1046
|
+
drop(reason?: ElevationDropReason): Observable<ElevationState>;
|
|
1047
|
+
/**
|
|
1048
|
+
* What a person's explicit request for elevation calls -- an Elevate button,
|
|
1049
|
+
* or a control that already knows its action needs elevation: ask the
|
|
1050
|
+
* server whether this session is elevated, and only when it is not, open
|
|
1051
|
+
* the prompt. Nothing calls it on a response by itself (the interceptor
|
|
1052
|
+
* only tells {@link ELEVATION_NOTICE}). Elevated already is false, no
|
|
1053
|
+
* prompt: elevation would change nothing.
|
|
1054
|
+
*/
|
|
1055
|
+
offerFor(refusal?: string): Observable<boolean>;
|
|
1056
|
+
/**
|
|
1057
|
+
* Open the prompt, or join the one already open. Resolves to whether the
|
|
1058
|
+
* session is elevated once the dialog closes.
|
|
1059
|
+
*/
|
|
1060
|
+
requestElevation(request: ElevationPromptRequest): Observable<boolean>;
|
|
1061
|
+
private options;
|
|
1062
|
+
private inert;
|
|
1063
|
+
/**
|
|
1064
|
+
* Store the answer, schedule the expiry read, announce a change.
|
|
1065
|
+
*
|
|
1066
|
+
* `explicit` names the change when the caller knows it (a grant). For a
|
|
1067
|
+
* plain refresh the kind is derived from the two states: elevated -> not
|
|
1068
|
+
* is `expired` when the server says so and `dropped` otherwise (the person,
|
|
1069
|
+
* another tab, the beacon, the tripwire); not -> elevated is a grant seen
|
|
1070
|
+
* from another tab; a live grant whose `expiresAt` moved is `observed`.
|
|
1071
|
+
*
|
|
1072
|
+
* `prev` is read RAW rather than through `state()`: the demotion is not
|
|
1073
|
+
* wanted here (this answer supersedes the stored one either way), and a
|
|
1074
|
+
* read that demoted would queue an expiry announcement the server's own
|
|
1075
|
+
* answer is about to make.
|
|
1076
|
+
*/
|
|
1077
|
+
private apply;
|
|
1078
|
+
private kindOf;
|
|
1079
|
+
/**
|
|
1080
|
+
* Tell the listings that this session's elevation ran out, once, and NOT
|
|
1081
|
+
* from inside the read that noticed: a subscriber refetches on this, and a
|
|
1082
|
+
* refetch started in the middle of a change detection pass is a write
|
|
1083
|
+
* during a read. The microtask runs at the end of the current task, before
|
|
1084
|
+
* anything can paint.
|
|
1085
|
+
*
|
|
1086
|
+
* A server answer arriving first clears the flag, so the announcement is
|
|
1087
|
+
* made by whichever of the two got there first, never by both. Other tabs
|
|
1088
|
+
* are NOT told: each one reaches the same conclusion from the same
|
|
1089
|
+
* `expiresAt` on its own read, so the storage write would only buy a
|
|
1090
|
+
* thundering herd of GETs.
|
|
1091
|
+
*/
|
|
1092
|
+
private announceExpiryLater;
|
|
1093
|
+
/**
|
|
1094
|
+
* Ask the server again at `expiresAt` -- an OPTIMISATION, not the
|
|
1095
|
+
* mechanism. `state()` already reports an elapsed grant as expired without
|
|
1096
|
+
* being told, so a timer that a sleeping machine swallows, or that a
|
|
1097
|
+
* background tab throttles to once a minute, now costs freshness rather
|
|
1098
|
+
* than correctness. What it buys is the listing refetch happening at the
|
|
1099
|
+
* moment of expiry instead of at the next read.
|
|
1100
|
+
*
|
|
1101
|
+
* Do not flip a local flag here: the lifetime counts from the grant on the
|
|
1102
|
+
* server's clock, and if the server still says elevated at that moment
|
|
1103
|
+
* (skew), the honest thing is to ask again shortly.
|
|
1104
|
+
*/
|
|
1105
|
+
private schedule;
|
|
1106
|
+
/**
|
|
1107
|
+
* One more attempt, then stop. Giving up is affordable now and was not
|
|
1108
|
+
* before: the state a failed read leaves behind is still read through the
|
|
1109
|
+
* arithmetic above, so an unreachable server cannot leave this tab
|
|
1110
|
+
* believing it is elevated. What is lost is only news from elsewhere -- a
|
|
1111
|
+
* grant or a drop in another session -- and that is what the tab picks up
|
|
1112
|
+
* when it next comes into view.
|
|
1113
|
+
*/
|
|
1114
|
+
private scheduleRetry;
|
|
1115
|
+
private clearTimer;
|
|
1116
|
+
private announceToOtherTabs;
|
|
1117
|
+
private readonly onStorage;
|
|
1118
|
+
private readonly onFocus;
|
|
1119
|
+
private readonly onVisibilityChange;
|
|
1120
|
+
/**
|
|
1121
|
+
* The tab is back. Read first -- that alone ends an elevation that ran out
|
|
1122
|
+
* while it was away, with no request and no network to depend on -- then
|
|
1123
|
+
* ask the server for what the read cannot know: a grant or a drop made
|
|
1124
|
+
* elsewhere, or a lifetime the server moved.
|
|
1125
|
+
*/
|
|
1126
|
+
private reconcile;
|
|
1127
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<ElevationService, never>;
|
|
1128
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<any>;
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
/**
|
|
1132
|
+
* A refusal for want of elevation is a 403 the server stamps `X-Elevation-Required`
|
|
1133
|
+
* where its gate refused a member who has not elevated ({@link isElevationRefusal}).
|
|
1134
|
+
* This interceptor never opens the elevation prompt. The prompt opens only when a
|
|
1135
|
+
* person asks for it: an Elevate button, or an action whose control already knows it
|
|
1136
|
+
* needs elevation and offers it ({@link ElevationService.offerFor}).
|
|
1137
|
+
*
|
|
1138
|
+
* What it does with a stamped 403:
|
|
1139
|
+
* - On a write a person made (POST, PUT, PATCH, DELETE without
|
|
1140
|
+
* {@link BACKGROUND_REQUEST}): tells {@link ELEVATION_NOTICE}, which shows the
|
|
1141
|
+
* refusal with an Elevate button. The request is not held and not repeated; the
|
|
1142
|
+
* caller receives the 403 at once, and after elevating the person repeats the action.
|
|
1143
|
+
* - On a read: nothing. Reads are what a page makes as it loads and what polls make in
|
|
1144
|
+
* the background, and the page shows its own state for a refused one -- an inline
|
|
1145
|
+
* "needs an elevated session" with an Elevate button, or the feature hidden.
|
|
1146
|
+
* - On a background request: nothing. Nobody is waiting on it.
|
|
1147
|
+
*
|
|
1148
|
+
* Until 2026-10-08 any stamped 403 opened the prompt, so the setup reads of almost every
|
|
1149
|
+
* console page, and a realtime token at every load, opened it for an administrator who
|
|
1150
|
+
* had not elevated: 13 of 15 pages on a menu click, 15 of 15 on a reload (measured).
|
|
1151
|
+
*
|
|
1152
|
+
* The elevation endpoint's own calls carry {@link BYPASS_ELEVATION}: a wrong password is a
|
|
1153
|
+
* 403 and is not a refused action. A stamped 403 from anywhere but this API is ignored:
|
|
1154
|
+
* the header is the server's word, and only this server's counts.
|
|
1155
|
+
*/
|
|
1156
|
+
declare const elevationInterceptor: HttpInterceptorFn;
|
|
1157
|
+
|
|
1158
|
+
declare class LoginComponent {
|
|
1159
|
+
identifier: string;
|
|
1160
|
+
password: string;
|
|
1161
|
+
readonly loading: i0.WritableSignal<boolean>;
|
|
1162
|
+
readonly error: i0.WritableSignal<string | null>;
|
|
1163
|
+
private readonly store;
|
|
1164
|
+
private readonly router;
|
|
1165
|
+
/** Shown when the guard signed an account out because the console is not granted to it. */
|
|
1166
|
+
static readonly NO_CONSOLE_ACCESS = "This account has no access to the console.";
|
|
1167
|
+
constructor();
|
|
1168
|
+
submit(): void;
|
|
1169
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<LoginComponent, never>;
|
|
1170
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<LoginComponent, "coolms-admin-login", never, {}, {}, never, never, true, never>;
|
|
1171
|
+
}
|
|
1172
|
+
|
|
1173
|
+
declare class AppInitService {
|
|
1174
|
+
private readonly http;
|
|
1175
|
+
private readonly store;
|
|
1176
|
+
private readonly router;
|
|
1177
|
+
private readonly location;
|
|
1178
|
+
private readonly prefs;
|
|
1179
|
+
private readonly crossTabSync;
|
|
1180
|
+
private readonly refreshCoordinator;
|
|
1181
|
+
/** The only hardcoded URL -- everything else comes from the manifest. */
|
|
1182
|
+
private readonly configUrl;
|
|
1183
|
+
/**
|
|
1184
|
+
* Emits (once) when load() has fully completed -- tokens settled, manifest
|
|
1185
|
+
* set (or swallowed on error). authGuard subscribes to this so it never
|
|
1186
|
+
* evaluates isAuthenticated against a partially-restored stale token.
|
|
1187
|
+
*/
|
|
1188
|
+
private readonly initComplete;
|
|
1189
|
+
readonly ready$: rxjs.Observable<void>;
|
|
1190
|
+
load(): Promise<void>;
|
|
1191
|
+
/**
|
|
1192
|
+
* Confirm the restored session is still alive, settling AuthState before the
|
|
1193
|
+
* app unblocks. Runs OUTSIDE the auth interceptor (BYPASS_AUTH) because the
|
|
1194
|
+
* interceptor queues behind ready$, which has not fired yet at this point.
|
|
1195
|
+
*
|
|
1196
|
+
* - access token accepted -> done, session alive;
|
|
1197
|
+
* - access token rejected -> attempt ONE refresh (access tokens are short-lived,
|
|
1198
|
+
* so an expired one with a valid refresh token must
|
|
1199
|
+
* stay logged in). The coordinator dispatches Logout
|
|
1200
|
+
* on a refresh 401 and SetTokens on success; a
|
|
1201
|
+
* transient failure (5xx/network) is swallowed so a
|
|
1202
|
+
* momentary blip never kills a valid session.
|
|
1203
|
+
*/
|
|
1204
|
+
private validateSession;
|
|
1205
|
+
/** GET an authed endpoint with an explicit Bearer header, bypassing the interceptor. */
|
|
1206
|
+
private probe;
|
|
1207
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<AppInitService, never>;
|
|
1208
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<any>;
|
|
1209
|
+
}
|
|
1210
|
+
|
|
1211
|
+
/** What the server said about this sign-in and the console. */
|
|
1212
|
+
type ConsoleAccess = 'granted' | 'refused' | 'unknown';
|
|
1213
|
+
/**
|
|
1214
|
+
* Whether the signed-in account may use the console, and the console's endpoint
|
|
1215
|
+
* map when it may.
|
|
1216
|
+
*
|
|
1217
|
+
* The server decides: `GET /api/v1/admin/manifest` answers the map. Since 2026-10-06
|
|
1218
|
+
* (Dmitry: "no host groups"; access is decided by module groups alone) it answers every
|
|
1219
|
+
* signed-in account: the map grants nothing, each call it names is decided by its own
|
|
1220
|
+
* route, and an account in no group sees the admin with an empty menu. A server from
|
|
1221
|
+
* before that answers only at `/api/v1/console/manifest`, to the `console` group, and
|
|
1222
|
+
* 403 to anyone else -- there a refused sign-in is still signed out; this asks the new
|
|
1223
|
+
* address first and the old one only on its 404. The public theme config
|
|
1224
|
+
* read before sign-in carries only what the sign-in page needs; the rest arrives
|
|
1225
|
+
* here, once per sign-in, and is merged into the app config key by key: a key the
|
|
1226
|
+
* console's map carries replaces the public one whole, so the console's copy of a
|
|
1227
|
+
* section is always the complete one.
|
|
1228
|
+
*
|
|
1229
|
+
* `unknown` is a failure to ask (the network, a 5xx), not an answer: it is not kept,
|
|
1230
|
+
* so while the endpoint fails every navigation asks again.
|
|
1231
|
+
*
|
|
1232
|
+
* !! An answer belongs to the sign-in it was asked for. A sign-in, a sign-out or a
|
|
1233
|
+
* restored session starts a new generation; an answer that arrives for a generation
|
|
1234
|
+
* that is gone is kept for nobody -- A's late 403 must not sign B out -- and whoever
|
|
1235
|
+
* is waiting on it is answered for the sign-in there is now.
|
|
1236
|
+
*
|
|
1237
|
+
* !! With nobody signed in there is nothing to ask, and nothing is asked. Without
|
|
1238
|
+
* that, the re-ask after a sign-out went out with no token, its 401 made the
|
|
1239
|
+
* interceptor dispatch Logout, which started another generation, which re-asked:
|
|
1240
|
+
* a loop for as long as the tab lived (found in review).
|
|
1241
|
+
*/
|
|
1242
|
+
declare class ConsoleAccessService {
|
|
1243
|
+
/** The admin manifest's URL: fixed, like the theme config's, because nothing is known before it. */
|
|
1244
|
+
static readonly URL = "/api/v1/admin/manifest";
|
|
1245
|
+
/** The address before 2026-10-06, asked only when a server older than that answers 404 at {@link URL}. */
|
|
1246
|
+
static readonly LEGACY_URL = "/api/v1/console/manifest";
|
|
1247
|
+
/** The login page's query parameter value for "signed out: no console access". */
|
|
1248
|
+
static readonly REFUSED_REASON = "no-console-access";
|
|
1249
|
+
private readonly http;
|
|
1250
|
+
private readonly store;
|
|
1251
|
+
private answer;
|
|
1252
|
+
private pending;
|
|
1253
|
+
private generation;
|
|
1254
|
+
constructor();
|
|
1255
|
+
/** Ask once per sign-in; the manifest is merged into the app config when granted. */
|
|
1256
|
+
ensure(): Promise<ConsoleAccess>;
|
|
1257
|
+
/**
|
|
1258
|
+
* Sign a refused account out: this sign-in ends on the server (its refresh token
|
|
1259
|
+
* with it), as the account menu's sign-out does, and then in this browser --
|
|
1260
|
+
* whatever the server answered, so a refused request cannot leave it signed in.
|
|
1261
|
+
*/
|
|
1262
|
+
signOut(): Observable<void>;
|
|
1263
|
+
private merge;
|
|
1264
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<ConsoleAccessService, never>;
|
|
1265
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<any>;
|
|
1266
|
+
}
|
|
1267
|
+
|
|
1116
1268
|
/**
|
|
1117
1269
|
* Layout config DTO -- the shape served from
|
|
1118
1270
|
* `GET /api/v1/config/layout/{id}`. Mirrors the YAML at
|
|
@@ -1138,138 +1290,143 @@ export declare class AppInitService {
|
|
|
1138
1290
|
* keys (e.g. `media:library`'s `naviGraph:` slot shorthand) keep working
|
|
1139
1291
|
* without further interface changes.
|
|
1140
1292
|
*/
|
|
1141
|
-
|
|
1142
|
-
|
|
1143
|
-
|
|
1144
|
-
|
|
1145
|
-
|
|
1146
|
-
|
|
1147
|
-
|
|
1148
|
-
|
|
1149
|
-
|
|
1150
|
-
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1155
|
-
|
|
1156
|
-
|
|
1157
|
-
|
|
1158
|
-
|
|
1159
|
-
|
|
1160
|
-
|
|
1161
|
-
|
|
1162
|
-
|
|
1163
|
-
|
|
1164
|
-
|
|
1165
|
-
|
|
1166
|
-
|
|
1167
|
-
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
}
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1293
|
+
interface LayoutHeaderAction {
|
|
1294
|
+
id: string;
|
|
1295
|
+
label?: string;
|
|
1296
|
+
icon?: string;
|
|
1297
|
+
title?: string;
|
|
1298
|
+
primary?: boolean;
|
|
1299
|
+
danger?: boolean;
|
|
1300
|
+
disabled?: boolean;
|
|
1301
|
+
/**
|
|
1302
|
+
* Optional capability gate evaluated FE-side against runtime state
|
|
1303
|
+
* (e.g. `administer` / `delete`) -- the config declares WHICH actions
|
|
1304
|
+
* exist + their gate; the FE decides if THIS user/row qualifies. Absent
|
|
1305
|
+
* = always shown. Used by detail pages whose actions are config-driven
|
|
1306
|
+
* (see `web:section-detail`).
|
|
1307
|
+
*
|
|
1308
|
+
* For a gate that depends on the RECORD rather than the user, prefer
|
|
1309
|
+
* `showWhen` below: `requires` needs a page to interpret the token, so
|
|
1310
|
+
* every new value costs a branch in TypeScript.
|
|
1311
|
+
*/
|
|
1312
|
+
requires?: string;
|
|
1313
|
+
/**
|
|
1314
|
+
* Conditions on the page's published context, same grammar and same
|
|
1315
|
+
* evaluator as a NaviGraph toolbar node, applied
|
|
1316
|
+
* by {@link LayoutActionsService}:
|
|
1317
|
+
*
|
|
1318
|
+
* showWhen -- the action does not APPLY, so it is absent.
|
|
1319
|
+
* disabledWhen -- temporarily UNAVAILABLE: stays put and greys out.
|
|
1320
|
+
* busyWhen -- the action is RUNNING: greys out too, and may relabel.
|
|
1321
|
+
* busyLabel -- label shown while `busyWhen` holds ("Saving...").
|
|
1322
|
+
* activeWhen -- pressed/active styling.
|
|
1323
|
+
*
|
|
1324
|
+
* Two carriers (a layout config and a NaviGraph tree) but ONE vocabulary:
|
|
1325
|
+
* a page that moves between them should not have to relearn how to say
|
|
1326
|
+
* "not now".
|
|
1327
|
+
*/
|
|
1328
|
+
showWhen?: Record<string, unknown>;
|
|
1329
|
+
disabledWhen?: Record<string, unknown>;
|
|
1330
|
+
busyWhen?: Record<string, unknown>;
|
|
1331
|
+
busyLabel?: string;
|
|
1332
|
+
activeWhen?: Record<string, unknown>;
|
|
1333
|
+
}
|
|
1334
|
+
interface LayoutSection {
|
|
1335
|
+
title?: string;
|
|
1336
|
+
/**
|
|
1337
|
+
* When true, the layout shell pins the section to the top of the
|
|
1338
|
+
* scroll container (CSS `position: sticky`). Used by inspector /
|
|
1339
|
+
* filter pages where an input form should stay visible while the
|
|
1340
|
+
* operator scrolls through results below. Currently honoured by
|
|
1341
|
+
* cms-inspector-layout; cms-detail-layout will follow when it
|
|
1342
|
+
* lands. Default false.
|
|
1343
|
+
*/
|
|
1344
|
+
sticky?: boolean;
|
|
1345
|
+
slots?: Record<string, {
|
|
1346
|
+
component?: string;
|
|
1347
|
+
[key: string]: unknown;
|
|
1348
|
+
}>;
|
|
1349
|
+
[key: string]: unknown;
|
|
1350
|
+
}
|
|
1351
|
+
interface LayoutConfig {
|
|
1352
|
+
id?: string;
|
|
1353
|
+
template?: string;
|
|
1354
|
+
title?: string;
|
|
1355
|
+
subtitle?: string;
|
|
1356
|
+
icon?: string;
|
|
1357
|
+
slots?: Record<string, unknown>;
|
|
1358
|
+
sections?: Record<string, LayoutSection>;
|
|
1359
|
+
headerActions?: LayoutHeaderAction[];
|
|
1360
|
+
/**
|
|
1361
|
+
* Actions rendered in the page's fixed bottom bar (`<cms-detail-footer>`)
|
|
1362
|
+
* -- primary / destructive actions kept out of the cramped header.
|
|
1363
|
+
* Same shape as `headerActions`; gated per-item via `requires`.
|
|
1364
|
+
*/
|
|
1365
|
+
footerActions?: LayoutHeaderAction[];
|
|
1366
|
+
permissions?: string[];
|
|
1367
|
+
[key: string]: unknown;
|
|
1368
|
+
}
|
|
1369
|
+
interface DialogConfig {
|
|
1370
|
+
[key: string]: unknown;
|
|
1371
|
+
}
|
|
1372
|
+
interface NavigraphConfig {
|
|
1373
|
+
[key: string]: unknown;
|
|
1374
|
+
}
|
|
1375
|
+
interface FormConfig {
|
|
1376
|
+
[key: string]: unknown;
|
|
1377
|
+
}
|
|
1378
|
+
interface DatagridConfig {
|
|
1195
1379
|
[key: string]: unknown;
|
|
1196
|
-
}>;
|
|
1197
|
-
[key: string]: unknown;
|
|
1198
|
-
}
|
|
1199
|
-
export interface LayoutConfig {
|
|
1200
|
-
id?: string;
|
|
1201
|
-
template?: string;
|
|
1202
|
-
title?: string;
|
|
1203
|
-
subtitle?: string;
|
|
1204
|
-
icon?: string;
|
|
1205
|
-
slots?: Record<string, unknown>;
|
|
1206
|
-
sections?: Record<string, LayoutSection>;
|
|
1207
|
-
headerActions?: LayoutHeaderAction[];
|
|
1208
|
-
/**
|
|
1209
|
-
* Actions rendered in the page's fixed bottom bar (`<cms-detail-footer>`)
|
|
1210
|
-
* -- primary / destructive actions kept out of the cramped header.
|
|
1211
|
-
* Same shape as `headerActions`; gated per-item via `requires`.
|
|
1212
|
-
*/
|
|
1213
|
-
footerActions?: LayoutHeaderAction[];
|
|
1214
|
-
permissions?: string[];
|
|
1215
|
-
[key: string]: unknown;
|
|
1216
|
-
}
|
|
1217
|
-
export interface DialogConfig {
|
|
1218
|
-
[key: string]: unknown;
|
|
1219
|
-
}
|
|
1220
|
-
export interface NavigraphConfig {
|
|
1221
|
-
[key: string]: unknown;
|
|
1222
|
-
}
|
|
1223
|
-
export interface FormConfig {
|
|
1224
|
-
[key: string]: unknown;
|
|
1225
|
-
}
|
|
1226
|
-
export interface DatagridConfig {
|
|
1227
|
-
[key: string]: unknown;
|
|
1228
1380
|
}
|
|
1229
1381
|
/**
|
|
1230
1382
|
* Fetches config YAMLs (served as JSON) from GET /api/v1/config/{type}/{id}.
|
|
1231
1383
|
*
|
|
1232
1384
|
* Responses are cached for the lifetime of the service (Map + shareReplay(1))
|
|
1233
|
-
* so repeated calls never re-fetch the same resource.
|
|
1385
|
+
* so repeated calls never re-fetch the same resource. A failed read is dropped
|
|
1386
|
+
* from the cache, so the next call asks the server again.
|
|
1234
1387
|
*
|
|
1235
1388
|
* configBase is read from the API manifest (manifest.configBase) and falls
|
|
1236
1389
|
* back to '/api/v1/config' when the manifest is not yet loaded.
|
|
1237
1390
|
*/
|
|
1238
|
-
|
|
1239
|
-
|
|
1240
|
-
|
|
1241
|
-
|
|
1242
|
-
|
|
1243
|
-
|
|
1244
|
-
|
|
1245
|
-
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
}
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1267
|
-
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1391
|
+
declare class ConfigService {
|
|
1392
|
+
private readonly http;
|
|
1393
|
+
private readonly store;
|
|
1394
|
+
private readonly cache;
|
|
1395
|
+
layout(id: string): Observable<LayoutConfig>;
|
|
1396
|
+
dialog(id: string): Observable<DialogConfig>;
|
|
1397
|
+
navigraph(slug: string): Observable<NavigraphConfig>;
|
|
1398
|
+
form(id: string): Observable<FormConfig>;
|
|
1399
|
+
datagrid(id: string): Observable<DatagridConfig>;
|
|
1400
|
+
private fetch;
|
|
1401
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<ConfigService, never>;
|
|
1402
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<any>;
|
|
1403
|
+
}
|
|
1404
|
+
|
|
1405
|
+
declare class SetAppConfig {
|
|
1406
|
+
readonly config: ThemeConfigResponse;
|
|
1407
|
+
static readonly type = "[AppConfig] Set";
|
|
1408
|
+
constructor(config: ThemeConfigResponse);
|
|
1409
|
+
}
|
|
1410
|
+
interface AppConfigStateModel {
|
|
1411
|
+
config: ThemeConfigResponse | null;
|
|
1412
|
+
loaded: boolean;
|
|
1413
|
+
}
|
|
1414
|
+
declare class AppConfigState {
|
|
1415
|
+
set(ctx: StateContext<AppConfigStateModel>, { config }: SetAppConfig): void;
|
|
1416
|
+
/** The whole config as last set: the public theme config, with the console's manifest once granted. */
|
|
1417
|
+
static config(state: AppConfigStateModel): ThemeConfigResponse | null;
|
|
1418
|
+
static manifest(state: AppConfigStateModel): ApiManifest | null;
|
|
1419
|
+
/**
|
|
1420
|
+
* The viewer manifest. Null when the backend didn't ship a viewers
|
|
1421
|
+
* section (no tagged providers); consumers should fall back to a
|
|
1422
|
+
* "no preview" surface.
|
|
1423
|
+
*/
|
|
1424
|
+
static viewers(state: AppConfigStateModel): ViewerApiManifest | null;
|
|
1425
|
+
static loaded(state: AppConfigStateModel): boolean;
|
|
1426
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<AppConfigState, never>;
|
|
1427
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<any>;
|
|
1428
|
+
}
|
|
1429
|
+
|
|
1273
1430
|
/**
|
|
1274
1431
|
* Converts any caught error into a human-readable message.
|
|
1275
1432
|
*
|
|
@@ -1280,16 +1437,17 @@ export declare class AppConfigState {
|
|
|
1280
1437
|
* 4. error.violations[] (constraint violations)
|
|
1281
1438
|
* 5. Status-based fallback
|
|
1282
1439
|
*/
|
|
1283
|
-
|
|
1284
|
-
|
|
1285
|
-
|
|
1286
|
-
|
|
1287
|
-
|
|
1440
|
+
declare class ErrorHandlerService {
|
|
1441
|
+
humanize(err: unknown): string;
|
|
1442
|
+
private statusMessage;
|
|
1443
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<ErrorHandlerService, never>;
|
|
1444
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<any>;
|
|
1288
1445
|
}
|
|
1446
|
+
|
|
1289
1447
|
/** What the user PICKED. `system` defers to the OS. */
|
|
1290
|
-
|
|
1448
|
+
type ThemeChoice = 'light' | 'dark' | 'system';
|
|
1291
1449
|
/** What that resolves to right now -- the only two the stylesheet knows. */
|
|
1292
|
-
|
|
1450
|
+
type ResolvedTheme = 'light' | 'dark';
|
|
1293
1451
|
/**
|
|
1294
1452
|
* Applies the user's theme by setting `data-theme` on the document element.
|
|
1295
1453
|
*
|
|
@@ -1303,77 +1461,78 @@ export type ResolvedTheme = 'light' | 'dark';
|
|
|
1303
1461
|
* cached for the SPA lifetime, with `update()` called by the Profile page after
|
|
1304
1462
|
* a save so the change lands without a refetch.
|
|
1305
1463
|
*/
|
|
1306
|
-
|
|
1307
|
-
|
|
1308
|
-
|
|
1309
|
-
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1313
|
-
|
|
1314
|
-
|
|
1315
|
-
|
|
1316
|
-
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
|
|
1326
|
-
|
|
1327
|
-
|
|
1328
|
-
|
|
1329
|
-
|
|
1330
|
-
|
|
1331
|
-
|
|
1332
|
-
|
|
1333
|
-
|
|
1334
|
-
|
|
1335
|
-
|
|
1336
|
-
|
|
1337
|
-
|
|
1338
|
-
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1342
|
-
|
|
1343
|
-
|
|
1344
|
-
|
|
1345
|
-
|
|
1346
|
-
|
|
1347
|
-
|
|
1348
|
-
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
}
|
|
1464
|
+
declare class ThemeService {
|
|
1465
|
+
private readonly api;
|
|
1466
|
+
private readonly store;
|
|
1467
|
+
private readonly _choice;
|
|
1468
|
+
private readonly _systemDark;
|
|
1469
|
+
private readonly _userAccent;
|
|
1470
|
+
private readonly _platformAccent;
|
|
1471
|
+
private loadOnce$?;
|
|
1472
|
+
/** The user's stored choice, including `system`. */
|
|
1473
|
+
readonly choice: i0.Signal<ThemeChoice>;
|
|
1474
|
+
/** This user's personal override, independent of the deployment's. */
|
|
1475
|
+
readonly userAccent: i0.Signal<string | null>;
|
|
1476
|
+
/** The deployment's brand accent from the API manifest, if it set one. */
|
|
1477
|
+
readonly platformAccent: i0.Signal<string | null>;
|
|
1478
|
+
/**
|
|
1479
|
+
* The colour actually painted: personal override, else the deployment's,
|
|
1480
|
+
* else null -- which leaves the stylesheet's own accent in place.
|
|
1481
|
+
*
|
|
1482
|
+
* Three rungs and each is a real state. A user who has chosen nothing is
|
|
1483
|
+
* NOT the same as one who chose the deployment's colour: clear the
|
|
1484
|
+
* deployment's brand and the first user follows it, the second does not.
|
|
1485
|
+
*/
|
|
1486
|
+
readonly accent: i0.Signal<string | null>;
|
|
1487
|
+
/** The choice with `system` resolved against the OS setting. */
|
|
1488
|
+
readonly resolved: i0.Signal<ResolvedTheme>;
|
|
1489
|
+
constructor();
|
|
1490
|
+
/**
|
|
1491
|
+
* One-shot load of the stored preference. Safe to call more than once and
|
|
1492
|
+
* safe for anonymous users -- a failure leaves the cached/system value in
|
|
1493
|
+
* place rather than forcing light, so the login screen still matches the OS.
|
|
1494
|
+
*/
|
|
1495
|
+
ensureLoaded(): Observable<ThemeChoice>;
|
|
1496
|
+
/**
|
|
1497
|
+
* Called by the Profile page after the Preferences tab saves, so the admin
|
|
1498
|
+
* re-themes on the spot. Ignores anything that is not a known choice --
|
|
1499
|
+
* the section PATCH returns the whole merged bag, not just this field.
|
|
1500
|
+
*/
|
|
1501
|
+
update(value: unknown): void;
|
|
1502
|
+
/**
|
|
1503
|
+
* Companion to {@link update} for the accent field of the same save.
|
|
1504
|
+
* An explicit null clears the override; anything malformed is ignored
|
|
1505
|
+
* rather than applied, so a bad value can never blank the admin's accent.
|
|
1506
|
+
*/
|
|
1507
|
+
updateAccent(value: unknown): void;
|
|
1508
|
+
private set;
|
|
1509
|
+
/**
|
|
1510
|
+
* The manifest is loaded at bootstrap, well before the authenticated shell
|
|
1511
|
+
* calls this, so a snapshot read is enough and no subscription is needed.
|
|
1512
|
+
* A malformed value is ignored rather than applied -- the container refuses
|
|
1513
|
+
* to build with one, but the manifest crosses the network all the same.
|
|
1514
|
+
*/
|
|
1515
|
+
private readPlatformAccent;
|
|
1516
|
+
private setAccent;
|
|
1517
|
+
/**
|
|
1518
|
+
* Writes the override onto the document element, where an inline style
|
|
1519
|
+
* outranks every stylesheet rule -- including the dark block -- so one
|
|
1520
|
+
* declaration re-points the accent in both themes.
|
|
1521
|
+
*
|
|
1522
|
+
* Only --cms-accent is set, not the -hover/-light/-text members of the
|
|
1523
|
+
* family. Deriving those from an arbitrary user colour needs a colour model
|
|
1524
|
+
* the palette does not have yet, and guessing them would produce hover
|
|
1525
|
+
* states that clash with the very colour the user chose. Until then the
|
|
1526
|
+
* override moves the brand fill and leaves its supporting tones alone.
|
|
1527
|
+
*/
|
|
1528
|
+
private applyAccent;
|
|
1529
|
+
private applyToDocument;
|
|
1530
|
+
private static readCache;
|
|
1531
|
+
private static readAccentCache;
|
|
1532
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<ThemeService, never>;
|
|
1533
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<any>;
|
|
1534
|
+
}
|
|
1535
|
+
|
|
1377
1536
|
/**
|
|
1378
1537
|
* The platform's loading indicator: the exclamation mark from Cool!MS,
|
|
1379
1538
|
* drawing itself.
|
|
@@ -1400,41 +1559,26 @@ export declare class ThemeService {
|
|
|
1400
1559
|
* Under `prefers-reduced-motion` it holds still and only breathes, because
|
|
1401
1560
|
* motion sickness is not a stylistic preference.
|
|
1402
1561
|
*/
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
|
|
1407
|
-
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
|
|
1411
|
-
|
|
1412
|
-
|
|
1413
|
-
|
|
1414
|
-
|
|
1415
|
-
|
|
1416
|
-
|
|
1417
|
-
|
|
1418
|
-
|
|
1419
|
-
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
"required": false;
|
|
1424
|
-
"isSignal": true;
|
|
1425
|
-
};
|
|
1426
|
-
"overlay": {
|
|
1427
|
-
"alias": "overlay";
|
|
1428
|
-
"required": false;
|
|
1429
|
-
"isSignal": true;
|
|
1430
|
-
};
|
|
1431
|
-
"inline": {
|
|
1432
|
-
"alias": "inline";
|
|
1433
|
-
"required": false;
|
|
1434
|
-
"isSignal": true;
|
|
1435
|
-
};
|
|
1436
|
-
}, {}, never, never, true, never>;
|
|
1437
|
-
}
|
|
1562
|
+
declare class CmsLoaderComponent {
|
|
1563
|
+
/** Optional caption. Absent renders the mark alone, which suits a small inline wait. */
|
|
1564
|
+
readonly label: i0.InputSignal<string>;
|
|
1565
|
+
/** Cover the positioned ancestor rather than sitting in the flow. */
|
|
1566
|
+
readonly overlay: i0.InputSignal<boolean>;
|
|
1567
|
+
/**
|
|
1568
|
+
* Row layout at text scale, for a wait inside a row, a status bar or a
|
|
1569
|
+
* button. Override `--cms-loader-size` to tune the mark.
|
|
1570
|
+
*/
|
|
1571
|
+
readonly inline: i0.InputSignal<boolean>;
|
|
1572
|
+
/**
|
|
1573
|
+
* A clip-path needs a document-unique id, and this component is used more
|
|
1574
|
+
* than once per page -- two loaders sharing an id makes the second one clip
|
|
1575
|
+
* against the first one's rectangle and stop animating.
|
|
1576
|
+
*/
|
|
1577
|
+
protected readonly clipId: string;
|
|
1578
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<CmsLoaderComponent, never>;
|
|
1579
|
+
static ɵcmp: i0.ɵɵComponentDeclaration<CmsLoaderComponent, "cms-loader", never, { "label": { "alias": "label"; "required": false; "isSignal": true; }; "overlay": { "alias": "overlay"; "required": false; "isSignal": true; }; "inline": { "alias": "inline"; "required": false; "isSignal": true; }; }, {}, never, never, true, never>;
|
|
1580
|
+
}
|
|
1581
|
+
|
|
1438
1582
|
/**
|
|
1439
1583
|
* Unified user preferences service -- all preference types share one
|
|
1440
1584
|
* localStorage key (`coolms_ui_prefs`) and one debounced server-sync pipeline.
|
|
@@ -1460,105 +1604,106 @@ export declare class CmsLoaderComponent {
|
|
|
1460
1604
|
* terminal.height
|
|
1461
1605
|
* terminal.open
|
|
1462
1606
|
*/
|
|
1463
|
-
|
|
1464
|
-
|
|
1465
|
-
|
|
1466
|
-
|
|
1467
|
-
|
|
1468
|
-
|
|
1469
|
-
|
|
1470
|
-
|
|
1471
|
-
|
|
1472
|
-
|
|
1473
|
-
|
|
1474
|
-
|
|
1475
|
-
|
|
1476
|
-
|
|
1477
|
-
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1482
|
-
|
|
1483
|
-
|
|
1484
|
-
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1488
|
-
|
|
1489
|
-
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
|
|
1502
|
-
|
|
1503
|
-
|
|
1504
|
-
|
|
1505
|
-
|
|
1506
|
-
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1516
|
-
|
|
1517
|
-
|
|
1518
|
-
|
|
1519
|
-
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1548
|
-
|
|
1549
|
-
|
|
1550
|
-
|
|
1551
|
-
|
|
1552
|
-
|
|
1553
|
-
|
|
1554
|
-
|
|
1555
|
-
|
|
1556
|
-
|
|
1557
|
-
|
|
1558
|
-
|
|
1559
|
-
|
|
1560
|
-
|
|
1561
|
-
}
|
|
1607
|
+
declare class UserPreferencesService {
|
|
1608
|
+
private readonly http;
|
|
1609
|
+
private readonly store;
|
|
1610
|
+
private readonly destroyRef;
|
|
1611
|
+
private readonly STORAGE_KEY;
|
|
1612
|
+
private readonly SYNC_DELAY;
|
|
1613
|
+
private syncSubject;
|
|
1614
|
+
constructor();
|
|
1615
|
+
/**
|
|
1616
|
+
* Write all prefs to localStorage and schedule a debounced server sync.
|
|
1617
|
+
*/
|
|
1618
|
+
private save;
|
|
1619
|
+
/**
|
|
1620
|
+
* Read the entire prefs object from localStorage, applying legacy key
|
|
1621
|
+
* migration on the first read after an upgrade.
|
|
1622
|
+
*/
|
|
1623
|
+
private load;
|
|
1624
|
+
/**
|
|
1625
|
+
* One-time migration of legacy flat keys to the new nested naming convention.
|
|
1626
|
+
* Mutates `all` in place and re-persists only when something changed.
|
|
1627
|
+
*/
|
|
1628
|
+
private migrate;
|
|
1629
|
+
/**
|
|
1630
|
+
* Seed localStorage from server preferences.
|
|
1631
|
+
* Called after login and on every session restore (page reload).
|
|
1632
|
+
*
|
|
1633
|
+
* Strategy:
|
|
1634
|
+
* - No local prefs yet (new device / clean install) -> use server prefs as-is.
|
|
1635
|
+
* - Local prefs already exist -> merge with local taking priority at each
|
|
1636
|
+
* top-level namespace. This ensures that locally-persisted state
|
|
1637
|
+
* (panel widths, last path, etc.) is never overwritten by the stale copy
|
|
1638
|
+
* that was saved in the auth token at login time.
|
|
1639
|
+
*/
|
|
1640
|
+
loadFromServer(uiPrefs: Record<string, unknown>): void;
|
|
1641
|
+
private syncToServer;
|
|
1642
|
+
/**
|
|
1643
|
+
* Get stored preferences for a specific grid.
|
|
1644
|
+
*/
|
|
1645
|
+
getGridPref(gridId: string): DataGridPreference | null;
|
|
1646
|
+
/**
|
|
1647
|
+
* Set visible column order for a grid.
|
|
1648
|
+
* Applies to localStorage instantly; server sync is debounced.
|
|
1649
|
+
*/
|
|
1650
|
+
setColumns(gridId: string, columns: string[]): void;
|
|
1651
|
+
/**
|
|
1652
|
+
* Record a column width for a grid column.
|
|
1653
|
+
* Applies to localStorage instantly; server sync is debounced.
|
|
1654
|
+
*/
|
|
1655
|
+
setColumnWidth(gridId: string, field: string, width: number): void;
|
|
1656
|
+
/**
|
|
1657
|
+
* Remove all preferences for a specific grid and immediately sync to server.
|
|
1658
|
+
*/
|
|
1659
|
+
resetGrid(gridId: string): void;
|
|
1660
|
+
private updateGrid;
|
|
1661
|
+
/**
|
|
1662
|
+
* Get persisted terminal panel height.
|
|
1663
|
+
*/
|
|
1664
|
+
getTerminalHeight(): number | null;
|
|
1665
|
+
/**
|
|
1666
|
+
* Persist terminal panel height.
|
|
1667
|
+
*/
|
|
1668
|
+
setTerminalHeight(height: number): void;
|
|
1669
|
+
/**
|
|
1670
|
+
* Get persisted state for a resizable panel identified by key.
|
|
1671
|
+
* Used by ExplorerLayoutComponent to persist panel widths across sessions.
|
|
1672
|
+
*/
|
|
1673
|
+
getPanelState(key: string): {
|
|
1674
|
+
width: number;
|
|
1675
|
+
collapsed: boolean;
|
|
1676
|
+
} | null;
|
|
1677
|
+
/**
|
|
1678
|
+
* Persist generic panel state by key.
|
|
1679
|
+
* Applies to localStorage instantly; server sync is debounced.
|
|
1680
|
+
*/
|
|
1681
|
+
setPanelState(key: string, width: number, collapsed: boolean): void;
|
|
1682
|
+
/**
|
|
1683
|
+
* Get all persisted state for a page namespace (e.g. 'vfs', 'media', 'nav').
|
|
1684
|
+
*/
|
|
1685
|
+
getPageState<T>(pageKey: string): T | null;
|
|
1686
|
+
/**
|
|
1687
|
+
* Merge partial state into a page namespace.
|
|
1688
|
+
* Existing keys in the namespace are preserved; only provided keys are updated.
|
|
1689
|
+
* Applies to localStorage instantly; server sync is debounced.
|
|
1690
|
+
*/
|
|
1691
|
+
setPageState(pageKey: string, state: Record<string, unknown>): void;
|
|
1692
|
+
/** @deprecated Use getPageState('media') instead */
|
|
1693
|
+
getMediaViewMode(): string | null;
|
|
1694
|
+
/** @deprecated Use setPageState('media', ...) instead */
|
|
1695
|
+
setMediaViewMode(mode: string): void;
|
|
1696
|
+
/** @deprecated Use getPanelState('media_panel_left') instead */
|
|
1697
|
+
getMediaCollectionsPanel(): {
|
|
1698
|
+
width: number;
|
|
1699
|
+
collapsed: boolean;
|
|
1700
|
+
} | null;
|
|
1701
|
+
/** @deprecated Use setPanelState('media_panel_left', ...) instead */
|
|
1702
|
+
setMediaCollectionsPanel(width: number, collapsed: boolean): void;
|
|
1703
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<UserPreferencesService, never>;
|
|
1704
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<any>;
|
|
1705
|
+
}
|
|
1706
|
+
|
|
1562
1707
|
/**
|
|
1563
1708
|
* Sidebar collapse state -- two independent dimensions:
|
|
1564
1709
|
*
|
|
@@ -1578,34 +1723,35 @@ export declare class UserPreferencesService {
|
|
|
1578
1723
|
* to defaulting un-collapsed -- the toggles still work for the session,
|
|
1579
1724
|
* just don't persist.
|
|
1580
1725
|
*/
|
|
1581
|
-
|
|
1582
|
-
|
|
1583
|
-
|
|
1584
|
-
|
|
1585
|
-
|
|
1586
|
-
|
|
1587
|
-
|
|
1588
|
-
|
|
1589
|
-
|
|
1590
|
-
|
|
1591
|
-
|
|
1592
|
-
|
|
1593
|
-
|
|
1594
|
-
|
|
1595
|
-
|
|
1596
|
-
|
|
1597
|
-
|
|
1598
|
-
|
|
1599
|
-
|
|
1600
|
-
|
|
1601
|
-
|
|
1602
|
-
|
|
1603
|
-
|
|
1604
|
-
|
|
1605
|
-
|
|
1606
|
-
|
|
1607
|
-
|
|
1608
|
-
}
|
|
1726
|
+
declare class SidebarStateService {
|
|
1727
|
+
private static readonly STORAGE_KEY;
|
|
1728
|
+
private static readonly STORAGE_KEY_SECTIONS;
|
|
1729
|
+
private static readonly STORAGE_KEY_ITEMS;
|
|
1730
|
+
readonly collapsed: i0.WritableSignal<boolean>;
|
|
1731
|
+
readonly collapsedSections: i0.WritableSignal<Set<string>>;
|
|
1732
|
+
readonly collapsedItems: i0.WritableSignal<Set<string>>;
|
|
1733
|
+
toggle(): void;
|
|
1734
|
+
isSectionCollapsed(id: string): boolean;
|
|
1735
|
+
toggleSection(id: string): void;
|
|
1736
|
+
/**
|
|
1737
|
+
* Force a section to be expanded. Used when navigation lands on
|
|
1738
|
+
* a route inside a manually-collapsed section -- it would be
|
|
1739
|
+
* confusing to leave the active item hidden. The user can
|
|
1740
|
+
* collapse the section again afterwards.
|
|
1741
|
+
*/
|
|
1742
|
+
ensureExpanded(id: string): void;
|
|
1743
|
+
isItemCollapsed(id: string): boolean;
|
|
1744
|
+
toggleItem(id: string): void;
|
|
1745
|
+
private readPersisted;
|
|
1746
|
+
private persist;
|
|
1747
|
+
private readSectionsPersisted;
|
|
1748
|
+
private persistSections;
|
|
1749
|
+
private readItemsPersisted;
|
|
1750
|
+
private persistItems;
|
|
1751
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<SidebarStateService, never>;
|
|
1752
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<any>;
|
|
1753
|
+
}
|
|
1754
|
+
|
|
1609
1755
|
/**
|
|
1610
1756
|
* One row returned by the `/api/entity-search` endpoint. `id` is
|
|
1611
1757
|
* what the picker emits via its `valueChange` output (and what the
|
|
@@ -1615,10 +1761,10 @@ export declare class SidebarStateService {
|
|
|
1615
1761
|
* when there is nothing to show; receiving `null` from a custom
|
|
1616
1762
|
* resolver is normalized to `undefined` here.
|
|
1617
1763
|
*/
|
|
1618
|
-
|
|
1619
|
-
|
|
1620
|
-
|
|
1621
|
-
|
|
1764
|
+
interface EntitySearchResult {
|
|
1765
|
+
readonly id: string | number;
|
|
1766
|
+
readonly label: string;
|
|
1767
|
+
readonly secondary?: string;
|
|
1622
1768
|
}
|
|
1623
1769
|
/**
|
|
1624
1770
|
* HTTP client for the Phase 2a `/api/entity-search` endpoint.
|
|
@@ -1626,53 +1772,55 @@ export interface EntitySearchResult {
|
|
|
1626
1772
|
* Permission filtering happens at the backend resolver layer --
|
|
1627
1773
|
* this service is a thin RPC.
|
|
1628
1774
|
*/
|
|
1629
|
-
|
|
1630
|
-
|
|
1631
|
-
|
|
1632
|
-
|
|
1633
|
-
|
|
1634
|
-
|
|
1635
|
-
|
|
1636
|
-
|
|
1637
|
-
|
|
1638
|
-
|
|
1639
|
-
|
|
1640
|
-
|
|
1641
|
-
|
|
1642
|
-
|
|
1643
|
-
}
|
|
1775
|
+
declare class EntitySearchService {
|
|
1776
|
+
private readonly http;
|
|
1777
|
+
/**
|
|
1778
|
+
* @param entityType Fully-qualified PHP class name of the entity
|
|
1779
|
+
* being searched (e.g. the backend's User entity).
|
|
1780
|
+
* @param query Free-text search input. Empty string is
|
|
1781
|
+
* valid -- the backend returns the default
|
|
1782
|
+
* listing for that entity type.
|
|
1783
|
+
* @param limit Max rows to return; clamped server-side to
|
|
1784
|
+
* [1, 100].
|
|
1785
|
+
*/
|
|
1786
|
+
search(entityType: string, query: string, limit?: number): Observable<EntitySearchResult[]>;
|
|
1787
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<EntitySearchService, never>;
|
|
1788
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<any>;
|
|
1789
|
+
}
|
|
1790
|
+
|
|
1644
1791
|
/**
|
|
1645
1792
|
* Well-known click target values embedded in meta.target.
|
|
1646
1793
|
* 'action.logout' -- dispatch Logout action
|
|
1647
1794
|
* 'route' -- navigate via routerLink (default when target absent)
|
|
1648
1795
|
* '_blank' -- open in a new tab (hrefOverride must be set)
|
|
1649
1796
|
*/
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1654
|
-
|
|
1655
|
-
|
|
1656
|
-
|
|
1657
|
-
|
|
1658
|
-
|
|
1659
|
-
|
|
1797
|
+
type NaviGraphTarget = 'action.logout' | 'route' | '_blank' | (string & {});
|
|
1798
|
+
interface NaviGraphMeta {
|
|
1799
|
+
readonly component?: string;
|
|
1800
|
+
readonly icon?: string;
|
|
1801
|
+
readonly routerLink?: string;
|
|
1802
|
+
readonly target?: NaviGraphTarget;
|
|
1803
|
+
readonly requiredRole?: string;
|
|
1804
|
+
readonly voterAttribute?: string;
|
|
1805
|
+
readonly contributor?: string;
|
|
1806
|
+
readonly [key: string]: unknown;
|
|
1660
1807
|
}
|
|
1661
1808
|
/** Flat node as returned from GET /navi/trees/{slug}/graph */
|
|
1662
|
-
|
|
1663
|
-
|
|
1664
|
-
|
|
1665
|
-
|
|
1666
|
-
|
|
1667
|
-
|
|
1668
|
-
|
|
1669
|
-
|
|
1670
|
-
|
|
1809
|
+
interface NaviGraphNodeFlat {
|
|
1810
|
+
readonly id: string;
|
|
1811
|
+
readonly path: string;
|
|
1812
|
+
readonly title: string;
|
|
1813
|
+
readonly parentId: string | null;
|
|
1814
|
+
readonly sortOrder: number;
|
|
1815
|
+
readonly isActive: boolean;
|
|
1816
|
+
readonly isVisible: boolean;
|
|
1817
|
+
readonly meta: NaviGraphMeta;
|
|
1671
1818
|
}
|
|
1672
1819
|
/** Same node enriched with resolved children after client-side tree build */
|
|
1673
|
-
|
|
1674
|
-
|
|
1820
|
+
interface NaviGraphNode extends NaviGraphNodeFlat {
|
|
1821
|
+
readonly children: NaviGraphNode[];
|
|
1675
1822
|
}
|
|
1823
|
+
|
|
1676
1824
|
/**
|
|
1677
1825
|
* Loads, caches, and drives admin NaviGraph trees.
|
|
1678
1826
|
*
|
|
@@ -1681,58 +1829,59 @@ export interface NaviGraphNode extends NaviGraphNodeFlat {
|
|
|
1681
1829
|
* const items = naviGraphService.adminNav(); // signal<NaviGraphNode[]>
|
|
1682
1830
|
* naviGraphService.handleClick(node);
|
|
1683
1831
|
*/
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1699
|
-
|
|
1700
|
-
|
|
1701
|
-
|
|
1702
|
-
|
|
1703
|
-
|
|
1704
|
-
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1711
|
-
|
|
1712
|
-
|
|
1713
|
-
|
|
1714
|
-
|
|
1715
|
-
|
|
1716
|
-
|
|
1717
|
-
|
|
1718
|
-
|
|
1719
|
-
|
|
1720
|
-
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1725
|
-
|
|
1726
|
-
|
|
1727
|
-
|
|
1728
|
-
|
|
1729
|
-
|
|
1730
|
-
|
|
1731
|
-
|
|
1732
|
-
|
|
1733
|
-
|
|
1734
|
-
|
|
1735
|
-
}
|
|
1832
|
+
declare class NaviGraphService {
|
|
1833
|
+
private readonly http;
|
|
1834
|
+
private readonly router;
|
|
1835
|
+
private readonly store;
|
|
1836
|
+
private readonly errors;
|
|
1837
|
+
/** Resolved root nodes for navi.admin (sidebar). */
|
|
1838
|
+
readonly adminNav: i0.WritableSignal<NaviGraphNode[]>;
|
|
1839
|
+
/** Resolved root nodes for navi.admin.topbar. */
|
|
1840
|
+
readonly topbarNav: i0.WritableSignal<NaviGraphNode[]>;
|
|
1841
|
+
private readonly cache;
|
|
1842
|
+
loadAdminNav(url: string): Observable<NaviGraphNode[]>;
|
|
1843
|
+
loadTopbarNav(url: string): Observable<NaviGraphNode[]>;
|
|
1844
|
+
/**
|
|
1845
|
+
* Handle a nav-node click.
|
|
1846
|
+
*
|
|
1847
|
+
* Routing logic based on meta.target:
|
|
1848
|
+
* 'action.logout' -- dispatch Logout
|
|
1849
|
+
* '_blank' -- window.open (uses node.path as href)
|
|
1850
|
+
* default -- router.navigate with meta.routerLink or node.path
|
|
1851
|
+
*/
|
|
1852
|
+
handleClick(node: NaviGraphNode): void;
|
|
1853
|
+
/**
|
|
1854
|
+
* Evaluate `showWhen` meta condition against a record.
|
|
1855
|
+
* Nodes with no `showWhen` are always visible.
|
|
1856
|
+
* Supports nested AND/OR logic: { and: [...] } / { or: [...] }
|
|
1857
|
+
*/
|
|
1858
|
+
isVisible(node: NaviGraphNode, record: Record<string, unknown>): boolean;
|
|
1859
|
+
/**
|
|
1860
|
+
* Standalone `showWhen` evaluator for callers that don't have a
|
|
1861
|
+
* full `NaviGraphNode` (e.g. a grid's row context menu -- a
|
|
1862
|
+
* `rowAction.showWhen` predicate is evaluated against the row's
|
|
1863
|
+
* own projection). `null`/`undefined` predicates default to
|
|
1864
|
+
* visible, mirroring `isVisible`'s "no rule = always shown"
|
|
1865
|
+
* semantics.
|
|
1866
|
+
*/
|
|
1867
|
+
matchesShowWhen(showWhen: Record<string, unknown> | undefined | null, record: Record<string, unknown>): boolean;
|
|
1868
|
+
private evalCondition;
|
|
1869
|
+
/**
|
|
1870
|
+
* Fetch a graph endpoint and return a nested tree.
|
|
1871
|
+
* Handles both compact JSON-LD IRIs (`member`) and
|
|
1872
|
+
* the legacy Hydra key (`hydra:member`).
|
|
1873
|
+
* Results are cached by URL for the lifetime of the service instance.
|
|
1874
|
+
*/
|
|
1875
|
+
loadTree(url: string): Observable<NaviGraphNode[]>;
|
|
1876
|
+
/**
|
|
1877
|
+
* Build a nested tree from a flat array of nodes.
|
|
1878
|
+
* Nodes are sorted by sortOrder at each level.
|
|
1879
|
+
*/
|
|
1880
|
+
private buildTree;
|
|
1881
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<NaviGraphService, never>;
|
|
1882
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<any>;
|
|
1883
|
+
}
|
|
1884
|
+
|
|
1736
1885
|
/**
|
|
1737
1886
|
* Registry mapping string keys to Angular component classes.
|
|
1738
1887
|
*
|
|
@@ -1746,19 +1895,20 @@ export declare class NaviGraphService {
|
|
|
1746
1895
|
* private readonly registry = inject(ComponentRegistry);
|
|
1747
1896
|
* const cls = this.registry.get('MediaLibraryPage');
|
|
1748
1897
|
*/
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
}
|
|
1898
|
+
declare class ComponentRegistry {
|
|
1899
|
+
private static readonly map;
|
|
1900
|
+
static register(key: string, component: Type<unknown>): void;
|
|
1901
|
+
static get(key: string): Type<unknown> | undefined;
|
|
1902
|
+
static has(key: string): boolean;
|
|
1903
|
+
static keys(): string[];
|
|
1904
|
+
register(key: string, component: Type<unknown>): void;
|
|
1905
|
+
get(key: string): Type<unknown> | null;
|
|
1906
|
+
has(key: string): boolean;
|
|
1907
|
+
keys(): string[];
|
|
1908
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<ComponentRegistry, never>;
|
|
1909
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<any>;
|
|
1910
|
+
}
|
|
1911
|
+
|
|
1762
1912
|
/**
|
|
1763
1913
|
* `console` -- the administration host contract (the platform rule: hosts implement contracts, modules offer entries).
|
|
1764
1914
|
*
|
|
@@ -1774,35 +1924,35 @@ export declare class ComponentRegistry {
|
|
|
1774
1924
|
* (`^1.2` -- major 1, minor at least 2), checked at build against this
|
|
1775
1925
|
* constant and at install against the theme's declaration.
|
|
1776
1926
|
*/
|
|
1777
|
-
|
|
1778
|
-
|
|
1779
|
-
|
|
1927
|
+
declare const CONSOLE_CONTRACT: {
|
|
1928
|
+
readonly name: "console";
|
|
1929
|
+
readonly version: "1.0";
|
|
1780
1930
|
};
|
|
1781
|
-
|
|
1782
|
-
|
|
1783
|
-
|
|
1784
|
-
|
|
1785
|
-
|
|
1786
|
-
|
|
1787
|
-
|
|
1788
|
-
|
|
1789
|
-
|
|
1790
|
-
|
|
1791
|
-
|
|
1792
|
-
|
|
1793
|
-
|
|
1931
|
+
interface ConsoleEntry {
|
|
1932
|
+
/** The backend module id, as `config/modules/<id>`. Activation is by this id. */
|
|
1933
|
+
readonly module: string;
|
|
1934
|
+
readonly contract: 'console';
|
|
1935
|
+
/** The console versions this entry was written for: a caret range over MAJOR.MINOR, e.g. '^1.0'. */
|
|
1936
|
+
readonly range: string;
|
|
1937
|
+
readonly routes?: readonly ConsoleRouteMount[];
|
|
1938
|
+
readonly bindings?: readonly ConsoleBinding[];
|
|
1939
|
+
readonly topbar?: readonly ConsoleTopbarItem[];
|
|
1940
|
+
readonly overlays?: readonly ConsoleOverlay[];
|
|
1941
|
+
readonly panels?: readonly ConsolePanel[];
|
|
1942
|
+
readonly states?: readonly Type<unknown>[];
|
|
1943
|
+
readonly providers?: readonly ConsoleProvision[];
|
|
1794
1944
|
}
|
|
1795
1945
|
/** What a route's `data` carries for the shell: the sidebar item to light, and whether the page owns its height. */
|
|
1796
|
-
|
|
1797
|
-
|
|
1798
|
-
|
|
1799
|
-
|
|
1800
|
-
|
|
1801
|
-
|
|
1802
|
-
|
|
1803
|
-
|
|
1804
|
-
|
|
1805
|
-
|
|
1946
|
+
interface ConsoleNavHint {
|
|
1947
|
+
readonly activeNav?: string;
|
|
1948
|
+
readonly fullHeight?: boolean;
|
|
1949
|
+
/** An intermediate crumb the top bar renders between Home and the page. */
|
|
1950
|
+
readonly breadcrumb?: {
|
|
1951
|
+
readonly label: string;
|
|
1952
|
+
readonly routerLink: string;
|
|
1953
|
+
};
|
|
1954
|
+
/** Which server layout the page renders, for the generic layout pages. */
|
|
1955
|
+
readonly layoutId?: string;
|
|
1806
1956
|
}
|
|
1807
1957
|
/**
|
|
1808
1958
|
* A route mounted under the console's authenticated layout. The host adds
|
|
@@ -1810,52 +1960,52 @@ export interface ConsoleNavHint {
|
|
|
1810
1960
|
* and a `canMatch` that answers whether the module is installed here. Routes
|
|
1811
1961
|
* are lazy: a static component here would put every module's pages in `main`.
|
|
1812
1962
|
*/
|
|
1813
|
-
|
|
1814
|
-
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1963
|
+
type ConsoleRouteMount = {
|
|
1964
|
+
readonly path: string;
|
|
1965
|
+
readonly children: () => Promise<Routes>;
|
|
1966
|
+
readonly nav?: ConsoleNavHint;
|
|
1967
|
+
readonly providers?: readonly Provider[];
|
|
1818
1968
|
} | {
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
|
|
1822
|
-
|
|
1969
|
+
readonly path: string;
|
|
1970
|
+
readonly component: () => Promise<Type<unknown>>;
|
|
1971
|
+
readonly nav?: ConsoleNavHint;
|
|
1972
|
+
readonly providers?: readonly Provider[];
|
|
1823
1973
|
} | {
|
|
1824
|
-
|
|
1825
|
-
|
|
1974
|
+
readonly path: string;
|
|
1975
|
+
readonly redirectTo: string;
|
|
1826
1976
|
};
|
|
1827
1977
|
/** A `ComponentRegistry` name -> class: names a module OWNS (its server layouts name them) and names it fills in another module's slot (`profile.tab:call`). */
|
|
1828
|
-
|
|
1829
|
-
|
|
1830
|
-
|
|
1978
|
+
interface ConsoleBinding {
|
|
1979
|
+
readonly name: string;
|
|
1980
|
+
readonly component: Type<unknown>;
|
|
1831
1981
|
}
|
|
1832
1982
|
/** A tile in the top bar's quick-access strip, rendered in ascending `order`. */
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
|
|
1836
|
-
|
|
1983
|
+
interface ConsoleTopbarItem {
|
|
1984
|
+
readonly id: string;
|
|
1985
|
+
readonly order: number;
|
|
1986
|
+
readonly component: Type<unknown>;
|
|
1837
1987
|
}
|
|
1838
1988
|
/** Mounted once, inside the authenticated layout, outside the router outlet. */
|
|
1839
|
-
|
|
1840
|
-
|
|
1841
|
-
|
|
1989
|
+
interface ConsoleOverlay {
|
|
1990
|
+
readonly id: string;
|
|
1991
|
+
readonly component: Type<unknown>;
|
|
1842
1992
|
}
|
|
1843
1993
|
/**
|
|
1844
1994
|
* A dock panel the layout opens and closes: the host renders its toggle in
|
|
1845
1995
|
* the top bar and the panel in the dock, and the panel talks back through
|
|
1846
1996
|
* {@link ConsolePanelHost} -- not through outputs, which a slot cannot carry.
|
|
1847
1997
|
*/
|
|
1848
|
-
|
|
1849
|
-
|
|
1850
|
-
|
|
1851
|
-
|
|
1852
|
-
|
|
1853
|
-
|
|
1854
|
-
|
|
1855
|
-
|
|
1856
|
-
|
|
1857
|
-
|
|
1858
|
-
|
|
1998
|
+
interface ConsolePanel {
|
|
1999
|
+
readonly id: string;
|
|
2000
|
+
readonly dock: 'bottom';
|
|
2001
|
+
/** The top-bar toggle: a label (the title), an icon name or a short text, and a key the host binds with Ctrl. */
|
|
2002
|
+
readonly toggle: {
|
|
2003
|
+
readonly label: string;
|
|
2004
|
+
readonly icon?: string;
|
|
2005
|
+
readonly text?: string;
|
|
2006
|
+
readonly key?: string;
|
|
2007
|
+
};
|
|
2008
|
+
readonly component: Type<unknown>;
|
|
1859
2009
|
}
|
|
1860
2010
|
/**
|
|
1861
2011
|
* What a `provide*` line is: environment providers from a library's registry
|
|
@@ -1863,24 +2013,24 @@ export interface ConsolePanel {
|
|
|
1863
2013
|
* implementation of a single-implementation port, listed as such so the
|
|
1864
2014
|
* assembly can refuse two modules implementing one port by name.
|
|
1865
2015
|
*/
|
|
1866
|
-
|
|
1867
|
-
|
|
1868
|
-
|
|
1869
|
-
|
|
2016
|
+
type ConsoleProvision = EnvironmentProviders | Provider | ConsolePortProvision;
|
|
2017
|
+
interface ConsolePortProvision {
|
|
2018
|
+
readonly port: InjectionToken<unknown>;
|
|
2019
|
+
readonly useExisting: Type<unknown>;
|
|
1870
2020
|
}
|
|
1871
|
-
|
|
2021
|
+
declare function isPortProvision(p: ConsoleProvision): p is ConsolePortProvision;
|
|
1872
2022
|
/** The identity function with the type: an entry file exports data, nothing runs. */
|
|
1873
|
-
|
|
2023
|
+
declare function consoleEntry(entry: ConsoleEntry): ConsoleEntry;
|
|
1874
2024
|
/**
|
|
1875
2025
|
* Whether a caret range over MAJOR.MINOR admits a version: same major, minor
|
|
1876
2026
|
* at least the range's. Malformed input admits nothing -- a range that cannot
|
|
1877
2027
|
* be read must refuse, never pass.
|
|
1878
2028
|
*/
|
|
1879
|
-
|
|
2029
|
+
declare function satisfiesRange(range: string, version: string): boolean;
|
|
1880
2030
|
/** The refusal an assembly raises: one message per collision, all of them, by name. */
|
|
1881
|
-
|
|
1882
|
-
|
|
1883
|
-
|
|
2031
|
+
declare class ConsoleAssemblyError extends Error {
|
|
2032
|
+
readonly problems: readonly string[];
|
|
2033
|
+
constructor(problems: readonly string[]);
|
|
1884
2034
|
}
|
|
1885
2035
|
/**
|
|
1886
2036
|
* The checks every assembly runs, in one place so the build-time spec, the
|
|
@@ -1888,7 +2038,8 @@ export declare class ConsoleAssemblyError extends Error {
|
|
|
1888
2038
|
* this contract, and the collision rules of the six lists. Returns the
|
|
1889
2039
|
* entries unchanged when nothing is wrong.
|
|
1890
2040
|
*/
|
|
1891
|
-
|
|
2041
|
+
declare function assembleConsole(entries: readonly ConsoleEntry[]): readonly ConsoleEntry[];
|
|
2042
|
+
|
|
1892
2043
|
/**
|
|
1893
2044
|
* The host side of `console@1`: what a theme calls to turn the assembled
|
|
1894
2045
|
* entries into routes, providers and the lists its shell renders.
|
|
@@ -1901,31 +2052,31 @@ export declare function assembleConsole(entries: readonly ConsoleEntry[]): reado
|
|
|
1901
2052
|
* silently absent.
|
|
1902
2053
|
*/
|
|
1903
2054
|
/** Every entry this build assembled, after {@link assembleConsole}'s checks. */
|
|
1904
|
-
|
|
2055
|
+
declare const CONSOLE_ENTRIES: InjectionToken<readonly ConsoleEntry[]>;
|
|
1905
2056
|
/**
|
|
1906
2057
|
* The port a dock panel talks back through: the layout provides one per
|
|
1907
2058
|
* panel it mounts, so the panel never needs an output the slot cannot carry.
|
|
1908
2059
|
*/
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
2060
|
+
declare abstract class ConsolePanelHost {
|
|
2061
|
+
abstract close(): void;
|
|
2062
|
+
abstract maximize(on: boolean): void;
|
|
1912
2063
|
}
|
|
1913
2064
|
/** The console modules the manifest says are installed here. */
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
1917
|
-
|
|
1918
|
-
|
|
1919
|
-
|
|
1920
|
-
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
2065
|
+
declare function installedConsoleModules(manifest: ApiManifest | null): ReadonlySet<string>;
|
|
2066
|
+
declare class ConsoleActivation {
|
|
2067
|
+
private readonly entries;
|
|
2068
|
+
private readonly manifest;
|
|
2069
|
+
readonly installed: i0.Signal<ReadonlySet<string>>;
|
|
2070
|
+
/** The entries of installed modules, in assembly order. */
|
|
2071
|
+
readonly active: i0.Signal<ConsoleEntry[]>;
|
|
2072
|
+
readonly topbar: i0.Signal<readonly ConsoleTopbarItem[]>;
|
|
2073
|
+
readonly overlays: i0.Signal<readonly ConsoleOverlay[]>;
|
|
2074
|
+
readonly panels: i0.Signal<readonly ConsolePanel[]>;
|
|
2075
|
+
/** Modules the manifest names that this build has no entry for: what a diagnostics page shows by name. */
|
|
2076
|
+
readonly missing: i0.Signal<string[]>;
|
|
2077
|
+
isInstalled(module: string): boolean;
|
|
2078
|
+
static ɵfac: i0.ɵɵFactoryDeclaration<ConsoleActivation, never>;
|
|
2079
|
+
static ɵprov: i0.ɵɵInjectableDeclaration<any>;
|
|
1929
2080
|
}
|
|
1930
2081
|
/**
|
|
1931
2082
|
* `canMatch` for a module's mount: the route exists only where the module is
|
|
@@ -1936,8 +2087,16 @@ export declare class ConsoleActivation {
|
|
|
1936
2087
|
* landed on the dashboard (measured 2026-09-22: no chunk requested for the
|
|
1937
2088
|
* mount, the dashboard's instead). `ready$` is what the auth guard waits on
|
|
1938
2089
|
* too, and it fires whether or not the config fetch succeeded.
|
|
2090
|
+
*
|
|
2091
|
+
* !! Then the console's own manifest, when someone is signed in: `ui.modules` is
|
|
2092
|
+
* the console's, not the public config's, and arrives with it
|
|
2093
|
+
* ({@link ConsoleAccessService}). canMatch runs before the layout's auth guard,
|
|
2094
|
+
* which asks the same question, so answering from the public config alone said
|
|
2095
|
+
* "no" to every module once that config stopped carrying them. Nobody signed in:
|
|
2096
|
+
* nothing is asked, the answer is the public config's, and the layout's guard
|
|
2097
|
+
* sends the person to sign in. A refusal merges nothing, so the answer is no.
|
|
1939
2098
|
*/
|
|
1940
|
-
|
|
2099
|
+
declare function consoleModuleInstalled(module: string): CanMatchFn;
|
|
1941
2100
|
/**
|
|
1942
2101
|
* The children a theme mounts under its authenticated layout for the
|
|
1943
2102
|
* assembled entries: one `Route` per mount, lazy as written, `data` from the
|
|
@@ -1945,7 +2104,7 @@ export declare function consoleModuleInstalled(module: string): CanMatchFn;
|
|
|
1945
2104
|
* shape around them (login, the layout, `canActivate: [authGuard]`, the
|
|
1946
2105
|
* wildcard).
|
|
1947
2106
|
*/
|
|
1948
|
-
|
|
2107
|
+
declare function consoleChildren(entries: readonly ConsoleEntry[]): Routes;
|
|
1949
2108
|
/**
|
|
1950
2109
|
* The providers a theme adds for the assembled entries: the store with the
|
|
1951
2110
|
* host's states and every module state, the registry bindings (an
|
|
@@ -1954,9 +2113,10 @@ export declare function consoleChildren(entries: readonly ConsoleEntry[]): Route
|
|
|
1954
2113
|
* library providers a module's provisions extend (the editor bridge, the
|
|
1955
2114
|
* PDF viewer), as the hand-written lines were.
|
|
1956
2115
|
*/
|
|
1957
|
-
|
|
1958
|
-
|
|
2116
|
+
declare function provideConsole(entries: readonly ConsoleEntry[], options: {
|
|
2117
|
+
readonly hostStates: readonly Type<unknown>[];
|
|
1959
2118
|
}): EnvironmentProviders;
|
|
2119
|
+
|
|
1960
2120
|
/**
|
|
1961
2121
|
* Phase H7 -- admin Site Selector.
|
|
1962
2122
|
*
|
|
@@ -1986,7 +2146,8 @@ export declare function provideConsole(entries: readonly ConsoleEntry[], options
|
|
|
1986
2146
|
* be ignored by the backend anyway, but skipping keeps wire traffic clean)
|
|
1987
2147
|
* - requests that already carry the header (programmatic overrides win)
|
|
1988
2148
|
*/
|
|
1989
|
-
|
|
2149
|
+
declare const sectionInterceptor: HttpInterceptorFn;
|
|
2150
|
+
|
|
1990
2151
|
/**
|
|
1991
2152
|
* The one fact the section interceptor needs: which site the operator is
|
|
1992
2153
|
* currently working in.
|
|
@@ -2001,13 +2162,16 @@ export declare const sectionInterceptor: HttpInterceptorFn;
|
|
|
2001
2162
|
* Read SYNCHRONOUSLY on purpose. An interceptor decides whether to stamp a
|
|
2002
2163
|
* header while building the request; it cannot wait on a stream.
|
|
2003
2164
|
*/
|
|
2004
|
-
|
|
2005
|
-
|
|
2006
|
-
|
|
2165
|
+
interface CurrentSectionPort {
|
|
2166
|
+
/** Slug of the active section, or null when the operator picked none. */
|
|
2167
|
+
currentSlug(): string | null;
|
|
2007
2168
|
}
|
|
2008
2169
|
/**
|
|
2009
2170
|
* Optional by design: an application assembled without a Sections module binds
|
|
2010
2171
|
* nothing and simply sends no `X-CoolMS-Section` header, which is exactly what
|
|
2011
2172
|
* a single-site install wants.
|
|
2012
2173
|
*/
|
|
2013
|
-
|
|
2174
|
+
declare const CURRENT_SECTION: InjectionToken<CurrentSectionPort>;
|
|
2175
|
+
|
|
2176
|
+
export { AppConfigState, AppInitService, AuthRefreshCoordinator, AuthState, BACKGROUND_REQUEST, BYPASS_AUTH, BYPASS_ELEVATION, CONSOLE_CONTRACT, CONSOLE_ENTRIES, CURRENT_SECTION, CmsLoaderComponent, ComponentRegistry, ConfigService, ConsoleAccessService, ConsoleActivation, ConsoleAssemblyError, ConsolePanelHost, CrossTabAuthSyncService, ELEVATION_NOTICE, ELEVATION_PROMPT, ELEVATION_REQUIRED_HEADER, ELEVATION_WARNING_FACTOR_MISSING, ELEVATION_WARNING_MFA_OFF, ELEVATION_WARNING_PASSWORD_NOT_SET, ElevationService, EntitySearchService, ErrorHandlerService, IdentityApiClient, Login, LoginComponent, Logout, NaviGraphService, PatchCurrentUser, RealtimeTokenClient, RestoreSession, SetAppConfig, SetTokens, SidebarStateService, ThemeService, UserPreferencesService, assembleConsole, authGuard, authInterceptor, consoleChildren, consoleEntry, consoleModuleInstalled, elevationInterceptor, installedConsoleModules, isElevationRefusal, isPortProvision, loginPageGuard, provideConsole, resolvePattern, satisfiesRange, sectionInterceptor };
|
|
2177
|
+
export type { ApiManifest, AppConfigStateModel, AuthApiManifest, AuthStateModel, CentrifugoConnectionTokenDto, CentrifugoSubscriptionTokenDto, ConsoleAccess, ConsoleBinding, ConsoleEntry, ConsoleManifestResponse, ConsoleNavHint, ConsoleOverlay, ConsolePanel, ConsolePortProvision, ConsoleProvision, ConsoleRouteMount, ConsoleTopbarItem, ContentApiManifest, CurrentSectionPort, DataGridApiManifest, DataGridPreference, DataGridPreferences, DataSourceDefinition, DataSourceLoading, DataSourceOption, DataSourceType, DataSourceWidget, DatagridConfig, DialogConfig, DocumentApiManifest, DomainExplorerApiManifest, DynamicEntityApiManifest, EditorApiManifest, EditorApiManifestEntry, EditorApiManifestProfile, ElevationChange, ElevationChangeKind, ElevationDropReason, ElevationEnded, ElevationEndedReason, ElevationNoticePort, ElevationPromptPort, ElevationPromptRequest, ElevationState, ElevationWarning, EntitySearchResult, FieldItem, FieldSecurityPolicy, FieldType, FormAction, FormActionType, FormActionVariant, FormConfig, FormRenderDefinition, HydraCollection, HydraView, IdentityApiManifest, LayoutConfig, LayoutHeaderAction, LayoutNode, LayoutSection, MediaApiManifest, NaviApiManifest, NaviGraphMeta, NaviGraphNode, NaviGraphNodeFlat, NaviGraphTarget, NavigraphConfig, PlatformDefaults, RelationDefinition, ResolvedTheme, SectionApiManifest, SubFormDefinition, TerminalApiManifest, ThemeChoice, ThemeConfigResponse, TokenResponse, UiApiManifest, UiModuleEntryManifest, UserDto, ValidatorDefinition, VfsApiManifest, ViewerApiManifest, ViewerDefinitionApiManifest, ViewerProfileApiManifest, VisibilityCondition, VisibilityOperator };
|