@xh/hoist 68.0.0-SNAPSHOT.1726224822379 → 68.0.0-SNAPSHOT.1726258981470

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.
@@ -154,7 +154,8 @@ export const instance: ColumnSpec = {
154
154
  field: {
155
155
  name: 'instance',
156
156
  type: 'string',
157
- displayName: 'Instance'
157
+ isDimension: true,
158
+ aggregator: 'UNIQUE'
158
159
  },
159
160
  width: 100
160
161
  };
@@ -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,20 +28,28 @@ 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.
@@ -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
  });
@@ -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
@@ -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;
@@ -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;
@@ -127,7 +127,7 @@ export class ExceptionHandler {
127
127
  ),
128
128
  actionButtonProps: {
129
129
  icon: Icon.search(),
130
- onClick: () => exceptionDialogModel.show(e, opts)
130
+ onClick: () => exceptionDialogModel.showDetails(e, opts)
131
131
  },
132
132
  intent: showAsError ? 'danger' : 'primary',
133
133
  ...ExceptionHandler.TOAST_PROPS
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xh/hoist",
3
- "version": "68.0.0-SNAPSHOT.1726224822379",
3
+ "version": "68.0.0-SNAPSHOT.1726258981470",
4
4
  "description": "Hoist add-on for building and deploying React Applications.",
5
5
  "repository": "github:xh/hoist-react",
6
6
  "homepage": "https://xh.io",