Kaynağa Gözat

If running im JupyterHub, provide a dialog that prompts the user to
restart the server.

Ian Rose 5 yıl önce
ebeveyn
işleme
d5e1c617ad

+ 1 - 16
packages/application-extension/src/index.tsx

@@ -2,7 +2,6 @@
 // Distributed under the terms of the Modified BSD License.
 
 import {
-  ConnectionLost,
   IConnectionLost,
   ILabShell,
   ILabStatus,
@@ -746,19 +745,6 @@ const paths: JupyterFrontEndPlugin<JupyterFrontEnd.IPaths> = {
   provides: JupyterFrontEnd.IPaths
 };
 
-/**
- * The default JupyterLab connection lost provider. This may be overridden
- * to provide custom behavior when a connection to the server is lost.
- */
-const connectionlost: JupyterFrontEndPlugin<IConnectionLost> = {
-  id: '@jupyterlab/apputils-extension:connectionlost',
-  activate: (app: JupyterFrontEnd): IConnectionLost => {
-    return ConnectionLost;
-  },
-  autoStart: true,
-  provides: IConnectionLost
-};
-
 /**
  * Export the plugins as default.
  */
@@ -773,8 +759,7 @@ const plugins: JupyterFrontEndPlugin<any>[] = [
   shell,
   status,
   info,
-  paths,
-  connectionlost
+  paths
 ];
 
 export default plugins;

+ 2 - 1
packages/hub-extension/package.json

@@ -32,7 +32,8 @@
     "@jupyterlab/application": "^1.0.0-alpha.9",
     "@jupyterlab/apputils": "^1.0.0-alpha.9",
     "@jupyterlab/coreutils": "^3.0.0-alpha.9",
-    "@jupyterlab/mainmenu": "^1.0.0-alpha.9"
+    "@jupyterlab/mainmenu": "^1.0.0-alpha.9",
+    "@jupyterlab/services": "^4.0.0-alpha.9"
   },
   "devDependencies": {
     "rimraf": "~2.6.2",

+ 76 - 2
packages/hub-extension/src/index.ts

@@ -3,9 +3,11 @@
 | Distributed under the terms of the Modified BSD License.
 |----------------------------------------------------------------------------*/
 
-import { ICommandPalette } from '@jupyterlab/apputils';
+import { Dialog, ICommandPalette, showDialog } from '@jupyterlab/apputils';
 
 import {
+  ConnectionLost,
+  IConnectionLost,
   IRouter,
   JupyterFrontEnd,
   JupyterFrontEndPlugin
@@ -15,6 +17,8 @@ import { URLExt } from '@jupyterlab/coreutils';
 
 import { IMainMenu } from '@jupyterlab/mainmenu';
 
+import { ServerConnection, ServiceManager } from '@jupyterlab/services';
+
 /**
  * The command IDs used by the plugin.
  */
@@ -22,6 +26,8 @@ export namespace CommandIDs {
   export const controlPanel: string = 'hub:control-panel';
 
   export const logout: string = 'hub:logout';
+
+  export const restart: string = 'hub:restart';
 }
 
 /**
@@ -50,6 +56,20 @@ function activateHubExtension(
 
   const { commands } = app;
 
+  // TODO: use /spawn/:user/:name
+  // but that requires jupyterhub 1.0
+  // and jupyterlab to pass username, servername to PageConfig
+  const restartUrl =
+    hubHost + URLExt.join(hubPrefix, `spawn?next=${hubPrefix}home`);
+
+  commands.addCommand(CommandIDs.restart, {
+    label: 'Restart Server',
+    caption: 'Request that the Hub restart this server',
+    execute: () => {
+      window.open(restartUrl, '_blank');
+    }
+  });
+
   commands.addCommand(CommandIDs.controlPanel, {
     label: 'Hub Control Panel',
     caption: 'Open the Hub control panel in a new browser tab',
@@ -86,4 +106,58 @@ const hubExtension: JupyterFrontEndPlugin<void> = {
   autoStart: true
 };
 
-export default hubExtension;
+/**
+ * The default JupyterLab connection lost provider. This may be overridden
+ * to provide custom behavior when a connection to the server is lost.
+ *
+ * If the application is being deployed within a JupyterHub context,
+ * this will provide a dialog that prompts the user to restart the server.
+ * Otherwise, it shows an error dialog.
+ */
+const connectionlost: JupyterFrontEndPlugin<IConnectionLost> = {
+  id: '@jupyterlab/apputils-extension:connectionlost',
+  requires: [JupyterFrontEnd.IPaths],
+  activate: (
+    app: JupyterFrontEnd,
+    paths: JupyterFrontEnd.IPaths
+  ): IConnectionLost => {
+    const hubPrefix = paths.urls.hubPrefix || '';
+    const baseUrl = paths.urls.base;
+
+    // Return the default error message if not running on JupyterHub.
+    if (!hubPrefix) {
+      return ConnectionLost;
+    }
+
+    // If we are running on JupyterHub, return a dialog
+    // that prompts the user to restart their server.
+    let showingError = false;
+    const onConnectionLost: IConnectionLost = async (
+      manager: ServiceManager.IManager,
+      err: ServerConnection.NetworkError
+    ): Promise<void> => {
+      if (showingError) {
+        return;
+      }
+      showingError = true;
+      const result = await showDialog({
+        title: 'Server Not Running',
+        body: `Your server at ${baseUrl} is not running.
+Would you like to restart it?`,
+        buttons: [
+          Dialog.okButton({ label: 'Restart' }),
+          Dialog.cancelButton({ label: 'Dismiss' })
+        ]
+      });
+      showingError = false;
+      if (result.button.accept) {
+        await app.commands.execute(CommandIDs.restart);
+      }
+    };
+    return onConnectionLost;
+  },
+  autoStart: true,
+  provides: IConnectionLost
+};
+
+export default [hubExtension, connectionlost] as JupyterFrontEndPlugin<any>[];

+ 3 - 0
packages/hub-extension/tsconfig.json

@@ -17,6 +17,9 @@
     },
     {
       "path": "../mainmenu"
+    },
+    {
+      "path": "../services"
     }
   ]
 }

+ 8 - 5
packages/services/src/kernel/manager.ts

@@ -116,7 +116,7 @@ export class KernelManager implements Kernel.IManager {
   /**
    * A signal emitted when there is a connection failure.
    */
-  get connectionFailure(): ISignal<this, ServerConnection.NetworkError> {
+  get connectionFailure(): ISignal<this, Error> {
     return this._connectionFailure;
   }
 
@@ -280,7 +280,12 @@ export class KernelManager implements Kernel.IManager {
    */
   protected async requestRunning(): Promise<void> {
     const models = await Kernel.listRunning(this.serverSettings).catch(err => {
-      if (err instanceof ServerConnection.NetworkError) {
+      // Check for a network error, or a 503 error, which is returned
+      // by a JupyterHub when a server is shut down.
+      if (
+        err instanceof ServerConnection.NetworkError ||
+        (err.response && err.response.status === 503)
+      ) {
         this._connectionFailure.emit(err);
         return [] as Kernel.IModel[];
       }
@@ -354,9 +359,7 @@ export class KernelManager implements Kernel.IManager {
   private _runningChanged = new Signal<this, Kernel.IModel[]>(this);
   private _specs: Kernel.ISpecModels | null = null;
   private _specsChanged = new Signal<this, Kernel.ISpecModels>(this);
-  private _connectionFailure = new Signal<this, ServerConnection.NetworkError>(
-    this
-  );
+  private _connectionFailure = new Signal<this, Error>(this);
 }
 
 /**

+ 4 - 12
packages/services/src/manager.ts

@@ -78,7 +78,7 @@ export class ServiceManager implements ServiceManager.IManager {
   /**
    * A signal emitted when there is a connection failure with the kernel.
    */
-  get connectionFailure(): ISignal<this, ServerConnection.NetworkError> {
+  get connectionFailure(): ISignal<this, Error> {
     return this._connectionFailure;
   }
 
@@ -166,19 +166,14 @@ export class ServiceManager implements ServiceManager.IManager {
     return this._readyPromise;
   }
 
-  private _onConnectionFailure(
-    sender: any,
-    err: ServerConnection.NetworkError
-  ): void {
+  private _onConnectionFailure(sender: any, err: Error): void {
     this._connectionFailure.emit(err);
   }
 
   private _isDisposed = false;
   private _readyPromise: Promise<void>;
   private _specsChanged = new Signal<this, Kernel.ISpecModels>(this);
-  private _connectionFailure = new Signal<this, ServerConnection.NetworkError>(
-    this
-  );
+  private _connectionFailure = new Signal<this, Error>(this);
   private _isReady = false;
 }
 
@@ -253,10 +248,7 @@ export namespace ServiceManager {
     /**
      * A signal emitted when there is a connection failure with the server.
      */
-    readonly connectionFailure: ISignal<
-      IManager,
-      ServerConnection.NetworkError
-    >;
+    readonly connectionFailure: ISignal<IManager, Error>;
   }
 
   /**

+ 8 - 5
packages/services/src/session/manager.ts

@@ -85,7 +85,7 @@ export class SessionManager implements Session.IManager {
   /**
    * A signal emitted when there is a connection failure.
    */
-  get connectionFailure(): ISignal<this, ServerConnection.NetworkError> {
+  get connectionFailure(): ISignal<this, Error> {
     return this._connectionFailure;
   }
 
@@ -289,7 +289,12 @@ export class SessionManager implements Session.IManager {
    */
   protected async requestRunning(): Promise<void> {
     const models = await Session.listRunning(this.serverSettings).catch(err => {
-      if (err instanceof ServerConnection.NetworkError) {
+      // Check for a network error, or a 503 error, which is returned
+      // by a JupyterHub when a server is shut down.
+      if (
+        err instanceof ServerConnection.NetworkError ||
+        (err.response && err.response.status === 503)
+      ) {
         this._connectionFailure.emit(err);
         return [] as Session.IModel[];
       }
@@ -380,9 +385,7 @@ export class SessionManager implements Session.IManager {
   private _pollSpecs: Poll;
   private _ready: Promise<void>;
   private _runningChanged = new Signal<this, Session.IModel[]>(this);
-  private _connectionFailure = new Signal<this, ServerConnection.NetworkError>(
-    this
-  );
+  private _connectionFailure = new Signal<this, Error>(this);
   private _sessions = new Set<Session.ISession>();
   private _specs: Kernel.ISpecModels | null = null;
   private _specsChanged = new Signal<this, Kernel.ISpecModels>(this);

+ 8 - 5
packages/services/src/terminal/manager.ts

@@ -69,7 +69,7 @@ export class TerminalManager implements TerminalSession.IManager {
   /**
    * A signal emitted when there is a connection failure.
    */
-  get connectionFailure(): ISignal<this, ServerConnection.NetworkError> {
+  get connectionFailure(): ISignal<this, Error> {
     return this._connectionFailure;
   }
 
@@ -246,7 +246,12 @@ export class TerminalManager implements TerminalSession.IManager {
   protected async requestRunning(): Promise<void> {
     const models = await TerminalSession.listRunning(this.serverSettings).catch(
       err => {
-        if (err instanceof ServerConnection.NetworkError) {
+        // Check for a network error, or a 503 error, which is returned
+        // by a JupyterHub when a server is shut down.
+        if (
+          err instanceof ServerConnection.NetworkError ||
+          (err.response && err.response.status === 503)
+        ) {
           this._connectionFailure.emit(err);
           return [] as TerminalSession.IModel[];
         }
@@ -325,9 +330,7 @@ export class TerminalManager implements TerminalSession.IManager {
   private _sessions = new Set<TerminalSession.ISession>();
   private _ready: Promise<void>;
   private _runningChanged = new Signal<this, TerminalSession.IModel[]>(this);
-  private _connectionFailure = new Signal<this, ServerConnection.NetworkError>(
-    this
-  );
+  private _connectionFailure = new Signal<this, Error>(this);
 }
 
 /**