@xh/hoist 68.0.0-SNAPSHOT.1726237236739 → 68.0.0-SNAPSHOT.1726599173889

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.
Files changed (40) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/admin/columns/Tracking.ts +1 -2
  3. package/admin/tabs/cluster/BaseInstanceModel.ts +21 -6
  4. package/admin/tabs/cluster/ClusterTab.ts +40 -15
  5. package/admin/tabs/cluster/ClusterTabModel.ts +21 -21
  6. package/admin/tabs/cluster/connpool/ConnPoolMonitorPanel.ts +2 -1
  7. package/admin/tabs/cluster/environment/ServerEnvPanel.ts +2 -1
  8. package/admin/tabs/cluster/hzobject/HzObjectPanel.ts +2 -1
  9. package/admin/tabs/cluster/logs/LogDisplayModel.ts +18 -24
  10. package/admin/tabs/cluster/logs/LogViewer.ts +2 -2
  11. package/admin/tabs/cluster/logs/LogViewerModel.ts +0 -3
  12. package/admin/tabs/cluster/memory/MemoryMonitorPanel.ts +2 -1
  13. package/admin/tabs/cluster/services/DetailsModel.ts +0 -8
  14. package/admin/tabs/cluster/services/ServicePanel.ts +2 -1
  15. package/admin/tabs/cluster/websocket/WebSocketModel.ts +1 -7
  16. package/admin/tabs/general/alertBanner/AlertBannerModel.ts +23 -24
  17. package/admin/tabs/general/alertBanner/AlertBannerPanel.ts +3 -5
  18. package/build/types/admin/tabs/cluster/BaseInstanceModel.d.ts +4 -1
  19. package/build/types/admin/tabs/cluster/ClusterTab.d.ts +2 -0
  20. package/build/types/admin/tabs/cluster/ClusterTabModel.d.ts +3 -2
  21. package/build/types/admin/tabs/cluster/logs/LogViewerModel.d.ts +0 -2
  22. package/build/types/admin/tabs/cluster/services/DetailsModel.d.ts +0 -1
  23. package/build/types/admin/tabs/cluster/websocket/WebSocketModel.d.ts +0 -2
  24. package/build/types/admin/tabs/general/alertBanner/AlertBannerModel.d.ts +3 -3
  25. package/build/types/admin/tabs/general/config/ConfigPanelModel.d.ts +1 -1
  26. package/build/types/admin/tabs/userData/jsonblob/JsonBlobModel.d.ts +1 -1
  27. package/build/types/admin/tabs/userData/prefs/editor/PrefEditorModel.d.ts +1 -1
  28. package/build/types/admin/tabs/userData/users/UserModel.d.ts +1 -1
  29. package/build/types/desktop/cmp/rest/RestGridModel.d.ts +2 -2
  30. package/build/types/desktop/cmp/rest/impl/RestFormModel.d.ts +1 -1
  31. package/build/types/promise/Promise.d.ts +5 -5
  32. package/build/types/svc/AlertBannerService.d.ts +3 -7
  33. package/build/types/svc/EnvironmentService.d.ts +6 -2
  34. package/build/types/svc/IdentityService.d.ts +1 -1
  35. package/core/exception/ExceptionHandler.ts +1 -1
  36. package/package.json +1 -1
  37. package/promise/Promise.ts +11 -12
  38. package/svc/AlertBannerService.ts +6 -30
  39. package/svc/EnvironmentService.ts +29 -21
  40. package/tsconfig.tsbuildinfo +1 -1
package/CHANGELOG.md CHANGED
@@ -2,9 +2,19 @@
2
2
 
3
3
  ## 68.0.0-SNAPSHOT - unreleased
4
4
 
5
+ ### 💥 Breaking Changes (upgrade difficulty: 🟢 LOW - Hoist Core update only)
6
+
7
+ * Requires `hoist-core >= 21.1` for consolidated polling of Alert Banner updates (see below).
8
+
5
9
  ### ⚙️ Technical
6
10
 
7
11
  * Updated Admin Console's Cluster tab to refresh more frequently.
12
+ * Consolidated the polling check for Alert Banner updates into existing `EnvironmentService`
13
+ polling, avoiding an extra request and improving alert banner responsiveness.
14
+
15
+ ### ⚙️ Typescript API Adjustments
16
+
17
+ * Corrected types of enhanced `Promise` methods.
8
18
 
9
19
  ## 67.0.0 - 2024-09-03
10
20
 
@@ -119,8 +119,7 @@ export const correlationId: ColumnSpec = {
119
119
  export const error: ColumnSpec = {
120
120
  field: {
121
121
  name: 'error',
122
- type: 'string',
123
- displayName: 'Error Details'
122
+ type: 'string'
124
123
  },
125
124
  flex: true,
126
125
  minWidth: 150,
@@ -9,8 +9,12 @@ import {HoistModel, LoadSpec, lookup, PlainObject, XH} from '@xh/hoist/core';
9
9
  import {fmtDateTimeSec, fmtJson} from '@xh/hoist/format';
10
10
  import {DAYS} from '@xh/hoist/utils/datetime';
11
11
  import {cloneDeep, forOwn, isArray, isNumber, isPlainObject} from 'lodash';
12
+ import {createRef} from 'react';
13
+ import {isDisplayed} from '@xh/hoist/utils/js';
12
14
 
13
15
  export class BaseInstanceModel extends HoistModel {
16
+ viewRef = createRef<HTMLElement>();
17
+
14
18
  @lookup(() => ClusterTabModel) parent: ClusterTabModel;
15
19
 
16
20
  get instanceName(): string {
@@ -24,25 +28,36 @@ export class BaseInstanceModel extends HoistModel {
24
28
  }
25
29
 
26
30
  handleLoadException(e: unknown, loadSpec: LoadSpec) {
27
- const instanceNotFound = this.isInstanceNotFound(e);
31
+ const instanceNotFound = this.isInstanceNotFound(e),
32
+ connDown = this.parent.lastLoadException,
33
+ {isVisible} = this,
34
+ {isAutoRefresh} = loadSpec;
28
35
  XH.handleException(e, {
29
- showAlert: !loadSpec.isAutoRefresh && !instanceNotFound,
30
- logOnServer: !instanceNotFound
36
+ alertType: 'toast',
37
+ showAlert: !instanceNotFound && !connDown && isVisible,
38
+ logOnServer: !instanceNotFound && !connDown && isVisible && !isAutoRefresh
31
39
  });
32
40
  }
33
41
 
34
- isInstanceNotFound(e: unknown): boolean {
35
- return e['name'] == 'InstanceNotFoundException';
42
+ get isVisible() {
43
+ return isDisplayed(this.viewRef.current);
36
44
  }
37
45
 
38
46
  //-------------------
39
47
  // Implementation
40
48
  //-------------------
49
+ private isInstanceNotFound(e: unknown): boolean {
50
+ return e['name'] == 'InstanceNotFoundException';
51
+ }
52
+
41
53
  private processTimestamps(stats: PlainObject) {
42
54
  forOwn(stats, (v, k) => {
43
55
  // Convert numbers that look like recent timestamps to date values.
44
56
  if (
45
- (k.endsWith('Time') || k.endsWith('Date') || k == 'timestamp') &&
57
+ (k.endsWith('Time') ||
58
+ k.endsWith('Date') ||
59
+ k.endsWith('Timestamp') ||
60
+ k == 'timestamp') &&
46
61
  isNumber(v) &&
47
62
  v > Date.now() - 365 * DAYS
48
63
  ) {
@@ -10,14 +10,13 @@ import {creates, hoistCmp} from '@xh/hoist/core';
10
10
  import {mask} from '@xh/hoist/desktop/cmp/mask';
11
11
  import {panel} from '@xh/hoist/desktop/cmp/panel';
12
12
  import {tabSwitcher} from '@xh/hoist/desktop/cmp/tab';
13
- import {box, hspacer, placeholder, vframe} from '@xh/hoist/cmp/layout';
13
+ import {box, div, hspacer, p, placeholder, vframe} from '@xh/hoist/cmp/layout';
14
14
  import {ClusterTabModel} from './ClusterTabModel';
15
15
  import {Icon} from '@xh/hoist/icon';
16
16
 
17
17
  export const clusterTab = hoistCmp.factory({
18
18
  model: creates(ClusterTabModel),
19
19
  render({model}) {
20
- const {instance} = model;
21
20
  return vframe(
22
21
  panel({
23
22
  modelConfig: {
@@ -29,19 +28,45 @@ export const clusterTab = hoistCmp.factory({
29
28
  },
30
29
  item: grid()
31
30
  }),
32
- instance?.isReady
33
- ? panel({
34
- compactHeader: true,
35
- tbar: [
36
- box({width: 150, item: model.formatInstance(instance)}),
37
- hspacer(25),
38
- tabSwitcher()
39
- ],
40
- flex: 1,
41
- item: tabContainer()
42
- })
43
- : placeholder(Icon.server(), 'Select a running instance above.'),
44
- mask({bind: model.loadModel})
31
+ detailPanel(),
32
+ failedConnectionMask()
45
33
  );
46
34
  }
47
35
  });
36
+
37
+ export const detailPanel = hoistCmp.factory<ClusterTabModel>({
38
+ render({model}) {
39
+ const {instance, lastLoadException} = model;
40
+ if (!instance?.isReady) {
41
+ return placeholder({
42
+ items: [Icon.server(), 'Select a running instance above.'],
43
+ omit: lastLoadException
44
+ });
45
+ }
46
+
47
+ return panel({
48
+ compactHeader: true,
49
+ tbar: [
50
+ box({width: 150, item: model.formatInstance(instance)}),
51
+ hspacer(25),
52
+ tabSwitcher()
53
+ ],
54
+ flex: 1,
55
+ item: tabContainer()
56
+ });
57
+ }
58
+ });
59
+
60
+ export const failedConnectionMask = hoistCmp.factory<ClusterTabModel>({
61
+ render({model}) {
62
+ return mask({
63
+ message: div(
64
+ p('Attempting to connect to cluster.'),
65
+ p('Local instance may be unavailable, please wait.')
66
+ ),
67
+ isDisplayed: true,
68
+ spinner: true,
69
+ omit: !model.lastLoadException
70
+ });
71
+ }
72
+ });
@@ -18,10 +18,11 @@ import {badge} from '@xh/hoist/cmp/badge';
18
18
  import {GridModel, numberCol} from '@xh/hoist/cmp/grid';
19
19
  import {hbox} from '@xh/hoist/cmp/layout';
20
20
  import {getRelativeTimestamp} from '@xh/hoist/cmp/relativetimestamp';
21
- import {TabContainerModel} from '@xh/hoist/cmp/tab';
22
- import {HoistModel, LoadSpec, managed, PlainObject, XH} from '@xh/hoist/core';
21
+ import {TabContainerModel, TabModel} from '@xh/hoist/cmp/tab';
22
+ import {HoistModel, LoadSpec, lookup, managed, PlainObject, XH} from '@xh/hoist/core';
23
23
  import {RecordActionSpec} from '@xh/hoist/data';
24
24
  import {Icon} from '@xh/hoist/icon';
25
+ import {makeObservable} from '@xh/hoist/mobx';
25
26
  import {Timer} from '@xh/hoist/utils/async';
26
27
  import {SECONDS} from '@xh/hoist/utils/datetime';
27
28
  import {ReactNode} from 'react';
@@ -29,6 +30,8 @@ import {ReactNode} from 'react';
29
30
  export class ClusterTabModel extends HoistModel {
30
31
  override persistWith = {localStorageKey: 'xhAdminClusterTabState'};
31
32
 
33
+ @lookup(TabModel) private tabModel: TabModel;
34
+
32
35
  shutdownAction: RecordActionSpec = {
33
36
  icon: Icon.skull(),
34
37
  text: 'Shutdown Instance',
@@ -39,7 +42,7 @@ export class ClusterTabModel extends HoistModel {
39
42
  };
40
43
 
41
44
  @managed readonly gridModel: GridModel = this.createGridModel();
42
- @managed readonly tabModel: TabContainerModel = this.createTabModel();
45
+ @managed readonly tabContainerModel: TabContainerModel = this.createTabContainerModel();
43
46
  @managed readonly timer: Timer;
44
47
 
45
48
  get instance(): PlainObject {
@@ -56,28 +59,25 @@ export class ClusterTabModel extends HoistModel {
56
59
 
57
60
  override async doLoadAsync(loadSpec: LoadSpec) {
58
61
  const {gridModel} = this;
59
- try {
60
- let data = await XH.fetchJson({url: 'clusterAdmin/allInstances', loadSpec});
61
- data = data.map(row => ({
62
- ...row,
63
- isLocal: row.name == XH.environmentService.serverInstance,
64
- usedHeapMb: row.memory?.usedHeapMb,
65
- usedPctMax: row.memory?.usedPctMax
66
- }));
67
-
68
- gridModel.loadData(data);
69
- await gridModel.preSelectFirstAsync();
70
- } catch (e) {
71
- gridModel.clear();
72
- XH.handleException(e);
73
- }
62
+ let data = await XH.fetchJson({url: 'clusterAdmin/allInstances', loadSpec});
63
+ data = data.map(row => ({
64
+ ...row,
65
+ isLocal: row.name == XH.environmentService.serverInstance,
66
+ usedHeapMb: row.memory?.usedHeapMb,
67
+ usedPctMax: row.memory?.usedPctMax
68
+ }));
69
+ gridModel.loadData(data);
70
+ await gridModel.preSelectFirstAsync();
74
71
  }
75
72
 
76
73
  constructor() {
77
74
  super();
75
+ makeObservable(this);
78
76
 
79
77
  this.timer = Timer.create({
80
- runFn: this.autoRefreshAsync,
78
+ runFn: () => {
79
+ if (this.tabModel?.isActive) this.autoRefreshAsync();
80
+ },
81
81
  interval: 5 * SECONDS,
82
82
  delay: true
83
83
  });
@@ -86,7 +86,7 @@ export class ClusterTabModel extends HoistModel {
86
86
  {
87
87
  track: () => this.instanceName,
88
88
  run: instName => {
89
- if (instName) this.tabModel.refreshContextModel.refreshAsync();
89
+ if (instName) this.tabContainerModel.refreshContextModel.refreshAsync();
90
90
  }
91
91
  },
92
92
  {
@@ -161,7 +161,7 @@ export class ClusterTabModel extends HoistModel {
161
161
  });
162
162
  }
163
163
 
164
- createTabModel() {
164
+ createTabContainerModel() {
165
165
  return new TabContainerModel({
166
166
  route: 'default.cluster',
167
167
  switcher: false,
@@ -60,7 +60,8 @@ export const connPoolMonitorPanel = hoistCmp.factory({
60
60
  ),
61
61
  poolConfigPanel()
62
62
  ),
63
- mask: 'onLoad'
63
+ mask: 'onLoad',
64
+ ref: model.viewRef
64
65
  });
65
66
  }
66
67
  });
@@ -28,7 +28,8 @@ export const serverEnvPanel = hoistCmp.factory({
28
28
  exportButton()
29
29
  ],
30
30
  item: lastLoadException ? errorMessage({error: lastLoadException}) : grid(),
31
- mask: 'onLoad'
31
+ mask: 'onLoad',
32
+ ref: model.viewRef
32
33
  });
33
34
  }
34
35
  });
@@ -40,7 +40,8 @@ export const hzObjectPanel = hoistCmp.factory({
40
40
  exportButton()
41
41
  ],
42
42
  item: hframe(grid(), detailsPanel()),
43
- mask: 'onLoad'
43
+ mask: 'onLoad',
44
+ ref: model.viewRef
44
45
  });
45
46
  }
46
47
  });
@@ -11,7 +11,7 @@ import {Icon} from '@xh/hoist/icon';
11
11
  import {bindable, makeObservable} from '@xh/hoist/mobx';
12
12
  import {Timer} from '@xh/hoist/utils/async';
13
13
  import {olderThan, ONE_SECOND, SECONDS} from '@xh/hoist/utils/datetime';
14
- import {debounced, isDisplayed} from '@xh/hoist/utils/js';
14
+ import {debounced} from '@xh/hoist/utils/js';
15
15
  import {escapeRegExp, maxBy} from 'lodash';
16
16
  import {LogViewerModel} from './LogViewerModel';
17
17
 
@@ -105,26 +105,21 @@ export class LogDisplayModel extends HoistModel {
105
105
  return;
106
106
  }
107
107
 
108
- try {
109
- const response = await XH.fetchJson({
110
- url: 'logViewerAdmin/getFile',
111
- params: {
112
- filename: parent.file,
113
- startLine: this.startLine,
114
- maxLines: this.maxLines,
115
- pattern: this.regexOption ? this.pattern : escapeRegExp(this.pattern),
116
- caseSensitive: this.caseSensitive,
117
- instance: parent.instanceName
118
- },
119
- loadSpec
120
- });
121
- if (!response.success) throw XH.exception(response.exception);
122
- this.updateGridData(response.content);
123
- } catch (e) {
124
- // Show errors inline in the viewer vs. a modal alert or catchDefault().
125
- const msg = e.message || 'An unknown error occurred';
126
- this.updateGridData([[0, `Error: ${msg}`]]);
127
- }
108
+ const response = await XH.fetchJson({
109
+ url: 'logViewerAdmin/getFile',
110
+ params: {
111
+ filename: parent.file,
112
+ startLine: this.startLine,
113
+ maxLines: this.maxLines,
114
+ pattern: this.regexOption ? this.pattern : escapeRegExp(this.pattern),
115
+ caseSensitive: this.caseSensitive,
116
+ instance: parent.instanceName
117
+ },
118
+ loadSpec
119
+ });
120
+ // Backward compatibility for Hoist Core < v22, which returned exception in-band
121
+ if (!response.success) throw XH.exception(response.exception);
122
+ this.updateGridData(response.content);
128
123
  }
129
124
 
130
125
  async scrollToTail() {
@@ -209,14 +204,13 @@ export class LogDisplayModel extends HoistModel {
209
204
  }
210
205
 
211
206
  private autoRefreshLines() {
212
- const {tailActive, parent} = this,
213
- {viewRef} = parent;
207
+ const {tailActive} = this;
214
208
 
215
209
  if (
216
210
  tailActive &&
217
211
  olderThan(this.lastLoadCompleted, 5 * SECONDS) &&
218
212
  !this.loadModel.isPending &&
219
- isDisplayed(viewRef.current)
213
+ this.parent.isVisible
220
214
  ) {
221
215
  this.loadLog();
222
216
  }
@@ -29,7 +29,6 @@ export const logViewer = hoistCmp.factory({
29
29
 
30
30
  return hframe({
31
31
  className,
32
- ref: model.viewRef,
33
32
  items: [
34
33
  panel({
35
34
  collapsedTitle: 'Log Files',
@@ -57,7 +56,8 @@ export const logViewer = hoistCmp.factory({
57
56
  }),
58
57
  logDisplay(),
59
58
  model.showLogLevelDialog ? logLevelDialog() : null
60
- ]
59
+ ],
60
+ ref: model.viewRef
61
61
  });
62
62
  }
63
63
  });
@@ -14,7 +14,6 @@ import {compactDateRenderer, fmtNumber} from '@xh/hoist/format';
14
14
  import {Icon} from '@xh/hoist/icon';
15
15
  import {bindable, makeObservable, observable} from '@xh/hoist/mobx';
16
16
  import download from 'downloadjs';
17
- import {createRef} from 'react';
18
17
  import {LogDisplayModel} from './LogDisplayModel';
19
18
 
20
19
  /**
@@ -23,8 +22,6 @@ import {LogDisplayModel} from './LogDisplayModel';
23
22
  export class LogViewerModel extends BaseInstanceModel {
24
23
  @observable file: string = null;
25
24
 
26
- viewRef = createRef<HTMLElement>();
27
-
28
25
  @managed
29
26
  logDisplayModel = new LogDisplayModel(this);
30
27
 
@@ -69,7 +69,8 @@ export const memoryMonitorPanel = hoistCmp.factory({
69
69
  item: chart()
70
70
  })
71
71
  ],
72
- mask: 'onLoad'
72
+ mask: 'onLoad',
73
+ ref: model.viewRef
73
74
  });
74
75
  }
75
76
  });
@@ -48,14 +48,6 @@ export class DetailsModel extends HoistModel {
48
48
  loadSpec
49
49
  });
50
50
  if (loadSpec.isStale) return;
51
- this.preprocessRawData(resp);
52
51
  this.stats = resp;
53
52
  }
54
-
55
- private preprocessRawData(resp) {
56
- // Format distributed objects for readability
57
- resp.distributedObjects?.forEach(obj => {
58
- obj.name = obj.name.substring(obj.name.indexOf('_') + 1);
59
- });
60
- }
61
53
  }
@@ -41,7 +41,8 @@ export const servicePanel = hoistCmp.factory({
41
41
  }),
42
42
  detailsPanel()
43
43
  ),
44
- mask: 'onLoad'
44
+ mask: 'onLoad',
45
+ ref: model.viewRef
45
46
  });
46
47
  }
47
48
  });
@@ -15,16 +15,12 @@ import {Icon} from '@xh/hoist/icon';
15
15
  import {makeObservable, observable, runInAction} from '@xh/hoist/mobx';
16
16
  import {Timer} from '@xh/hoist/utils/async';
17
17
  import {SECONDS} from '@xh/hoist/utils/datetime';
18
- import {isDisplayed} from '@xh/hoist/utils/js';
19
18
  import {isEmpty} from 'lodash';
20
- import {createRef} from 'react';
21
19
  import * as WSCol from './WebSocketColumns';
22
20
  import {RecordActionSpec} from '@xh/hoist/data';
23
21
  import {AppModel} from '@xh/hoist/admin/AppModel';
24
22
 
25
23
  export class WebSocketModel extends BaseInstanceModel {
26
- viewRef = createRef<HTMLElement>();
27
-
28
24
  @observable
29
25
  lastRefresh: number;
30
26
 
@@ -87,9 +83,7 @@ export class WebSocketModel extends BaseInstanceModel {
87
83
 
88
84
  this._timer = Timer.create({
89
85
  runFn: () => {
90
- if (isDisplayed(this.viewRef.current)) {
91
- this.autoRefreshAsync();
92
- }
86
+ if (this.isVisible) this.autoRefreshAsync();
93
87
  },
94
88
  interval: 5 * SECONDS,
95
89
  delay: true
@@ -10,13 +10,13 @@ import {FormModel} from '@xh/hoist/cmp/form';
10
10
  import {fragment, p} from '@xh/hoist/cmp/layout';
11
11
  import {HoistModel, Intent, LoadSpec, managed, PlainObject, XH} from '@xh/hoist/core';
12
12
  import {dateIs, required} from '@xh/hoist/data';
13
- import {action, computed, makeObservable, observable} from '@xh/hoist/mobx';
13
+ import {action, bindable, computed, makeObservable, observable} from '@xh/hoist/mobx';
14
14
  import {AlertBannerSpec} from '@xh/hoist/svc';
15
15
  import {isEqual, isMatch, sortBy, without} from 'lodash';
16
16
 
17
17
  export class AlertBannerModel extends HoistModel {
18
- savedValue;
19
- @observable.ref savedPresets: PlainObject[] = [];
18
+ savedValue: AlertBannerSpec;
19
+ @bindable.ref savedPresets: PlainObject[] = [];
20
20
 
21
21
  @managed
22
22
  formModel = new FormModel({
@@ -118,7 +118,6 @@ export class AlertBannerModel extends HoistModel {
118
118
  this.formModel.setValues({...preset, expires: null});
119
119
  }
120
120
 
121
- @action
122
121
  addPreset() {
123
122
  const {message, intent, iconName, enableClose, clientApps} = this.formModel.values,
124
123
  dateCreated = Date.now(),
@@ -188,24 +187,6 @@ export class AlertBannerModel extends HoistModel {
188
187
  }
189
188
  }
190
189
 
191
- async saveBannerSpecAsync(spec: AlertBannerSpec) {
192
- const {active, message, intent, iconName, enableClose, clientApps} = spec;
193
- try {
194
- await XH.fetchService.postJson({
195
- url: 'alertBannerAdmin/setAlertSpec',
196
- body: spec,
197
- track: {
198
- category: 'Audit',
199
- message: 'Updated Alert Banner',
200
- data: {active, message, intent, iconName, enableClose, clientApps},
201
- logData: ['active']
202
- }
203
- });
204
- } catch (e) {
205
- XH.handleException(e);
206
- }
207
- }
208
-
209
190
  //----------------
210
191
  // Implementation
211
192
  //----------------
@@ -232,7 +213,7 @@ export class AlertBannerModel extends HoistModel {
232
213
  let preservedPublishDate = null;
233
214
 
234
215
  // Ask some questions if we are dealing with live stuff
235
- if (XH.alertBannerService.enabled && (active || savedValue?.active)) {
216
+ if (active || savedValue?.active) {
236
217
  // Question 1. Reshow when modifying an active && already active, closable banner?
237
218
  if (
238
219
  active &&
@@ -299,7 +280,25 @@ export class AlertBannerModel extends HoistModel {
299
280
  };
300
281
 
301
282
  await this.saveBannerSpecAsync(value);
302
- await XH.alertBannerService.checkForBannerAsync();
283
+ await XH.environmentService.pollServerAsync();
303
284
  await this.refreshAsync();
304
285
  }
286
+
287
+ private async saveBannerSpecAsync(spec: AlertBannerSpec) {
288
+ const {active, message, intent, iconName, enableClose, clientApps} = spec;
289
+ try {
290
+ await XH.fetchService.postJson({
291
+ url: 'alertBannerAdmin/setAlertSpec',
292
+ body: spec,
293
+ track: {
294
+ category: 'Audit',
295
+ message: 'Updated Alert Banner',
296
+ data: {active, message, intent, iconName, enableClose, clientApps},
297
+ logData: ['active']
298
+ }
299
+ });
300
+ } catch (e) {
301
+ XH.handleException(e);
302
+ }
303
+ }
305
304
  }
@@ -37,7 +37,7 @@ import {toolbar} from '@xh/hoist/desktop/cmp/toolbar';
37
37
  import {dateTimeRenderer} from '@xh/hoist/format';
38
38
  import {Icon} from '@xh/hoist/icon';
39
39
  import {menu, menuItem, popover} from '@xh/hoist/kit/blueprint';
40
- import {LocalDate, SECONDS} from '@xh/hoist/utils/datetime';
40
+ import {LocalDate} from '@xh/hoist/utils/datetime';
41
41
  import {isEmpty} from 'lodash';
42
42
  import {ReactNode} from 'react';
43
43
  import {AlertBannerModel} from './AlertBannerModel';
@@ -72,15 +72,13 @@ const formPanel = hoistCmp.factory<AlertBannerModel>(({model}) => {
72
72
  labelWidth: 100
73
73
  },
74
74
  items: [
75
- XH.alertBannerService.enabled
75
+ XH.getConf('xhAlertBannerConfig', {}).enabled
76
76
  ? div({
77
77
  className: `${baseClassName}__intro`,
78
78
  items: [
79
79
  p(`Show an alert banner to all ${XH.appName} users.`),
80
80
  p(
81
- `Configure and preview below. Presets can be saved and loaded via bottom bar menu. Banner will appear to all users within ${
82
- XH.alertBannerService.interval / SECONDS
83
- }s once marked Active and saved.`
81
+ 'Configure and preview below. Presets can be saved and loaded via bottom bar menu. Banner will appear to all users once marked Active and saved.'
84
82
  )
85
83
  ]
86
84
  })
@@ -1,10 +1,13 @@
1
+ /// <reference types="react" />
1
2
  import { ClusterTabModel } from '@xh/hoist/admin/tabs/cluster/ClusterTabModel';
2
3
  import { HoistModel, LoadSpec, PlainObject } from '@xh/hoist/core';
3
4
  export declare class BaseInstanceModel extends HoistModel {
5
+ viewRef: import("react").RefObject<HTMLElement>;
4
6
  parent: ClusterTabModel;
5
7
  get instanceName(): string;
6
8
  fmtStats(stats: PlainObject): string;
7
9
  handleLoadException(e: unknown, loadSpec: LoadSpec): void;
8
- isInstanceNotFound(e: unknown): boolean;
10
+ get isVisible(): boolean;
11
+ private isInstanceNotFound;
9
12
  private processTimestamps;
10
13
  }
@@ -1,2 +1,4 @@
1
1
  import { ClusterTabModel } from './ClusterTabModel';
2
2
  export declare const clusterTab: import("@xh/hoist/core").ElementFactory<import("@xh/hoist/core").DefaultHoistProps<ClusterTabModel>>;
3
+ export declare const detailPanel: import("@xh/hoist/core").ElementFactory<import("@xh/hoist/core").DefaultHoistProps<ClusterTabModel>>;
4
+ export declare const failedConnectionMask: import("@xh/hoist/core").ElementFactory<import("@xh/hoist/core").DefaultHoistProps<ClusterTabModel>>;
@@ -8,9 +8,10 @@ export declare class ClusterTabModel extends HoistModel {
8
8
  persistWith: {
9
9
  localStorageKey: string;
10
10
  };
11
+ private tabModel;
11
12
  shutdownAction: RecordActionSpec;
12
13
  readonly gridModel: GridModel;
13
- readonly tabModel: TabContainerModel;
14
+ readonly tabContainerModel: TabContainerModel;
14
15
  readonly timer: Timer;
15
16
  get instance(): PlainObject;
16
17
  get instanceName(): string;
@@ -18,7 +19,7 @@ export declare class ClusterTabModel extends HoistModel {
18
19
  doLoadAsync(loadSpec: LoadSpec): Promise<void>;
19
20
  constructor();
20
21
  private createGridModel;
21
- createTabModel(): TabContainerModel;
22
+ createTabContainerModel(): TabContainerModel;
22
23
  formatInstance(instance: PlainObject): ReactNode;
23
24
  shutdownInstanceAsync(instance: PlainObject): Promise<void>;
24
25
  }
@@ -1,4 +1,3 @@
1
- /// <reference types="react" />
2
1
  import { BaseInstanceModel } from '@xh/hoist/admin/tabs/cluster/BaseInstanceModel';
3
2
  import { GridModel } from '@xh/hoist/cmp/grid';
4
3
  import { LoadSpec } from '@xh/hoist/core';
@@ -9,7 +8,6 @@ import { LogDisplayModel } from './LogDisplayModel';
9
8
  */
10
9
  export declare class LogViewerModel extends BaseInstanceModel {
11
10
  file: string;
12
- viewRef: import("react").RefObject<HTMLElement>;
13
11
  logDisplayModel: LogDisplayModel;
14
12
  filesGridModel: GridModel;
15
13
  instanceOnly: boolean;
@@ -8,5 +8,4 @@ export declare class DetailsModel extends HoistModel {
8
8
  get selectedRecord(): StoreRecord;
9
9
  onLinked(): void;
10
10
  doLoadAsync(loadSpec: LoadSpec): Promise<void>;
11
- private preprocessRawData;
12
11
  }
@@ -1,10 +1,8 @@
1
- /// <reference types="react" />
2
1
  import { BaseInstanceModel } from '@xh/hoist/admin/tabs/cluster/BaseInstanceModel';
3
2
  import { GridModel } from '@xh/hoist/cmp/grid';
4
3
  import { LoadSpec } from '@xh/hoist/core';
5
4
  import { RecordActionSpec } from '@xh/hoist/data';
6
5
  export declare class WebSocketModel extends BaseInstanceModel {
7
- viewRef: import("react").RefObject<HTMLElement>;
8
6
  lastRefresh: number;
9
7
  gridModel: GridModel;
10
8
  private _timer;