Skip to content

Commit

Permalink
Improve Exception/Connectivity handling with Cluster Tab (#3785)
Browse files Browse the repository at this point in the history
  • Loading branch information
lbwexler authored Sep 13, 2024
1 parent 91c2345 commit 03fbd74
Show file tree
Hide file tree
Showing 13 changed files with 110 additions and 83 deletions.
22 changes: 17 additions & 5 deletions admin/tabs/cluster/BaseInstanceModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,12 @@ import {HoistModel, LoadSpec, lookup, PlainObject, XH} from '@xh/hoist/core';
import {fmtDateTimeSec, fmtJson} from '@xh/hoist/format';
import {DAYS} from '@xh/hoist/utils/datetime';
import {cloneDeep, forOwn, isArray, isNumber, isPlainObject} from 'lodash';
import {createRef} from 'react';
import {isDisplayed} from '@xh/hoist/utils/js';

export class BaseInstanceModel extends HoistModel {
viewRef = createRef<HTMLElement>();

@lookup(() => ClusterTabModel) parent: ClusterTabModel;

get instanceName(): string {
Expand All @@ -24,20 +28,28 @@ export class BaseInstanceModel extends HoistModel {
}

handleLoadException(e: unknown, loadSpec: LoadSpec) {
const instanceNotFound = this.isInstanceNotFound(e);
const instanceNotFound = this.isInstanceNotFound(e),
connDown = this.parent.lastLoadException,
{isVisible} = this,
{isAutoRefresh} = loadSpec;
XH.handleException(e, {
showAlert: !loadSpec.isAutoRefresh && !instanceNotFound,
logOnServer: !instanceNotFound
alertType: 'toast',
showAlert: !instanceNotFound && !connDown && isVisible,
logOnServer: !instanceNotFound && !connDown && isVisible && !isAutoRefresh
});
}

isInstanceNotFound(e: unknown): boolean {
return e['name'] == 'InstanceNotFoundException';
get isVisible() {
return isDisplayed(this.viewRef.current);
}

//-------------------
// Implementation
//-------------------
private isInstanceNotFound(e: unknown): boolean {
return e['name'] == 'InstanceNotFoundException';
}

private processTimestamps(stats: PlainObject) {
forOwn(stats, (v, k) => {
// Convert numbers that look like recent timestamps to date values.
Expand Down
55 changes: 40 additions & 15 deletions admin/tabs/cluster/ClusterTab.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,13 @@ import {creates, hoistCmp} from '@xh/hoist/core';
import {mask} from '@xh/hoist/desktop/cmp/mask';
import {panel} from '@xh/hoist/desktop/cmp/panel';
import {tabSwitcher} from '@xh/hoist/desktop/cmp/tab';
import {box, hspacer, placeholder, vframe} from '@xh/hoist/cmp/layout';
import {box, div, hspacer, p, placeholder, vframe} from '@xh/hoist/cmp/layout';
import {ClusterTabModel} from './ClusterTabModel';
import {Icon} from '@xh/hoist/icon';

export const clusterTab = hoistCmp.factory({
model: creates(ClusterTabModel),
render({model}) {
const {instance} = model;
return vframe(
panel({
modelConfig: {
Expand All @@ -29,19 +28,45 @@ export const clusterTab = hoistCmp.factory({
},
item: grid()
}),
instance?.isReady
? panel({
compactHeader: true,
tbar: [
box({width: 150, item: model.formatInstance(instance)}),
hspacer(25),
tabSwitcher()
],
flex: 1,
item: tabContainer()
})
: placeholder(Icon.server(), 'Select a running instance above.'),
mask({bind: model.loadModel})
detailPanel(),
failedConnectionMask()
);
}
});

export const detailPanel = hoistCmp.factory<ClusterTabModel>({
render({model}) {
const {instance, lastLoadException} = model;
if (!instance?.isReady) {
return placeholder({
items: [Icon.server(), 'Select a running instance above.'],
omit: lastLoadException
});
}

return panel({
compactHeader: true,
tbar: [
box({width: 150, item: model.formatInstance(instance)}),
hspacer(25),
tabSwitcher()
],
flex: 1,
item: tabContainer()
});
}
});

export const failedConnectionMask = hoistCmp.factory<ClusterTabModel>({
render({model}) {
return mask({
message: div(
p('Attempting to connect to cluster.'),
p('Local instance may be unavailable, please wait.')
),
isDisplayed: true,
spinner: true,
omit: !model.lastLoadException
});
}
});
42 changes: 21 additions & 21 deletions admin/tabs/cluster/ClusterTabModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,20 @@ import {badge} from '@xh/hoist/cmp/badge';
import {GridModel, numberCol} from '@xh/hoist/cmp/grid';
import {hbox} from '@xh/hoist/cmp/layout';
import {getRelativeTimestamp} from '@xh/hoist/cmp/relativetimestamp';
import {TabContainerModel} from '@xh/hoist/cmp/tab';
import {HoistModel, LoadSpec, managed, PlainObject, XH} from '@xh/hoist/core';
import {TabContainerModel, TabModel} from '@xh/hoist/cmp/tab';
import {HoistModel, LoadSpec, lookup, managed, PlainObject, XH} from '@xh/hoist/core';
import {RecordActionSpec} from '@xh/hoist/data';
import {Icon} from '@xh/hoist/icon';
import {makeObservable} from '@xh/hoist/mobx';
import {Timer} from '@xh/hoist/utils/async';
import {SECONDS} from '@xh/hoist/utils/datetime';
import {ReactNode} from 'react';

export class ClusterTabModel extends HoistModel {
override persistWith = {localStorageKey: 'xhAdminClusterTabState'};

@lookup(TabModel) private tabModel: TabModel;

shutdownAction: RecordActionSpec = {
icon: Icon.skull(),
text: 'Shutdown Instance',
Expand All @@ -39,7 +42,7 @@ export class ClusterTabModel extends HoistModel {
};

@managed readonly gridModel: GridModel = this.createGridModel();
@managed readonly tabModel: TabContainerModel = this.createTabModel();
@managed readonly tabContainerModel: TabContainerModel = this.createTabContainerModel();
@managed readonly timer: Timer;

get instance(): PlainObject {
Expand All @@ -56,28 +59,25 @@ export class ClusterTabModel extends HoistModel {

override async doLoadAsync(loadSpec: LoadSpec) {
const {gridModel} = this;
try {
let data = await XH.fetchJson({url: 'clusterAdmin/allInstances', loadSpec});
data = data.map(row => ({
...row,
isLocal: row.name == XH.environmentService.serverInstance,
usedHeapMb: row.memory?.usedHeapMb,
usedPctMax: row.memory?.usedPctMax
}));

gridModel.loadData(data);
await gridModel.preSelectFirstAsync();
} catch (e) {
gridModel.clear();
XH.handleException(e);
}
let data = await XH.fetchJson({url: 'clusterAdmin/allInstances', loadSpec});
data = data.map(row => ({
...row,
isLocal: row.name == XH.environmentService.serverInstance,
usedHeapMb: row.memory?.usedHeapMb,
usedPctMax: row.memory?.usedPctMax
}));
gridModel.loadData(data);
await gridModel.preSelectFirstAsync();
}

constructor() {
super();
makeObservable(this);

this.timer = Timer.create({
runFn: this.autoRefreshAsync,
runFn: () => {
if (this.tabModel?.isActive) this.autoRefreshAsync();
},
interval: 5 * SECONDS,
delay: true
});
Expand All @@ -86,7 +86,7 @@ export class ClusterTabModel extends HoistModel {
{
track: () => this.instanceName,
run: instName => {
if (instName) this.tabModel.refreshContextModel.refreshAsync();
if (instName) this.tabContainerModel.refreshContextModel.refreshAsync();
}
},
{
Expand Down Expand Up @@ -161,7 +161,7 @@ export class ClusterTabModel extends HoistModel {
});
}

createTabModel() {
createTabContainerModel() {
return new TabContainerModel({
route: 'default.cluster',
switcher: false,
Expand Down
3 changes: 2 additions & 1 deletion admin/tabs/cluster/connpool/ConnPoolMonitorPanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,7 +60,8 @@ export const connPoolMonitorPanel = hoistCmp.factory({
),
poolConfigPanel()
),
mask: 'onLoad'
mask: 'onLoad',
ref: model.viewRef
});
}
});
Expand Down
3 changes: 2 additions & 1 deletion admin/tabs/cluster/environment/ServerEnvPanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@ export const serverEnvPanel = hoistCmp.factory({
exportButton()
],
item: lastLoadException ? errorMessage({error: lastLoadException}) : grid(),
mask: 'onLoad'
mask: 'onLoad',
ref: model.viewRef
});
}
});
3 changes: 2 additions & 1 deletion admin/tabs/cluster/hzobject/HzObjectPanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,8 @@ export const hzObjectPanel = hoistCmp.factory({
exportButton()
],
item: hframe(grid(), detailsPanel()),
mask: 'onLoad'
mask: 'onLoad',
ref: model.viewRef
});
}
});
Expand Down
42 changes: 18 additions & 24 deletions admin/tabs/cluster/logs/LogDisplayModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {Icon} from '@xh/hoist/icon';
import {bindable, makeObservable} from '@xh/hoist/mobx';
import {Timer} from '@xh/hoist/utils/async';
import {olderThan, ONE_SECOND, SECONDS} from '@xh/hoist/utils/datetime';
import {debounced, isDisplayed} from '@xh/hoist/utils/js';
import {debounced} from '@xh/hoist/utils/js';
import {escapeRegExp, maxBy} from 'lodash';
import {LogViewerModel} from './LogViewerModel';

Expand Down Expand Up @@ -105,26 +105,21 @@ export class LogDisplayModel extends HoistModel {
return;
}

try {
const response = await XH.fetchJson({
url: 'logViewerAdmin/getFile',
params: {
filename: parent.file,
startLine: this.startLine,
maxLines: this.maxLines,
pattern: this.regexOption ? this.pattern : escapeRegExp(this.pattern),
caseSensitive: this.caseSensitive,
instance: parent.instanceName
},
loadSpec
});
if (!response.success) throw XH.exception(response.exception);
this.updateGridData(response.content);
} catch (e) {
// Show errors inline in the viewer vs. a modal alert or catchDefault().
const msg = e.message || 'An unknown error occurred';
this.updateGridData([[0, `Error: ${msg}`]]);
}
const response = await XH.fetchJson({
url: 'logViewerAdmin/getFile',
params: {
filename: parent.file,
startLine: this.startLine,
maxLines: this.maxLines,
pattern: this.regexOption ? this.pattern : escapeRegExp(this.pattern),
caseSensitive: this.caseSensitive,
instance: parent.instanceName
},
loadSpec
});
// Backward compatibility for Hoist Core < v22, which returned exception in-band
if (!response.success) throw XH.exception(response.exception);
this.updateGridData(response.content);
}

async scrollToTail() {
Expand Down Expand Up @@ -209,14 +204,13 @@ export class LogDisplayModel extends HoistModel {
}

private autoRefreshLines() {
const {tailActive, parent} = this,
{viewRef} = parent;
const {tailActive} = this;

if (
tailActive &&
olderThan(this.lastLoadCompleted, 5 * SECONDS) &&
!this.loadModel.isPending &&
isDisplayed(viewRef.current)
this.parent.isVisible
) {
this.loadLog();
}
Expand Down
4 changes: 2 additions & 2 deletions admin/tabs/cluster/logs/LogViewer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@ export const logViewer = hoistCmp.factory({

return hframe({
className,
ref: model.viewRef,
items: [
panel({
collapsedTitle: 'Log Files',
Expand Down Expand Up @@ -57,7 +56,8 @@ export const logViewer = hoistCmp.factory({
}),
logDisplay(),
model.showLogLevelDialog ? logLevelDialog() : null
]
],
ref: model.viewRef
});
}
});
3 changes: 0 additions & 3 deletions admin/tabs/cluster/logs/LogViewerModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ import {compactDateRenderer, fmtNumber} from '@xh/hoist/format';
import {Icon} from '@xh/hoist/icon';
import {bindable, makeObservable, observable} from '@xh/hoist/mobx';
import download from 'downloadjs';
import {createRef} from 'react';
import {LogDisplayModel} from './LogDisplayModel';

/**
Expand All @@ -23,8 +22,6 @@ import {LogDisplayModel} from './LogDisplayModel';
export class LogViewerModel extends BaseInstanceModel {
@observable file: string = null;

viewRef = createRef<HTMLElement>();

@managed
logDisplayModel = new LogDisplayModel(this);

Expand Down
3 changes: 2 additions & 1 deletion admin/tabs/cluster/memory/MemoryMonitorPanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,8 @@ export const memoryMonitorPanel = hoistCmp.factory({
item: chart()
})
],
mask: 'onLoad'
mask: 'onLoad',
ref: model.viewRef
});
}
});
3 changes: 2 additions & 1 deletion admin/tabs/cluster/services/ServicePanel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ export const servicePanel = hoistCmp.factory({
}),
detailsPanel()
),
mask: 'onLoad'
mask: 'onLoad',
ref: model.viewRef
});
}
});
8 changes: 1 addition & 7 deletions admin/tabs/cluster/websocket/WebSocketModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,16 +15,12 @@ import {Icon} from '@xh/hoist/icon';
import {makeObservable, observable, runInAction} from '@xh/hoist/mobx';
import {Timer} from '@xh/hoist/utils/async';
import {SECONDS} from '@xh/hoist/utils/datetime';
import {isDisplayed} from '@xh/hoist/utils/js';
import {isEmpty} from 'lodash';
import {createRef} from 'react';
import * as WSCol from './WebSocketColumns';
import {RecordActionSpec} from '@xh/hoist/data';
import {AppModel} from '@xh/hoist/admin/AppModel';

export class WebSocketModel extends BaseInstanceModel {
viewRef = createRef<HTMLElement>();

@observable
lastRefresh: number;

Expand Down Expand Up @@ -87,9 +83,7 @@ export class WebSocketModel extends BaseInstanceModel {

this._timer = Timer.create({
runFn: () => {
if (isDisplayed(this.viewRef.current)) {
this.autoRefreshAsync();
}
if (this.isVisible) this.autoRefreshAsync();
},
interval: 5 * SECONDS,
delay: true
Expand Down
Loading

0 comments on commit 03fbd74

Please sign in to comment.