@xh/hoist 72.2.0 → 72.4.0
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 +40 -2
- package/admin/tabs/activity/tracking/detail/ActivityDetailModel.ts +2 -2
- package/admin/tabs/cluster/instances/BaseInstanceModel.ts +1 -29
- package/admin/tabs/cluster/instances/InstancesTabModel.ts +4 -3
- package/admin/tabs/cluster/instances/logs/LogDisplay.ts +12 -14
- package/admin/tabs/cluster/instances/logs/LogDisplayModel.ts +0 -2
- package/admin/tabs/cluster/instances/logs/LogViewer.ts +6 -5
- package/admin/tabs/cluster/instances/logs/LogViewerModel.ts +8 -1
- package/admin/tabs/cluster/instances/memory/MemoryMonitorModel.ts +1 -0
- package/admin/tabs/cluster/instances/services/DetailsModel.ts +1 -2
- package/admin/tabs/cluster/instances/services/DetailsPanel.ts +20 -14
- package/admin/tabs/cluster/instances/services/ServiceModel.ts +14 -6
- package/admin/tabs/cluster/instances/services/ServicePanel.ts +9 -10
- package/admin/tabs/cluster/instances/websocket/WebSocketColumns.ts +9 -0
- package/admin/tabs/cluster/instances/websocket/WebSocketModel.ts +2 -1
- package/admin/tabs/cluster/objects/DetailModel.ts +4 -40
- package/admin/tabs/cluster/objects/DetailPanel.ts +2 -1
- package/admin/tabs/userData/roles/RoleModel.ts +1 -1
- package/appcontainer/AppContainerModel.ts +2 -0
- package/appcontainer/AppStateModel.ts +46 -9
- package/build/types/admin/tabs/activity/tracking/ActivityTrackingModel.d.ts +4 -1
- package/build/types/admin/tabs/cluster/instances/BaseInstanceModel.d.ts +1 -3
- package/build/types/admin/tabs/cluster/instances/services/DetailsModel.d.ts +2 -3
- package/build/types/admin/tabs/cluster/instances/websocket/WebSocketColumns.d.ts +1 -0
- package/build/types/admin/tabs/cluster/objects/DetailModel.d.ts +1 -3
- package/build/types/appcontainer/AppStateModel.d.ts +7 -1
- package/build/types/cmp/viewmanager/ViewManagerModel.d.ts +7 -0
- package/build/types/core/XH.d.ts +11 -1
- package/build/types/desktop/cmp/tab/TabSwitcher.d.ts +1 -1
- package/build/types/format/FormatDate.d.ts +22 -1
- package/build/types/format/FormatMisc.d.ts +3 -2
- package/build/types/security/BaseOAuthClient.d.ts +6 -7
- package/build/types/security/Types.d.ts +32 -5
- package/build/types/security/msal/MsalClient.d.ts +14 -1
- package/build/types/svc/ClientHealthService.d.ts +58 -0
- package/build/types/svc/TrackService.d.ts +19 -1
- package/build/types/svc/index.d.ts +1 -0
- package/build/types/utils/js/index.d.ts +0 -1
- package/cmp/viewmanager/ViewManagerModel.ts +10 -1
- package/core/XH.ts +26 -1
- package/data/Store.ts +3 -0
- package/desktop/cmp/grid/editors/BooleanEditor.ts +15 -3
- package/desktop/cmp/tab/TabSwitcher.ts +1 -1
- package/desktop/cmp/viewmanager/ViewMenu.ts +11 -9
- package/format/FormatDate.ts +45 -3
- package/format/FormatMisc.ts +6 -4
- package/package.json +2 -2
- package/security/BaseOAuthClient.ts +12 -10
- package/security/Types.ts +35 -6
- package/security/msal/MsalClient.ts +126 -21
- package/svc/ClientHealthService.ts +165 -0
- package/svc/FetchService.ts +3 -2
- package/svc/TrackService.ts +27 -5
- package/svc/WebSocketService.ts +1 -2
- package/svc/index.ts +1 -0
- package/tsconfig.tsbuildinfo +1 -1
- package/utils/js/index.ts +0 -1
- package/build/types/utils/js/BrowserUtils.d.ts +0 -4
- package/utils/js/BrowserUtils.ts +0 -52
|
@@ -4,11 +4,10 @@
|
|
|
4
4
|
*
|
|
5
5
|
* Copyright © 2025 Extremely Heavy Industries Inc.
|
|
6
6
|
*/
|
|
7
|
-
import {AppState, AppSuspendData, HoistModel, XH} from '@xh/hoist/core';
|
|
7
|
+
import {AppState, AppSuspendData, HoistModel, PlainObject, XH} from '@xh/hoist/core';
|
|
8
8
|
import {action, makeObservable, observable} from '@xh/hoist/mobx';
|
|
9
9
|
import {Timer} from '@xh/hoist/utils/async';
|
|
10
|
-
import {
|
|
11
|
-
import {camelCase, isBoolean, isString, mapKeys} from 'lodash';
|
|
10
|
+
import {camelCase, isBoolean, isString, mapKeys, pick} from 'lodash';
|
|
12
11
|
|
|
13
12
|
/**
|
|
14
13
|
* Support for Core Hoist Application state and loading.
|
|
@@ -24,8 +23,13 @@ export class AppStateModel extends HoistModel {
|
|
|
24
23
|
suspendData: AppSuspendData;
|
|
25
24
|
accessDeniedMessage: string = 'Access Denied';
|
|
26
25
|
|
|
26
|
+
/**
|
|
27
|
+
* Timestamp when the app first started loading, prior to even JS download/eval.
|
|
28
|
+
* Read from timestamp set on window within index.html.
|
|
29
|
+
*/
|
|
30
|
+
readonly loadStarted: number = window['_xhLoadTimestamp'];
|
|
31
|
+
|
|
27
32
|
private timings: Record<AppState, number> = {} as Record<AppState, number>;
|
|
28
|
-
private loadStarted: number = window['_xhLoadTimestamp']; // set in index.html
|
|
29
33
|
private lastStateChangeTime: number = this.loadStarted;
|
|
30
34
|
|
|
31
35
|
constructor() {
|
|
@@ -88,13 +92,14 @@ export class AppStateModel extends HoistModel {
|
|
|
88
92
|
timestamp: loadStarted,
|
|
89
93
|
elapsed: Date.now() - loadStarted - (timings.LOGIN_REQUIRED ?? 0),
|
|
90
94
|
data: {
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
locationHref: window.location.href,
|
|
95
|
+
clientId: XH.clientId,
|
|
96
|
+
sessionId: XH.sessionId,
|
|
94
97
|
timings: mapKeys(timings, (v, k) => camelCase(k)),
|
|
95
|
-
|
|
98
|
+
clientHealth: XH.clientHealthService.getReport(),
|
|
99
|
+
window: this.getWindowData(),
|
|
100
|
+
screen: this.getScreenData()
|
|
96
101
|
},
|
|
97
|
-
logData: ['
|
|
102
|
+
logData: ['clientId', 'sessionId'],
|
|
98
103
|
omit: !XH.appSpec.trackAppLoad
|
|
99
104
|
})
|
|
100
105
|
});
|
|
@@ -110,4 +115,36 @@ export class AppStateModel extends HoistModel {
|
|
|
110
115
|
});
|
|
111
116
|
});
|
|
112
117
|
}
|
|
118
|
+
|
|
119
|
+
private getScreenData(): PlainObject {
|
|
120
|
+
const screen = window.screen as any;
|
|
121
|
+
if (!screen) return null;
|
|
122
|
+
|
|
123
|
+
const ret: PlainObject = pick(screen, [
|
|
124
|
+
'availWidth',
|
|
125
|
+
'availHeight',
|
|
126
|
+
'width',
|
|
127
|
+
'height',
|
|
128
|
+
'colorDepth',
|
|
129
|
+
'pixelDepth',
|
|
130
|
+
'availLeft',
|
|
131
|
+
'availTop'
|
|
132
|
+
]);
|
|
133
|
+
if (screen.orientation) {
|
|
134
|
+
ret.orientation = pick(screen.orientation, ['angle', 'type']);
|
|
135
|
+
}
|
|
136
|
+
return ret;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
private getWindowData(): PlainObject {
|
|
140
|
+
return pick(window, [
|
|
141
|
+
'devicePixelRatio',
|
|
142
|
+
'screenX',
|
|
143
|
+
'screenY',
|
|
144
|
+
'innerWidth',
|
|
145
|
+
'innerHeight',
|
|
146
|
+
'outerWidth',
|
|
147
|
+
'outerHeight'
|
|
148
|
+
]);
|
|
149
|
+
}
|
|
113
150
|
}
|
|
@@ -26,7 +26,10 @@ export declare class ActivityTrackingModel extends HoistModel {
|
|
|
26
26
|
*/
|
|
27
27
|
get queryDisplayString(): string;
|
|
28
28
|
get endDay(): LocalDate;
|
|
29
|
-
get maxRowOptions():
|
|
29
|
+
get maxRowOptions(): {
|
|
30
|
+
value: number;
|
|
31
|
+
label: string;
|
|
32
|
+
}[];
|
|
30
33
|
get maxRows(): number;
|
|
31
34
|
/** True if data loaded from the server has been topped by maxRows. */
|
|
32
35
|
get maxRowsReached(): boolean;
|
|
@@ -1,13 +1,11 @@
|
|
|
1
1
|
/// <reference types="react" />
|
|
2
2
|
import { InstancesTabModel } from '@xh/hoist/admin/tabs/cluster/instances/InstancesTabModel';
|
|
3
|
-
import { HoistModel, LoadSpec
|
|
3
|
+
import { HoistModel, LoadSpec } from '@xh/hoist/core';
|
|
4
4
|
export declare class BaseInstanceModel extends HoistModel {
|
|
5
5
|
viewRef: import("react").RefObject<HTMLElement>;
|
|
6
6
|
parent: InstancesTabModel;
|
|
7
7
|
get instanceName(): string;
|
|
8
|
-
fmtStats(stats: PlainObject): string;
|
|
9
8
|
handleLoadException(e: unknown, loadSpec: LoadSpec): void;
|
|
10
9
|
get isVisible(): boolean;
|
|
11
10
|
private isInstanceNotFound;
|
|
12
|
-
private processTimestamps;
|
|
13
11
|
}
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
import { HoistModel, LoadSpec, PlainObject } from '@xh/hoist/core';
|
|
2
|
-
import { StoreRecord } from '@xh/hoist/data';
|
|
3
2
|
import { ServiceModel } from './ServiceModel';
|
|
4
3
|
export declare class DetailsModel extends HoistModel {
|
|
5
4
|
parent: ServiceModel;
|
|
6
|
-
svcName:
|
|
5
|
+
svcName: String;
|
|
7
6
|
stats: PlainObject;
|
|
8
|
-
get selectedRecord(): StoreRecord;
|
|
7
|
+
get selectedRecord(): import("../../../../../data").StoreRecord;
|
|
9
8
|
onLinked(): void;
|
|
10
9
|
doLoadAsync(loadSpec: LoadSpec): Promise<void>;
|
|
11
10
|
}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ClusterObjectsModel } from '@xh/hoist/admin/tabs/cluster/objects/ClusterObjectsModel';
|
|
2
2
|
import { GridModel } from '@xh/hoist/cmp/grid';
|
|
3
|
-
import { HoistModel
|
|
3
|
+
import { HoistModel } from '@xh/hoist/core';
|
|
4
4
|
import { StoreRecord } from '@xh/hoist/data';
|
|
5
5
|
export declare class DetailModel extends HoistModel {
|
|
6
6
|
parent: ClusterObjectsModel;
|
|
@@ -11,9 +11,7 @@ export declare class DetailModel extends HoistModel {
|
|
|
11
11
|
get instanceName(): string;
|
|
12
12
|
get selectedAdminStats(): any;
|
|
13
13
|
constructor();
|
|
14
|
-
fmtStats(stats: PlainObject): string;
|
|
15
14
|
private updateGridModel;
|
|
16
15
|
private createGridModel;
|
|
17
16
|
private createColSpec;
|
|
18
|
-
private processTimestamps;
|
|
19
17
|
}
|
|
@@ -10,8 +10,12 @@ export declare class AppStateModel extends HoistModel {
|
|
|
10
10
|
lastActivityMs: number;
|
|
11
11
|
suspendData: AppSuspendData;
|
|
12
12
|
accessDeniedMessage: string;
|
|
13
|
+
/**
|
|
14
|
+
* Timestamp when the app first started loading, prior to even JS download/eval.
|
|
15
|
+
* Read from timestamp set on window within index.html.
|
|
16
|
+
*/
|
|
17
|
+
readonly loadStarted: number;
|
|
13
18
|
private timings;
|
|
14
|
-
private loadStarted;
|
|
15
19
|
private lastStateChangeTime;
|
|
16
20
|
constructor();
|
|
17
21
|
setAppState(nextState: AppState): void;
|
|
@@ -19,4 +23,6 @@ export declare class AppStateModel extends HoistModel {
|
|
|
19
23
|
checkAccess(): boolean;
|
|
20
24
|
private trackLoad;
|
|
21
25
|
private createActivityListeners;
|
|
26
|
+
private getScreenData;
|
|
27
|
+
private getWindowData;
|
|
22
28
|
}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { HoistModel, LoadSpec, PlainObject, TaskObserver, Thunkable } from '@xh/hoist/core';
|
|
2
2
|
import type { ViewManagerProvider } from '@xh/hoist/core';
|
|
3
|
+
import { ReactNode } from 'react';
|
|
3
4
|
import { ViewInfo } from './ViewInfo';
|
|
4
5
|
import { View } from './View';
|
|
5
6
|
export interface ViewCreateSpec {
|
|
@@ -85,6 +86,11 @@ export interface ViewManagerConfig {
|
|
|
85
86
|
* Optional user-facing display name for describing global views. Defaults to 'global'
|
|
86
87
|
*/
|
|
87
88
|
globalDisplayName?: string;
|
|
89
|
+
/**
|
|
90
|
+
* Optional key to pass a method that returns a customized BlueprintJS `menuItem` for listing
|
|
91
|
+
* views in the ViewManager menu.
|
|
92
|
+
*/
|
|
93
|
+
viewMenuItemFn?: (view: ViewInfo, model: ViewManagerModel) => ReactNode;
|
|
88
94
|
}
|
|
89
95
|
/**
|
|
90
96
|
* ViewManagerModel coordinates the loading, saving, and management of user-defined bundles of
|
|
@@ -121,6 +127,7 @@ export declare class ViewManagerModel<T = PlainObject> extends HoistModel {
|
|
|
121
127
|
readonly instance: string;
|
|
122
128
|
readonly typeDisplayName: string;
|
|
123
129
|
readonly globalDisplayName: string;
|
|
130
|
+
readonly viewMenuItemFn: (view: ViewInfo, model: ViewManagerModel) => ReactNode;
|
|
124
131
|
readonly enableAutoSave: boolean;
|
|
125
132
|
readonly enableDefault: boolean;
|
|
126
133
|
readonly enableGlobal: boolean;
|
package/build/types/core/XH.d.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { RouterModel } from '@xh/hoist/appcontainer/RouterModel';
|
|
2
2
|
import { HoistAuthModel } from '@xh/hoist/core/HoistAuthModel';
|
|
3
3
|
import { Store } from '@xh/hoist/data';
|
|
4
|
-
import { AlertBannerService, AutoRefreshService, ChangelogService, ConfigService, EnvironmentService, FetchOptions, FetchService, GridAutosizeService, GridExportService, IdentityService, IdleService, InspectorService, JsonBlobService, LocalStorageService, PrefService, SessionStorageService, TrackService, WebSocketService } from '@xh/hoist/svc';
|
|
4
|
+
import { AlertBannerService, AutoRefreshService, ChangelogService, ConfigService, EnvironmentService, FetchOptions, FetchService, GridAutosizeService, GridExportService, IdentityService, IdleService, InspectorService, JsonBlobService, LocalStorageService, PrefService, SessionStorageService, TrackService, WebSocketService, ClientHealthService } from '@xh/hoist/svc';
|
|
5
5
|
import { Router, State } from 'router5';
|
|
6
6
|
import { CancelFn } from 'router5/types/types/base';
|
|
7
7
|
import { SetOptional } from 'type-fest';
|
|
@@ -21,6 +21,13 @@ export declare const MIN_HOIST_CORE_VERSION = "28.0";
|
|
|
21
21
|
* Available via import as `XH` - also installed as `window.XH` for troubleshooting purposes.
|
|
22
22
|
*/
|
|
23
23
|
export declare class XHApi {
|
|
24
|
+
/** Unique id for this loaded instance of the app. Unique for every refresh of document. */
|
|
25
|
+
clientId: string;
|
|
26
|
+
/**
|
|
27
|
+
* Unique id for this browser tab/window on this domain.
|
|
28
|
+
* Corresponds to the scope of the built-in sessionStorage object.
|
|
29
|
+
*/
|
|
30
|
+
sessionId: string;
|
|
24
31
|
/** Core implementation model hosting all application state. */
|
|
25
32
|
appContainerModel: AppContainerModel;
|
|
26
33
|
/** Provider of centralized exception handling for the app. */
|
|
@@ -47,6 +54,7 @@ export declare class XHApi {
|
|
|
47
54
|
alertBannerService: AlertBannerService;
|
|
48
55
|
autoRefreshService: AutoRefreshService;
|
|
49
56
|
changelogService: ChangelogService;
|
|
57
|
+
clientHealthService: ClientHealthService;
|
|
50
58
|
configService: ConfigService;
|
|
51
59
|
environmentService: EnvironmentService;
|
|
52
60
|
fetchService: FetchService;
|
|
@@ -404,6 +412,8 @@ export declare class XHApi {
|
|
|
404
412
|
*/
|
|
405
413
|
genId(): string;
|
|
406
414
|
private get acm();
|
|
415
|
+
private genClientId;
|
|
416
|
+
private genSessionId;
|
|
407
417
|
}
|
|
408
418
|
/** The app-wide singleton instance. */
|
|
409
419
|
export declare const XH: XHApi;
|
|
@@ -10,6 +10,6 @@ import '@xh/hoist/desktop/register';
|
|
|
10
10
|
*
|
|
11
11
|
* Overflowing tabs can be displayed in a dropdown menu if `enableOverflow` is true.
|
|
12
12
|
* Note that in order for tabs to overflow, the TabSwitcher or it's wrapper must have a
|
|
13
|
-
*
|
|
13
|
+
* maximum width.
|
|
14
14
|
*/
|
|
15
15
|
export declare const TabSwitcher: import("react").FC<TabSwitcherProps>, tabSwitcher: import("@xh/hoist/core").ElementFactory<TabSwitcherProps>;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { ReactNode } from 'react';
|
|
2
|
-
import { DateLike } from '../core/types/Types';
|
|
2
|
+
import { DateLike, PlainObject } from '../core/types/Types';
|
|
3
3
|
import { FormatOptions } from './FormatMisc';
|
|
4
4
|
export declare const DATE_FMT = "YYYY-MM-DD", DATETIME_FMT = "YYYY-MM-DD h:mma", DATETIMESEC_FMT = "YYYY-MM-DD h:mm:ssa", TIME_FMT = "h:mma", MONTH_DAY_FMT = "MMM D";
|
|
5
5
|
/**
|
|
@@ -49,4 +49,25 @@ export interface CompactDateFormatOptions extends FormatOptions<DateLike> {
|
|
|
49
49
|
* Render dates formatted based on distance in time from current day.
|
|
50
50
|
*/
|
|
51
51
|
export declare function fmtCompactDate(v: DateLike, opts?: CompactDateFormatOptions): ReactNode;
|
|
52
|
+
export interface TimestampReplacerConfig {
|
|
53
|
+
/**
|
|
54
|
+
* Suffixes used to identify keys that may hold timestamps.
|
|
55
|
+
* Defaults to ['time', 'date', 'timestamp']
|
|
56
|
+
*/
|
|
57
|
+
suffixes?: string[];
|
|
58
|
+
/**
|
|
59
|
+
* Format for replaced timestamp.
|
|
60
|
+
* Defaults to 'MMM DD HH:mm:ss.SSS'
|
|
61
|
+
*/
|
|
62
|
+
format?: string;
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Replace timestamps in an Object with formatted strings.
|
|
66
|
+
*/
|
|
67
|
+
export declare function withFormattedTimestamps(obj: PlainObject, config?: TimestampReplacerConfig): PlainObject;
|
|
68
|
+
/**
|
|
69
|
+
* Create a replacer, suitable for JSON.stringify, that will replace timestamps with
|
|
70
|
+
* formatted strings.
|
|
71
|
+
*/
|
|
72
|
+
export declare function timestampReplacer(config?: TimestampReplacerConfig): (k: string, v: any) => any;
|
|
52
73
|
export declare const dateRenderer: (obj?: string | DateFormatOptions) => (v: DateLike) => ReactNode, dateTimeRenderer: (obj?: string | DateFormatOptions) => (v: any) => ReactNode, dateTimeSecRenderer: (obj?: string | DateFormatOptions) => (v: DateLike) => ReactNode, timeRenderer: (obj?: string | DateFormatOptions) => (v: DateLike) => ReactNode, compactDateRenderer: (obj?: CompactDateFormatOptions) => (v: DateLike) => ReactNode;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { CSSProperties, ReactNode } from 'react';
|
|
2
|
+
import { PlainObject } from '@xh/hoist/core';
|
|
2
3
|
export interface FormatOptions<T = any> {
|
|
3
4
|
/** Display value for null values. */
|
|
4
5
|
nullDisplay?: ReactNode;
|
|
@@ -32,9 +33,9 @@ export interface JSONFormatOptions {
|
|
|
32
33
|
space?: number | string;
|
|
33
34
|
}
|
|
34
35
|
/**
|
|
35
|
-
* Pretty-print a JSON string, adding line breaks and indentation.
|
|
36
|
+
* Pretty-print a JSON string or (JSON Object), adding line breaks and indentation.
|
|
36
37
|
*/
|
|
37
|
-
export declare function fmtJson(
|
|
38
|
+
export declare function fmtJson(v: string | PlainObject, opts?: JSONFormatOptions): string;
|
|
38
39
|
/**
|
|
39
40
|
* Basic util for splitting a string (via ' ') and capitalizing each word - e.g. for names.
|
|
40
41
|
* Not intended to handle more advanced usages such as HTML or other word boundary characters.
|
|
@@ -40,14 +40,13 @@ export interface BaseOAuthClientConfig<S extends AccessTokenSpec> {
|
|
|
40
40
|
*/
|
|
41
41
|
idScopes?: string[];
|
|
42
42
|
/**
|
|
43
|
-
* Optional
|
|
43
|
+
* Optional spec for access tokens to be loaded and maintained to support access to one or more
|
|
44
|
+
* different back-end resources, distinct from the core Hoist auth flow via ID token.
|
|
44
45
|
*
|
|
45
|
-
* Map of
|
|
46
|
-
*
|
|
47
|
-
*
|
|
48
|
-
*
|
|
49
|
-
*
|
|
50
|
-
* Use this map to gain targeted access tokens for different back-end resources.
|
|
46
|
+
* Map of key to a spec for an access token. The key is an arbitrary, app-determined string
|
|
47
|
+
* used to retrieve the loaded token via {@link getAccessTokenAsync}. The spec is implementation
|
|
48
|
+
* specific, but will typically include scopes to be loaded for the access token and potentially
|
|
49
|
+
* other metadata required by the underlying provider.
|
|
51
50
|
*/
|
|
52
51
|
accessTokens?: Record<string, S>;
|
|
53
52
|
}
|
|
@@ -1,12 +1,39 @@
|
|
|
1
1
|
import { Token } from './Token';
|
|
2
|
-
export type TokenMap = Record<string, Token>;
|
|
3
2
|
export interface AccessTokenSpec {
|
|
4
3
|
/**
|
|
5
|
-
* Mode governing when the access token should be requested from provider
|
|
6
|
-
* eager (
|
|
7
|
-
*
|
|
4
|
+
* Mode governing when the access token should be requested from provider:
|
|
5
|
+
* - eager (or undefined) - load on overall initialization, but do not block on failure.
|
|
6
|
+
* Useful for tokens that an app is almost certain to require during a user session.
|
|
7
|
+
* - lazy - defer loading until first requested by client. Useful for tokens that might
|
|
8
|
+
* never be needed by the app during a given user session.
|
|
8
9
|
*/
|
|
9
|
-
fetchMode
|
|
10
|
+
fetchMode?: 'eager' | 'lazy';
|
|
10
11
|
/** Scopes for the desired access token.*/
|
|
11
12
|
scopes: string[];
|
|
12
13
|
}
|
|
14
|
+
export type TokenMap = Record<string, Token>;
|
|
15
|
+
/** Aggregated telemetry results, produced by {@link MsalClient} when enabled via config. */
|
|
16
|
+
export interface TelemetryResults {
|
|
17
|
+
/** Stats by event type - */
|
|
18
|
+
events: Record<string, TelemetryEventResults>;
|
|
19
|
+
}
|
|
20
|
+
/** Aggregated telemetry results for a single type of event. */
|
|
21
|
+
export interface TelemetryEventResults {
|
|
22
|
+
firstTime: number;
|
|
23
|
+
lastTime: number;
|
|
24
|
+
successCount: number;
|
|
25
|
+
failureCount: number;
|
|
26
|
+
/** Timing info (in ms) for event instances reported with duration. */
|
|
27
|
+
duration: {
|
|
28
|
+
count: number;
|
|
29
|
+
total: number;
|
|
30
|
+
average: number;
|
|
31
|
+
worst: number;
|
|
32
|
+
};
|
|
33
|
+
lastFailure?: {
|
|
34
|
+
time: number;
|
|
35
|
+
duration: number;
|
|
36
|
+
code: string;
|
|
37
|
+
name: string;
|
|
38
|
+
};
|
|
39
|
+
}
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
import * as msal from '@azure/msal-browser';
|
|
2
2
|
import { LogLevel } from '@azure/msal-browser';
|
|
3
|
+
import { PlainObject } from '@xh/hoist/core';
|
|
3
4
|
import { Token } from '@xh/hoist/security/Token';
|
|
4
|
-
import { AccessTokenSpec, TokenMap } from '../Types';
|
|
5
5
|
import { BaseOAuthClient, BaseOAuthClientConfig } from '../BaseOAuthClient';
|
|
6
|
+
import { AccessTokenSpec, TelemetryResults, TokenMap } from '../Types';
|
|
6
7
|
export interface MsalClientConfig extends BaseOAuthClientConfig<MsalTokenSpec> {
|
|
7
8
|
/**
|
|
8
9
|
* Authority for your organization's tenant: `https://login.microsoftonline.com/[tenantId]`.
|
|
@@ -16,6 +17,12 @@ export interface MsalClientConfig extends BaseOAuthClientConfig<MsalTokenSpec> {
|
|
|
16
17
|
* The value of the domain hint is a registered domain for the tenant.
|
|
17
18
|
*/
|
|
18
19
|
domainHint?: string;
|
|
20
|
+
/**
|
|
21
|
+
* True to enable support for built-in telemetry provided by this class's internal MSAL client.
|
|
22
|
+
* Captured performance events will be summarized via {@link telemetryResults}.
|
|
23
|
+
* See https://github.com/AzureAD/microsoft-authentication-library-for-js/blob/dev/lib/msal-browser/docs/performance.md
|
|
24
|
+
*/
|
|
25
|
+
enableTelemetry?: boolean;
|
|
19
26
|
/**
|
|
20
27
|
* If specified, the client will use this value when initializing the app to enforce a minimum
|
|
21
28
|
* amount of time during which no further auth flow with the provider should be necessary.
|
|
@@ -80,6 +87,9 @@ export declare class MsalClient extends BaseOAuthClient<MsalClientConfig, MsalTo
|
|
|
80
87
|
private client;
|
|
81
88
|
private account;
|
|
82
89
|
private initialTokenLoad;
|
|
90
|
+
/** Enable telemetry via `enableTelemetry` ctor config, or via {@link enableTelemetry}. */
|
|
91
|
+
telemetryResults: TelemetryResults;
|
|
92
|
+
private _telemetryCbHandle;
|
|
83
93
|
constructor(config: MsalClientConfig);
|
|
84
94
|
protected doInitAsync(): Promise<TokenMap>;
|
|
85
95
|
protected doLoginPopupAsync(): Promise<void>;
|
|
@@ -87,6 +97,9 @@ export declare class MsalClient extends BaseOAuthClient<MsalClientConfig, MsalTo
|
|
|
87
97
|
protected fetchIdTokenAsync(useCache?: boolean): Promise<Token>;
|
|
88
98
|
protected fetchAccessTokenAsync(spec: MsalTokenSpec, useCache?: boolean): Promise<Token>;
|
|
89
99
|
protected doLogoutAsync(): Promise<void>;
|
|
100
|
+
getFormattedTelemetry(): PlainObject;
|
|
101
|
+
enableTelemetry(): void;
|
|
102
|
+
disableTelemetry(): void;
|
|
90
103
|
private loginSsoAsync;
|
|
91
104
|
private createClientAsync;
|
|
92
105
|
private logFromMsal;
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { HoistService, PageState, PlainObject } from '@xh/hoist/core';
|
|
2
|
+
/**
|
|
3
|
+
* Service for gathering data about client health.
|
|
4
|
+
*
|
|
5
|
+
* Hoist sends this data once on application load, and can be configured to send
|
|
6
|
+
* it at regularly scheduled intervals. Configure via soft-config property
|
|
7
|
+
* 'xhActivityTracking.clientHealthReport'.
|
|
8
|
+
*/
|
|
9
|
+
export declare class ClientHealthService extends HoistService {
|
|
10
|
+
static instance: ClientHealthService;
|
|
11
|
+
private sources;
|
|
12
|
+
initAsync(): Promise<void>;
|
|
13
|
+
/**
|
|
14
|
+
* Main entry point. Return a default report of client health.
|
|
15
|
+
*/
|
|
16
|
+
getReport(): ClientHealthReport;
|
|
17
|
+
/** Get report, suitable for viewing in console. **/
|
|
18
|
+
getFormattedReport(): PlainObject;
|
|
19
|
+
/**
|
|
20
|
+
* Register a new source for client health report data. No-op if background health report is
|
|
21
|
+
* not generally enabled via `xhActivityTrackingConfig.clientHealthReport.intervalMins`.
|
|
22
|
+
*
|
|
23
|
+
* @param key - key under which to report the data - can be used to remove this source later.
|
|
24
|
+
* @param callback - function returning serializable to include with each report.
|
|
25
|
+
*/
|
|
26
|
+
addSource(key: string, callback: () => any): void;
|
|
27
|
+
/** Unregister a previously-enabled source for client health report data. */
|
|
28
|
+
removeSource(key: string): void;
|
|
29
|
+
getGeneral(): GeneralData;
|
|
30
|
+
getConnection(): ConnectionData;
|
|
31
|
+
getMemory(): MemoryData;
|
|
32
|
+
getCustom(): PlainObject;
|
|
33
|
+
private sendReport;
|
|
34
|
+
}
|
|
35
|
+
export interface GeneralData {
|
|
36
|
+
startTime: number;
|
|
37
|
+
durationMins: number;
|
|
38
|
+
idleMins: number;
|
|
39
|
+
pageState: PageState;
|
|
40
|
+
webSocket: string;
|
|
41
|
+
}
|
|
42
|
+
export interface ConnectionData {
|
|
43
|
+
downlink: number;
|
|
44
|
+
effectiveType: string;
|
|
45
|
+
rtt: number;
|
|
46
|
+
}
|
|
47
|
+
export interface MemoryData {
|
|
48
|
+
modelCount: number;
|
|
49
|
+
usedPctLimit?: number;
|
|
50
|
+
jsHeapSizeLimit?: number;
|
|
51
|
+
totalJSHeapSize?: number;
|
|
52
|
+
usedJSHeapSize?: number;
|
|
53
|
+
}
|
|
54
|
+
export interface ClientHealthReport {
|
|
55
|
+
general: GeneralData;
|
|
56
|
+
connection: ConnectionData;
|
|
57
|
+
memory: MemoryData;
|
|
58
|
+
}
|
|
@@ -9,7 +9,7 @@ export declare class TrackService extends HoistService {
|
|
|
9
9
|
private oncePerSessionSent;
|
|
10
10
|
private pending;
|
|
11
11
|
initAsync(): Promise<void>;
|
|
12
|
-
get conf():
|
|
12
|
+
get conf(): ActivityTrackingConfig;
|
|
13
13
|
get enabled(): boolean;
|
|
14
14
|
/** Track User Activity. */
|
|
15
15
|
track(options: TrackOptions | string): void;
|
|
@@ -18,3 +18,21 @@ export declare class TrackService extends HoistService {
|
|
|
18
18
|
private toServerJson;
|
|
19
19
|
private logMessage;
|
|
20
20
|
}
|
|
21
|
+
interface ActivityTrackingConfig {
|
|
22
|
+
clientHealthReport?: Partial<TrackOptions> & {
|
|
23
|
+
intervalMins: number;
|
|
24
|
+
};
|
|
25
|
+
enabled: boolean;
|
|
26
|
+
logData: boolean;
|
|
27
|
+
maxDataLength: number;
|
|
28
|
+
maxRows?: {
|
|
29
|
+
default: number;
|
|
30
|
+
options: number[];
|
|
31
|
+
};
|
|
32
|
+
levels?: Array<{
|
|
33
|
+
username: string | '*';
|
|
34
|
+
category: string | '*';
|
|
35
|
+
severity: 'DEBUG' | 'INFO' | 'WARN';
|
|
36
|
+
}>;
|
|
37
|
+
}
|
|
38
|
+
export {};
|
|
@@ -13,5 +13,6 @@ export * from './JsonBlobService';
|
|
|
13
13
|
export * from './PrefService';
|
|
14
14
|
export * from './TrackService';
|
|
15
15
|
export * from './WebSocketService';
|
|
16
|
+
export * from './ClientHealthService';
|
|
16
17
|
export * from './storage/LocalStorageService';
|
|
17
18
|
export * from './storage/SessionStorageService';
|
|
@@ -123,6 +123,12 @@ export interface ViewManagerConfig {
|
|
|
123
123
|
* Optional user-facing display name for describing global views. Defaults to 'global'
|
|
124
124
|
*/
|
|
125
125
|
globalDisplayName?: string;
|
|
126
|
+
|
|
127
|
+
/**
|
|
128
|
+
* Optional key to pass a method that returns a customized BlueprintJS `menuItem` for listing
|
|
129
|
+
* views in the ViewManager menu.
|
|
130
|
+
*/
|
|
131
|
+
viewMenuItemFn?: (view: ViewInfo, model: ViewManagerModel) => ReactNode;
|
|
126
132
|
}
|
|
127
133
|
|
|
128
134
|
/**
|
|
@@ -165,6 +171,7 @@ export class ViewManagerModel<T = PlainObject> extends HoistModel {
|
|
|
165
171
|
readonly instance: string;
|
|
166
172
|
readonly typeDisplayName: string;
|
|
167
173
|
readonly globalDisplayName: string;
|
|
174
|
+
readonly viewMenuItemFn: (view: ViewInfo, model: ViewManagerModel) => ReactNode;
|
|
168
175
|
readonly enableAutoSave: boolean;
|
|
169
176
|
readonly enableDefault: boolean;
|
|
170
177
|
readonly enableGlobal: boolean;
|
|
@@ -283,6 +290,7 @@ export class ViewManagerModel<T = PlainObject> extends HoistModel {
|
|
|
283
290
|
instance = 'default',
|
|
284
291
|
typeDisplayName,
|
|
285
292
|
globalDisplayName = 'global',
|
|
293
|
+
viewMenuItemFn,
|
|
286
294
|
manageGlobal = false,
|
|
287
295
|
enableAutoSave = true,
|
|
288
296
|
enableDefault = true,
|
|
@@ -296,13 +304,14 @@ export class ViewManagerModel<T = PlainObject> extends HoistModel {
|
|
|
296
304
|
|
|
297
305
|
throwIf(
|
|
298
306
|
!enableDefault && !initialViewSpec,
|
|
299
|
-
"ViewManagerModel requires 'initialViewSpec' if
|
|
307
|
+
"ViewManagerModel requires 'initialViewSpec' if 'enableDefault' is false."
|
|
300
308
|
);
|
|
301
309
|
|
|
302
310
|
this.type = type;
|
|
303
311
|
this.instance = instance;
|
|
304
312
|
this.typeDisplayName = lowerCase(typeDisplayName ?? genDisplayName(type));
|
|
305
313
|
this.globalDisplayName = globalDisplayName;
|
|
314
|
+
this.viewMenuItemFn = viewMenuItemFn;
|
|
306
315
|
this.manageGlobal = executeIfFunction(manageGlobal) ?? false;
|
|
307
316
|
this.enableDefault = enableDefault;
|
|
308
317
|
this.enableGlobal = enableGlobal;
|
package/core/XH.ts
CHANGED
|
@@ -28,7 +28,8 @@ import {
|
|
|
28
28
|
PrefService,
|
|
29
29
|
SessionStorageService,
|
|
30
30
|
TrackService,
|
|
31
|
-
WebSocketService
|
|
31
|
+
WebSocketService,
|
|
32
|
+
ClientHealthService
|
|
32
33
|
} from '@xh/hoist/svc';
|
|
33
34
|
import {camelCase, flatten, isString, uniqueId} from 'lodash';
|
|
34
35
|
import {Router, State} from 'router5';
|
|
@@ -64,6 +65,7 @@ import {
|
|
|
64
65
|
import {installServicesAsync} from './impl/InstallServices';
|
|
65
66
|
import {instanceManager} from './impl/InstanceManager';
|
|
66
67
|
import {HoistModel, ModelSelector, RefreshContextModel} from './model';
|
|
68
|
+
import ShortUniqueId from 'short-unique-id';
|
|
67
69
|
|
|
68
70
|
export const MIN_HOIST_CORE_VERSION = '28.0';
|
|
69
71
|
|
|
@@ -84,6 +86,15 @@ declare const xhIsDevelopmentMode: boolean;
|
|
|
84
86
|
* Available via import as `XH` - also installed as `window.XH` for troubleshooting purposes.
|
|
85
87
|
*/
|
|
86
88
|
export class XHApi {
|
|
89
|
+
/** Unique id for this loaded instance of the app. Unique for every refresh of document. */
|
|
90
|
+
clientId: string = this.genClientId();
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Unique id for this browser tab/window on this domain.
|
|
94
|
+
* Corresponds to the scope of the built-in sessionStorage object.
|
|
95
|
+
*/
|
|
96
|
+
sessionId: string = this.genSessionId();
|
|
97
|
+
|
|
87
98
|
//--------------------------
|
|
88
99
|
// Implementation Delegates
|
|
89
100
|
//--------------------------
|
|
@@ -131,6 +142,7 @@ export class XHApi {
|
|
|
131
142
|
alertBannerService: AlertBannerService;
|
|
132
143
|
autoRefreshService: AutoRefreshService;
|
|
133
144
|
changelogService: ChangelogService;
|
|
145
|
+
clientHealthService: ClientHealthService;
|
|
134
146
|
configService: ConfigService;
|
|
135
147
|
environmentService: EnvironmentService;
|
|
136
148
|
fetchService: FetchService;
|
|
@@ -794,6 +806,19 @@ export class XHApi {
|
|
|
794
806
|
private get acm(): AppContainerModel {
|
|
795
807
|
return this.appContainerModel;
|
|
796
808
|
}
|
|
809
|
+
|
|
810
|
+
private genClientId(): string {
|
|
811
|
+
return new ShortUniqueId({length: 8}).rnd();
|
|
812
|
+
}
|
|
813
|
+
|
|
814
|
+
private genSessionId(): string {
|
|
815
|
+
let ret = window.sessionStorage?.getItem('xhSessionId');
|
|
816
|
+
if (!ret) {
|
|
817
|
+
ret = new ShortUniqueId({length: 8}).rnd();
|
|
818
|
+
window.sessionStorage?.setItem('xhSessionId', ret);
|
|
819
|
+
}
|
|
820
|
+
return ret;
|
|
821
|
+
}
|
|
797
822
|
}
|
|
798
823
|
|
|
799
824
|
/** The app-wide singleton instance. */
|
package/data/Store.ts
CHANGED
|
@@ -18,6 +18,7 @@ import {
|
|
|
18
18
|
isEmpty,
|
|
19
19
|
isFunction,
|
|
20
20
|
isNil,
|
|
21
|
+
isNull,
|
|
21
22
|
isString,
|
|
22
23
|
values,
|
|
23
24
|
remove as lodashRemove,
|
|
@@ -692,6 +693,8 @@ export class Store extends HoistBase {
|
|
|
692
693
|
* for backwards compat with app code predating support for multiple {@link summaryRecords}.
|
|
693
694
|
*/
|
|
694
695
|
get summaryRecord(): StoreRecord {
|
|
696
|
+
if (isNull(this.summaryRecords)) return null;
|
|
697
|
+
|
|
695
698
|
throwIf(
|
|
696
699
|
this.summaryRecords.length > 1,
|
|
697
700
|
'Store has multiple summary records - must access via Store.summaryRecords.'
|