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