From 07f3f226043e29f32e1f5ac0d1abce166e6fb42c Mon Sep 17 00:00:00 2001 From: williamsyang-work Date: Mon, 22 Jan 2024 17:28:37 +0100 Subject: [PATCH] Add Server Offline Landing to Placeholder Widget Adds a prompt to start the server when the front-end detects that there is no server currently running. This provides a clearer application flow for the user. Signed-off-by: Will Yang --- .../components/trace-context-component.tsx | 32 ++++++++++------ .../trace-explorer-placeholder-widget.tsx | 28 ++++++++++---- .../react-components/style/trace-viewer.css | 20 ++++++++++ ...heia-trace-explorer-placeholder-widget.tsx | 38 +++++++++++++++++-- .../trace-explorer-server-status-widget.tsx | 26 +++++++++++-- .../trace-explorer/trace-explorer-widget.tsx | 26 ++++++++++++- ...ce-server-connection-status-client-impl.ts | 29 ++++++++++++-- .../trace-viewer/trace-viewer-contribution.ts | 11 +++--- .../src/browser/trace-viewer/trace-viewer.tsx | 14 +++++++ .../common/trace-server-connection-status.ts | 18 ++++++++- 10 files changed, 204 insertions(+), 38 deletions(-) diff --git a/packages/react-components/src/components/trace-context-component.tsx b/packages/react-components/src/components/trace-context-component.tsx index 2136e8a96..bc8059f7c 100644 --- a/packages/react-components/src/components/trace-context-component.tsx +++ b/packages/react-components/src/components/trace-context-component.tsx @@ -48,6 +48,7 @@ export interface TraceContextProps { removeResizeHandler: (handler: () => void) => void; backgroundTheme: string; persistedState?: PersistedState; + serverStatus?: boolean; } export interface TraceContextState { @@ -491,20 +492,29 @@ export class TraceContextComponent extends React.Component 0 && (this.props.outputs.length || this.props.overviewDescriptor); return ( -
this.onContextMenu(event)} - onKeyDown={event => this.onKeyDown(event)} - onKeyUp={event => this.onKeyUp(event)} - ref={this.traceContextContainer} - > - - - {shouldRenderOutputs ? this.renderOutputs() : this.renderPlaceHolder()} -
+ <> + {/* Render the grey-out overlay if the server is down */} + {serverStatus === false && ( +
+
Please start the server to resume using the application.
+
+ )} +
this.onContextMenu(event)} + onKeyDown={event => this.onKeyDown(event)} + onKeyUp={event => this.onKeyUp(event)} + ref={this.traceContextContainer} + > + + + {shouldRenderOutputs ? this.renderOutputs() : this.renderPlaceHolder()} +
+ ); } diff --git a/packages/react-components/src/trace-explorer/trace-explorer-placeholder-widget.tsx b/packages/react-components/src/trace-explorer/trace-explorer-placeholder-widget.tsx index 44bd1ffd0..dc15297d3 100644 --- a/packages/react-components/src/trace-explorer/trace-explorer-placeholder-widget.tsx +++ b/packages/react-components/src/trace-explorer/trace-explorer-placeholder-widget.tsx @@ -4,31 +4,43 @@ import * as React from 'react'; export interface ReactPlaceholderWidgetProps { loading: boolean; + serverOn: boolean; + tracesOpen: boolean; handleOpenTrace: () => void; + handleStartServer: () => void; } -export class ReactExplorerPlaceholderWidget extends React.Component { +export class ReactExplorerPlaceholderWidget extends React.Component { constructor(props: ReactPlaceholderWidgetProps) { super(props); } render(): React.ReactNode { + const { loading, serverOn, handleOpenTrace, handleStartServer } = this.props; + const onClick = serverOn ? handleOpenTrace : handleStartServer; + const infoText = serverOn + ? 'You have not yet opened a trace.' + : 'No trace server instance is currently running.'; + const buttonText = serverOn ? 'Open Trace' : 'Start Trace Server'; + return (
-
{'You have not yet opened a trace.'}
+
{infoText}
); } } + +export default ReactExplorerPlaceholderWidget; diff --git a/packages/react-components/style/trace-viewer.css b/packages/react-components/style/trace-viewer.css index 00f0b8067..b9d795379 100644 --- a/packages/react-components/style/trace-viewer.css +++ b/packages/react-components/style/trace-viewer.css @@ -13,4 +13,24 @@ div { .trace-viewer-container { margin: 0px 5px 0px 5px; height: 100%; +} + +/* Grey out container */ +.overlay { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: rgba(0, 0, 0, 0.5); /* Semi-transparent black */ + display: flex; + justify-content: center; + align-items: center; + z-index: 999; /* Ensure the overlay appears on top */ +} + +.warning-text { + color: white; /* Color of the warning text */ + text-align: center; + font-size: 24px; } \ No newline at end of file diff --git a/theia-extensions/viewer-prototype/src/browser/trace-explorer/trace-explorer-sub-widgets/theia-trace-explorer-placeholder-widget.tsx b/theia-extensions/viewer-prototype/src/browser/trace-explorer/trace-explorer-sub-widgets/theia-trace-explorer-placeholder-widget.tsx index 9ef03f1bb..30eab339e 100644 --- a/theia-extensions/viewer-prototype/src/browser/trace-explorer/trace-explorer-sub-widgets/theia-trace-explorer-placeholder-widget.tsx +++ b/theia-extensions/viewer-prototype/src/browser/trace-explorer/trace-explorer-sub-widgets/theia-trace-explorer-placeholder-widget.tsx @@ -2,8 +2,9 @@ import { inject, injectable, postConstruct } from '@theia/core/shared/inversify' import { ReactWidget } from '@theia/core/lib/browser'; import * as React from 'react'; import { CommandService } from '@theia/core'; -import { OpenTraceCommand } from '../../trace-viewer/trace-viewer-commands'; +import { OpenTraceCommand, StartServerCommand } from '../../trace-viewer/trace-viewer-commands'; import { ReactExplorerPlaceholderWidget } from 'traceviewer-react-components/lib/trace-explorer/trace-explorer-placeholder-widget'; +import { TraceServerConnectionStatusClient } from '../../../common/trace-server-connection-status'; @injectable() export class TraceExplorerPlaceholderWidget extends ReactWidget { @@ -11,22 +12,35 @@ export class TraceExplorerPlaceholderWidget extends ReactWidget { static LABEL = 'Trace Explorer Placeholder Widget'; state = { - loading: false + loading: false, + serverStatus: false }; @inject(CommandService) protected readonly commandService!: CommandService; + @inject(TraceServerConnectionStatusClient) + protected traceServerConnectionStatusProxy: TraceServerConnectionStatusClient; @postConstruct() protected init(): void { this.id = TraceExplorerPlaceholderWidget.ID; this.title.label = TraceExplorerPlaceholderWidget.LABEL; + this.traceServerConnectionStatusProxy.addServerStatusChangeListener(this.handleOnServerStatusChange); this.update(); } + dispose(): void { + super.dispose(); + this.traceServerConnectionStatusProxy.removeServerStatusChangeListener(this.handleOnServerStatusChange); + + } + render(): React.ReactNode { - const { loading } = this.state; + const { loading, serverStatus } = this.state; return ( @@ -42,4 +56,22 @@ export class TraceExplorerPlaceholderWidget extends ReactWidget { this.state.loading = false; this.update(); } + + protected handleStartServer = async (): Promise => this.doHandleStartServer(); + + private async doHandleStartServer() { + this.state.loading = true; + this.update(); + await this.commandService.executeCommand(StartServerCommand.id); + this.state.loading = false; + // const status = await this.traceServerConnectionStatusProxy.getStatus(); + // this.handleOnServerStatusChange(status); + } + + protected handleOnServerStatusChange = (status: boolean): void => this.doHandleOnServerStatusChange(status); + + private doHandleOnServerStatusChange = (status: boolean): void => { + this.state.serverStatus = status; + this.update(); + }; } diff --git a/theia-extensions/viewer-prototype/src/browser/trace-explorer/trace-explorer-sub-widgets/trace-explorer-server-status-widget.tsx b/theia-extensions/viewer-prototype/src/browser/trace-explorer/trace-explorer-sub-widgets/trace-explorer-server-status-widget.tsx index 4c83746d6..9f1307b43 100644 --- a/theia-extensions/viewer-prototype/src/browser/trace-explorer/trace-explorer-sub-widgets/trace-explorer-server-status-widget.tsx +++ b/theia-extensions/viewer-prototype/src/browser/trace-explorer/trace-explorer-sub-widgets/trace-explorer-server-status-widget.tsx @@ -7,6 +7,7 @@ import { CommandService } from '@theia/core'; export class TraceExplorerServerStatusWidget extends ReactWidget { static ID = 'trace-explorer-server-status-widget'; static LABEL = 'Trace Explorer Server Status Widget'; + private serverOn = false; @inject(CommandService) protected readonly commandService!: CommandService; @@ -17,17 +18,34 @@ export class TraceExplorerServerStatusWidget extends ReactWidget { this.update(); } + public updateStatus = (status: boolean): void => { + this.serverOn = status; + this.update(); + }; + render(): React.ReactNode { + + const className = this.serverOn ? + 'fa fa-check-circle-o fa-lg' : + 'fa fa-times-circle-o fa-lg'; + + const title = this.serverOn ? + 'Server health and latency are good. No known issues' : + 'Trace Viewer Critical Error: Trace Server Offline'; + + const color = this.serverOn ? 'green' : 'red'; + return (
Server Status + className={className} + title={title} + style={{ color, marginLeft: '5px' }} + />
); + } } diff --git a/theia-extensions/viewer-prototype/src/browser/trace-explorer/trace-explorer-widget.tsx b/theia-extensions/viewer-prototype/src/browser/trace-explorer/trace-explorer-widget.tsx index 91e06f7b9..79e15f23d 100644 --- a/theia-extensions/viewer-prototype/src/browser/trace-explorer/trace-explorer-widget.tsx +++ b/theia-extensions/viewer-prototype/src/browser/trace-explorer/trace-explorer-widget.tsx @@ -83,12 +83,14 @@ export class TraceExplorerWidget extends BaseWidget { layout.addWidget(this.traceViewsContainer); this.node.tabIndex = 0; signalManager().on(Signals.OPENED_TRACES_UPDATED, this.onUpdateSignal); + this.connectionStatusClient.addServerStatusChangeListener(this.onServerStatusChange); this.update(); } dispose(): void { super.dispose(); signalManager().off(Signals.OPENED_TRACES_UPDATED, this.onUpdateSignal); + this.connectionStatusClient.removeServerStatusChangeListener(this.onServerStatusChange); } protected onUpdateSignal = (payload: OpenedTracesUpdatedSignalPayload): void => @@ -100,6 +102,13 @@ export class TraceExplorerWidget extends BaseWidget { protected onUpdateRequest(msg: Message): void { super.onUpdateRequest(msg); + + if (this.connectionStatusClient.status === false) { + this.traceViewsContainer.hide(); + this.placeholderWidget.show(); + return; + } + if (this._numberOfOpenedTraces > 0) { this.traceViewsContainer.show(); this.placeholderWidget.hide(); @@ -115,12 +124,25 @@ export class TraceExplorerWidget extends BaseWidget { } protected async onAfterShow(): Promise { - this.connectionStatusClient.addConnectionStatusListener(); + this.connectionStatusClient.subscribe(); const status = await this.traceServerConnectionStatusProxy.getStatus(); this.connectionStatusClient.updateStatus(status); } protected onAfterHide(): void { - this.connectionStatusClient.removeConnectionStatusListener(); + this.connectionStatusClient.unsubscribe(); } + + protected onServerStatusChange = (status: boolean): void => this.doHandleOnServerStatusChange(status); + protected doHandleOnServerStatusChange(status: boolean): void { + alert(`server status is: ${status}`); + this.serverStatusWidget.updateStatus(status); + if (status === true) { + this.placeholderWidget.hide(); + } else { + this.placeholderWidget.show(); + } + this.update(); + } + } diff --git a/theia-extensions/viewer-prototype/src/browser/trace-server-connection-status-client-impl.ts b/theia-extensions/viewer-prototype/src/browser/trace-server-connection-status-client-impl.ts index 8ecfd297c..156234863 100644 --- a/theia-extensions/viewer-prototype/src/browser/trace-server-connection-status-client-impl.ts +++ b/theia-extensions/viewer-prototype/src/browser/trace-server-connection-status-client-impl.ts @@ -2,24 +2,47 @@ import { injectable } from 'inversify'; import { TraceServerConnectionStatusClient } from '../common/trace-server-connection-status'; +type Listener = (serverStatus: boolean) => void; @injectable() export class TraceServerConnectionStatusClientImpl implements TraceServerConnectionStatusClient { protected active = false; + protected _status = false; + protected listeners: Listener[] = []; updateStatus(status: boolean): void { + this._status = status; if (this.active) { - TraceServerConnectionStatusClientImpl.renderStatus(status); + // TraceServerConnectionStatusClientImpl.renderStatus(status); + this.listeners.forEach(fn => { + fn(status); + console.dir(fn); + }); } } - addConnectionStatusListener(): void { + subscribe(): void { this.active = true; } - removeConnectionStatusListener(): void { + unsubscribe(): void { this.active = false; } + addServerStatusChangeListener(fn: Listener): void { + this.listeners.push(fn); + } + + removeServerStatusChangeListener(fn: Listener): void { + const index = this.listeners.indexOf(fn); + if (index) { + this.listeners.splice(index, 1); + } + } + + get status(): boolean { + return this._status; + } + static renderStatus(status: boolean): void { if (document.getElementById('server-status-id')) { document.getElementById('server-status-id')!.className = status diff --git a/theia-extensions/viewer-prototype/src/browser/trace-viewer/trace-viewer-contribution.ts b/theia-extensions/viewer-prototype/src/browser/trace-viewer/trace-viewer-contribution.ts index 96d4d420a..d0d4b3dcd 100644 --- a/theia-extensions/viewer-prototype/src/browser/trace-viewer/trace-viewer-contribution.ts +++ b/theia-extensions/viewer-prototype/src/browser/trace-viewer/trace-viewer-contribution.ts @@ -24,9 +24,9 @@ import { TracePreferences, TRACE_PATH, TRACE_ARGS } from '../trace-server-prefer import { TspClientProvider } from '../tsp-client-provider-impl'; import { ChartShortcutsDialog } from '../trace-explorer/trace-explorer-sub-widgets/charts-cheatsheet-component'; import { signalManager } from 'traceviewer-base/lib/signals/signal-manager'; -import { TraceServerConnectionStatusClientImpl } from '../trace-server-connection-status-client-impl'; import { FileStat } from '@theia/filesystem/lib/common/files'; import { ITspClient } from 'tsp-typescript-client'; +import { TraceServerConnectionStatusClient } from '../../common/trace-server-connection-status'; interface TraceViewerWidgetOpenerOptions extends WidgetOpenerOptions { traceUUID: string; @@ -50,6 +50,7 @@ export class TraceViewerContribution @inject(TracePreferences) protected tracePreferences: TracePreferences; @inject(TraceServerConfigService) protected readonly traceServerConfigService: TraceServerConfigService; @inject(MessageService) protected readonly messageService: MessageService; + @inject(TraceServerConnectionStatusClient) protected readonly serverStatusService: TraceServerConnectionStatusClient; readonly id = TraceViewerWidget.ID; readonly label = 'Trace Viewer'; @@ -94,7 +95,7 @@ export class TraceViewerContribution progress.report({ message: 'Trace server started.', work: { done: 100, total: 100 } }); } progress.cancel(); - TraceServerConnectionStatusClientImpl.renderStatus(true); + this.serverStatusService.updateStatus(true); signalManager().fireTraceServerStartedSignal(); this.openDialog(rootPath); } @@ -163,7 +164,7 @@ export class TraceViewerContribution } else { progress.report({ message: 'Trace server started.', work: { done: 100, total: 100 } }); } - TraceServerConnectionStatusClientImpl.renderStatus(true); + this.serverStatusService.updateStatus(true); signalManager().fireTraceServerStartedSignal(); return super.open(traceURI, options); } @@ -230,7 +231,7 @@ export class TraceViewerContribution } else { progress.report({ message: 'Trace server started.', work: { done: 100, total: 100 } }); } - TraceServerConnectionStatusClientImpl.renderStatus(true); + this.serverStatusService.updateStatus(true); signalManager().fireTraceServerStartedSignal(); return; } @@ -261,7 +262,7 @@ export class TraceViewerContribution try { await this.traceServerConfigService.stopTraceServer(); this.messageService.info('Trace server terminated successfully.'); - TraceServerConnectionStatusClientImpl.renderStatus(false); + this.serverStatusService.updateStatus(false); } catch (err) { this.messageService.error('Failed to stop the trace server.'); } diff --git a/theia-extensions/viewer-prototype/src/browser/trace-viewer/trace-viewer.tsx b/theia-extensions/viewer-prototype/src/browser/trace-viewer/trace-viewer.tsx index 416ebcf59..f480b41f0 100644 --- a/theia-extensions/viewer-prototype/src/browser/trace-viewer/trace-viewer.tsx +++ b/theia-extensions/viewer-prototype/src/browser/trace-viewer/trace-viewer.tsx @@ -11,6 +11,7 @@ import { TraceContextComponent, PersistedState } from 'traceviewer-react-components/lib/components/trace-context-component'; +import { TraceServerConnectionStatusClient } from '../../common/trace-server-connection-status'; import { Experiment } from 'tsp-typescript-client/lib/models/experiment'; import { TheiaMessageManager } from '../theia-message-manager'; import { ThemeService } from '@theia/core/lib/browser/theming'; @@ -56,6 +57,7 @@ export class TraceViewerWidget extends ReactWidget implements StatefulWidget { protected traceContextComponent: React.RefObject; protected persistedState?: PersistedState; protected loadTraceOverview = true; + protected serverStatus: boolean; protected resizeHandlers: (() => void)[] = []; protected readonly addResizeHandler = (h: () => void): void => { @@ -105,6 +107,7 @@ export class TraceViewerWidget extends ReactWidget implements StatefulWidget { @inject(ThemeService) protected readonly themeService: ThemeService; @inject(OverviewPreferences) protected overviewPreferences: OverviewPreferences; @inject(FileDialogService) protected readonly fileDialogService: FileDialogService; + @inject(TraceServerConnectionStatusClient) protected readonly connectionStatusService: TraceServerConnectionStatusClient; @postConstruct() protected init(): void { @@ -118,6 +121,7 @@ export class TraceViewerWidget extends ReactWidget implements StatefulWidget { this.title.closable = true; this.addClass('theia-trace-open'); this.backgroundTheme = this.themeService.getCurrentTheme().type; + this.serverStatus = this.connectionStatusService.status; this.toDispose.push(this.themeService.onDidColorThemeChange(() => this.updateBackgroundTheme())); if (!this.options.traceUUID) { this.initialize(); @@ -160,6 +164,7 @@ export class TraceViewerWidget extends ReactWidget implements StatefulWidget { if (this.loadTraceOverview) { this.doHandleTraceOverviewOpenedSignal(this.openedExperiment?.UUID); } + } protected readonly toDisposeOnNewExplorer = new DisposableCollection(); @@ -173,6 +178,7 @@ export class TraceViewerWidget extends ReactWidget implements StatefulWidget { signalManager().on(Signals.OPEN_OVERVIEW_OUTPUT, this.onTraceOverviewOpened); signalManager().on(Signals.OVERVIEW_OUTPUT_SELECTED, this.onTraceOverviewOutputSelected); signalManager().on(Signals.SAVE_AS_CSV, this.onSaveAsCSV); + this.connectionStatusService.addServerStatusChangeListener(this.onServerStatusChange); } protected updateBackgroundTheme(): void { @@ -188,6 +194,8 @@ export class TraceViewerWidget extends ReactWidget implements StatefulWidget { signalManager().off(Signals.OPEN_OVERVIEW_OUTPUT, this.onTraceOverviewOpened); signalManager().off(Signals.OVERVIEW_OUTPUT_SELECTED, this.onTraceOverviewOutputSelected); signalManager().off(Signals.SAVE_AS_CSV, this.onSaveAsCSV); + this.connectionStatusService.removeServerStatusChangeListener(this.onServerStatusChange); + } async initialize(): Promise { @@ -336,6 +344,11 @@ export class TraceViewerWidget extends ReactWidget implements StatefulWidget { this.loadTraceOverview = false; } + private onServerStatusChange = (status: boolean): void => { + this.serverStatus = status; + this.update(); + }; + private async fetchMarkerSets(expUUID: string) { const markers = await this.tspClient.fetchMarkerSets(expUUID); const markersResponse = markers.getModel(); @@ -390,6 +403,7 @@ export class TraceViewerWidget extends ReactWidget implements StatefulWidget { backgroundTheme={this.backgroundTheme} persistedState={this.persistedState} messageManager={this._signalHandler} + serverStatus={this.serverStatus} /> ) : ( 'Trace is loading...' diff --git a/theia-extensions/viewer-prototype/src/common/trace-server-connection-status.ts b/theia-extensions/viewer-prototype/src/common/trace-server-connection-status.ts index 831a14d2f..50b71a2e4 100644 --- a/theia-extensions/viewer-prototype/src/common/trace-server-connection-status.ts +++ b/theia-extensions/viewer-prototype/src/common/trace-server-connection-status.ts @@ -31,9 +31,23 @@ export interface TraceServerConnectionStatusClient { /** * Subscribe this client to the connection status */ - addConnectionStatusListener(): void; + subscribe(): void; /** * Unsubscribe this client from the connection status */ - removeConnectionStatusListener(): void; + unsubscribe(): void; + + /** + * Adds event listener for server status change + * @param fn event listener + */ + addServerStatusChangeListener(fn: (status: boolean) => void): void; + + /** + * Removes event listener for server status change. + * @param fn event listener to be removed + */ + removeServerStatusChangeListener(fn: (status: boolean) => void): void; + + status: boolean; }