@xh/hoist 71.0.0-SNAPSHOT.1731971865033 → 71.0.0-SNAPSHOT.1732733427409

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.
@@ -32,6 +32,10 @@ export class ClusterTabModel extends HoistModel {
32
32
 
33
33
  @lookup(TabModel) private tabModel: TabModel;
34
34
 
35
+ @managed readonly gridModel: GridModel = this.createGridModel();
36
+ @managed readonly tabContainerModel: TabContainerModel = this.createTabContainerModel();
37
+ @managed readonly timer: Timer;
38
+
35
39
  shutdownAction: RecordActionSpec = {
36
40
  icon: Icon.skull(),
37
41
  text: 'Shutdown Instance',
@@ -41,10 +45,6 @@ export class ClusterTabModel extends HoistModel {
41
45
  recordsRequired: 1
42
46
  };
43
47
 
44
- @managed readonly gridModel: GridModel = this.createGridModel();
45
- @managed readonly tabContainerModel: TabContainerModel = this.createTabContainerModel();
46
- @managed readonly timer: Timer;
47
-
48
48
  get instance(): PlainObject {
49
49
  return this.gridModel.selectedRecord?.data;
50
50
  }
@@ -59,7 +59,16 @@ export class ClusterTabModel extends HoistModel {
59
59
 
60
60
  override async doLoadAsync(loadSpec: LoadSpec) {
61
61
  const {gridModel} = this;
62
- let data = await XH.fetchJson({url: 'clusterAdmin/allInstances', loadSpec});
62
+
63
+ let data = await XH.fetchJson({
64
+ url: 'clusterAdmin/allInstances',
65
+ // Tighter default timeout for background auto-refresh, to ensure we report connectivity
66
+ // issues promptly. This call should be quick, but still allow full default timeout for
67
+ // a manual refresh.
68
+ timeout: loadSpec.isAutoRefresh ? this.autoRefreshTimeout : undefined,
69
+ loadSpec
70
+ });
71
+
63
72
  data = data.map(row => ({
64
73
  ...row,
65
74
  isLocal: row.name == XH.environmentService.serverInstance,
@@ -78,7 +87,7 @@ export class ClusterTabModel extends HoistModel {
78
87
  runFn: () => {
79
88
  if (this.tabModel?.isActive) this.autoRefreshAsync();
80
89
  },
81
- interval: 5 * SECONDS,
90
+ interval: this.autoRefreshInterval,
82
91
  delay: true
83
92
  });
84
93
 
@@ -96,6 +105,16 @@ export class ClusterTabModel extends HoistModel {
96
105
  );
97
106
  }
98
107
 
108
+ formatInstance(instance: PlainObject): ReactNode {
109
+ const content = [instance.name];
110
+ if (instance.isPrimary) content.push(badge({item: 'primary', intent: 'primary'}));
111
+ if (instance.isLocal) content.push(badge('local'));
112
+ return hbox(content);
113
+ }
114
+
115
+ //------------------
116
+ // Implementation
117
+ //------------------
99
118
  private createGridModel() {
100
119
  return new GridModel({
101
120
  store: {
@@ -161,7 +180,7 @@ export class ClusterTabModel extends HoistModel {
161
180
  });
162
181
  }
163
182
 
164
- createTabContainerModel() {
183
+ private createTabContainerModel() {
165
184
  return new TabContainerModel({
166
185
  route: 'default.cluster',
167
186
  switcher: false,
@@ -187,14 +206,7 @@ export class ClusterTabModel extends HoistModel {
187
206
  });
188
207
  }
189
208
 
190
- formatInstance(instance: PlainObject): ReactNode {
191
- const content = [instance.name];
192
- if (instance.isPrimary) content.push(badge({item: 'primary', intent: 'primary'}));
193
- if (instance.isLocal) content.push(badge('local'));
194
- return hbox(content);
195
- }
196
-
197
- async shutdownInstanceAsync(instance: PlainObject) {
209
+ private async shutdownInstanceAsync(instance: PlainObject) {
198
210
  if (
199
211
  !(await XH.confirm({
200
212
  message: `Are you SURE you want to shutdown instance ${instance.name}?`,
@@ -216,4 +228,12 @@ export class ClusterTabModel extends HoistModel {
216
228
  .linkTo({observer: this.loadModel, message: 'Attempting instance shutdown'})
217
229
  .catchDefault();
218
230
  }
231
+
232
+ private get autoRefreshInterval(): number {
233
+ return XH.getConf('xhAdminAppConfig', {}).clusterTabAutoRefreshInterval ?? 4 * SECONDS;
234
+ }
235
+
236
+ private get autoRefreshTimeout(): number {
237
+ return XH.getConf('xhAdminAppConfig', {}).clusterTabAutoRefreshTimeout ?? 2 * SECONDS;
238
+ }
219
239
  }
@@ -186,7 +186,7 @@ export class DistributedObjectsModel extends BaseInstanceModel {
186
186
  ? className
187
187
  : className.startsWith('xh')
188
188
  ? 'Hoist'
189
- : obj.type == 'Cache'
189
+ : obj.type == 'Hibernate Cache'
190
190
  ? 'Hibernate'
191
191
  : 'Other';
192
192
 
@@ -9,17 +9,19 @@ export declare class ClusterTabModel extends HoistModel {
9
9
  localStorageKey: string;
10
10
  };
11
11
  private tabModel;
12
- shutdownAction: RecordActionSpec;
13
12
  readonly gridModel: GridModel;
14
13
  readonly tabContainerModel: TabContainerModel;
15
14
  readonly timer: Timer;
15
+ shutdownAction: RecordActionSpec;
16
16
  get instance(): PlainObject;
17
17
  get instanceName(): string;
18
18
  get isMultiInstance(): boolean;
19
19
  doLoadAsync(loadSpec: LoadSpec): Promise<void>;
20
20
  constructor();
21
- private createGridModel;
22
- createTabContainerModel(): TabContainerModel;
23
21
  formatInstance(instance: PlainObject): ReactNode;
24
- shutdownInstanceAsync(instance: PlainObject): Promise<void>;
22
+ private createGridModel;
23
+ private createTabContainerModel;
24
+ private shutdownInstanceAsync;
25
+ private get autoRefreshInterval();
26
+ private get autoRefreshTimeout();
25
27
  }
@@ -104,10 +104,7 @@ export declare class DashContainerModel extends DashModel<DashContainerViewSpec,
104
104
  * This method will clear the persistent state saved for this component, if any.
105
105
  */
106
106
  restoreDefaultsAsync(): Promise<void>;
107
- /**
108
- * Load state into the DashContainer, recreating its layout and contents
109
- * @param state - State to load
110
- */
107
+ /** Load state into the DashContainer, recreating its layout and contents */
111
108
  loadStateAsync(state: DashViewState[]): Promise<void>;
112
109
  /**
113
110
  * Add a view to the container.
@@ -214,8 +214,12 @@ export class DashContainerModel
214
214
  // Initialize GoldenLayout with initial state once ref is ready
215
215
  this.addReaction(
216
216
  {
217
- track: () => [this.containerRef.current, this.layoutLocked],
218
- run: () => this.loadStateAsync(this.state)
217
+ track: () => [this.containerRef.current, this.layoutLocked] as const,
218
+ run: ([ref, locked]) => {
219
+ // This reaction is intended to run when ref becomes available.
220
+ // It's a no-op if ref *removed* due to component re-render.
221
+ if (ref) this.loadStateAsync(this.state);
222
+ }
219
223
  },
220
224
  {
221
225
  track: () => this.viewState,
@@ -239,32 +243,36 @@ export class DashContainerModel
239
243
  await this.loadStateAsync(restoreState.initialState);
240
244
  }
241
245
 
242
- /**
243
- * Load state into the DashContainer, recreating its layout and contents
244
- * @param state - State to load
245
- */
246
+ /** Load state into the DashContainer, recreating its layout and contents */
246
247
  async loadStateAsync(state: DashViewState[]) {
247
- const containerEl = this.containerRef.current;
248
- if (!containerEl) {
249
- this.logWarn(
250
- 'DashboardContainer not yet rendered - cannot update state - change will be discarded!'
251
- );
252
- return;
253
- }
248
+ // Always save a reference to the state, even if the container is not yet rendered.
249
+ // Allows ref reaction on this class to loop back and apply it once GL is ready.
250
+ runInAction(() => (this.state = state));
254
251
 
255
- // Show mask to provide user feedback
256
- return wait()
257
- .thenAction(() => {
258
- this.destroyGoldenLayout();
259
- this.goldenLayout = this.createGoldenLayout(containerEl, state);
260
- })
261
- .wait(500)
262
- .then(() => {
252
+ // DOM required from this point on to recreate GL with new state.
253
+ const containerEl = this.containerRef.current;
254
+ if (!containerEl) return;
255
+
256
+ // Use of async below requires we check after each wait to ensure we haven't re-rendered
257
+ // again with a new ref. Bail out if so - ref reaction will re-enter and complete the job.
258
+ const refIsStale = () => this.containerRef.current !== containerEl;
259
+
260
+ return (
261
+ wait()
262
+ .thenAction(() => {
263
+ if (refIsStale()) return;
264
+ this.destroyGoldenLayout();
265
+ this.goldenLayout = this.createGoldenLayout(containerEl, state);
266
+ })
263
267
  // Since React v18, it's necessary to wait a short while for ViewModels to be available.
264
- this.refreshActiveViews();
265
- this.updateTabHeaders();
266
- })
267
- .linkTo(this.loadingStateTask);
268
+ .wait(500)
269
+ .then(() => {
270
+ if (refIsStale()) return;
271
+ this.refreshActiveViews();
272
+ this.updateTabHeaders();
273
+ })
274
+ .linkTo(this.loadingStateTask)
275
+ );
268
276
  }
269
277
 
270
278
  /**
@@ -343,13 +351,7 @@ export class DashContainerModel
343
351
 
344
352
  setPersistableState(persistableState: PersistableState<{state: DashViewState[]}>) {
345
353
  const {state} = persistableState.value;
346
- if (!state) return;
347
- if (this.containerRef.current) {
348
- this.loadStateAsync(state);
349
- } else {
350
- // If the container is not yet rendered, store the state directly
351
- this.state = state;
352
- }
354
+ if (state) this.loadStateAsync(state);
353
355
  }
354
356
 
355
357
  //------------------------
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@xh/hoist",
3
- "version": "71.0.0-SNAPSHOT.1731971865033",
3
+ "version": "71.0.0-SNAPSHOT.1732733427409",
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",