-
Notifications
You must be signed in to change notification settings - Fork 107
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
I want to provide a button to the Hub Control Panel when we are running under a JupyterHub. This requires conditional rendering in some way - either we render the initial HTML as a template, or setup an API endpoint that JS can use. This PR templatizes the initial rendering. The following changes are made: 1. Convert into a Jupyter Server extension. This gives us the flexibility we need here. 2. Inherit a handler directly from jupyter_server_proxy, enabling us to use the *same* URL for both the initial HTTP rendering and the websockify! However, this means we lose the launcher icon - let's try mitigate that somehow.
- Loading branch information
Showing
9 changed files
with
163 additions
and
68 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
7 changes: 7 additions & 0 deletions
7
jupyter-config/jupyter_notebook_config.d/jupyter_remote_desktop_proxy.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"NotebookApp": { | ||
"nbserver_extensions": { | ||
"jupyter_remote_desktop_proxy": true | ||
} | ||
} | ||
} |
7 changes: 7 additions & 0 deletions
7
jupyter-config/jupyter_server_config.d/jupyter_remote_desktop_proxy.json
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
{ | ||
"ServerApp": { | ||
"jpserver_extensions": { | ||
"jupyter_remote_desktop_proxy": true | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,64 +1,17 @@ | ||
import os | ||
import shlex | ||
import tempfile | ||
from shutil import which | ||
|
||
HERE = os.path.dirname(os.path.abspath(__file__)) | ||
|
||
|
||
def setup_desktop(): | ||
# make a secure temporary directory for sockets | ||
# This is only readable, writeable & searchable by our uid | ||
sockets_dir = tempfile.mkdtemp() | ||
sockets_path = os.path.join(sockets_dir, 'vnc-socket') | ||
vncserver = which('vncserver') | ||
from .server_extension import load_jupyter_server_extension | ||
|
||
if vncserver is None: | ||
# Use bundled tigervnc | ||
vncserver = os.path.join(HERE, 'share/tigervnc/bin/vncserver') | ||
|
||
# TigerVNC provides the option to connect a Unix socket. TurboVNC does not. | ||
# TurboVNC and TigerVNC share the same origin and both use a Perl script | ||
# as the executable vncserver. We can determine if vncserver is TigerVNC | ||
# by searching TigerVNC string in the Perl script. | ||
with open(vncserver) as vncserver_file: | ||
is_tigervnc = "TigerVNC" in vncserver_file.read() | ||
HERE = os.path.dirname(os.path.abspath(__file__)) | ||
|
||
if is_tigervnc: | ||
vnc_args = [vncserver, '-rfbunixpath', sockets_path] | ||
socket_args = ['--unix-target', sockets_path] | ||
else: | ||
vnc_args = [vncserver] | ||
socket_args = [] | ||
|
||
if not os.path.exists(os.path.expanduser('~/.vnc/xstartup')): | ||
vnc_args.extend(['-xstartup', os.path.join(HERE, 'share/xstartup')]) | ||
def _jupyter_server_extension_points(): | ||
""" | ||
Set up the server extension for collecting metrics | ||
""" | ||
return [{"module": "jupyter_remote_desktop_proxy"}] | ||
|
||
vnc_command = shlex.join( | ||
vnc_args | ||
+ [ | ||
'-verbose', | ||
'-geometry', | ||
'1680x1050', | ||
'-SecurityTypes', | ||
'None', | ||
'-fg', | ||
] | ||
) | ||
|
||
return { | ||
'command': [ | ||
'websockify', | ||
'-v', | ||
'--web', | ||
os.path.join(HERE, 'static'), | ||
'--heartbeat', | ||
'30', | ||
'{port}', | ||
] | ||
+ socket_args | ||
+ ['--', '/bin/sh', '-c', f'cd {os.getcwd()} && {vnc_command}'], | ||
'timeout': 30, | ||
'mappath': {'/': '/index.html'}, | ||
'new_browser_window': True, | ||
} | ||
# For backward compatibility | ||
_load_jupyter_server_extension = load_jupyter_server_extension | ||
_jupyter_server_extension_paths = _jupyter_server_extension_points |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import os | ||
import shlex | ||
import tempfile | ||
from shutil import which | ||
|
||
import jinja2 | ||
from jupyter_server_proxy.handlers import SuperviseAndProxyHandler | ||
from tornado import web | ||
|
||
jinja_env = jinja2.Environment( | ||
loader=jinja2.FileSystemLoader( | ||
os.path.join(os.path.dirname(__file__), 'templates') | ||
), | ||
) | ||
|
||
|
||
HERE = os.path.dirname(os.path.abspath(__file__)) | ||
|
||
|
||
def get_websockify_command(): | ||
# make a secure temporary directory for sockets | ||
# This is only readable, writeable & searchable by our uid | ||
sockets_dir = tempfile.mkdtemp() | ||
sockets_path = os.path.join(sockets_dir, 'vnc-socket') | ||
vncserver = which('vncserver') | ||
|
||
if vncserver is None: | ||
# Use bundled tigervnc | ||
vncserver = os.path.join(HERE, 'share/tigervnc/bin/vncserver') | ||
|
||
# TigerVNC provides the option to connect a Unix socket. TurboVNC does not. | ||
# TurboVNC and TigerVNC share the same origin and both use a Perl script | ||
# as the executable vncserver. We can determine if vncserver is TigerVNC | ||
# by searching TigerVNC string in the Perl script. | ||
with open(vncserver) as vncserver_file: | ||
is_tigervnc = "TigerVNC" in vncserver_file.read() | ||
|
||
if is_tigervnc: | ||
vnc_args = [vncserver, '-rfbunixpath', sockets_path] | ||
socket_args = ['--unix-target', sockets_path] | ||
else: | ||
vnc_args = [vncserver] | ||
socket_args = [] | ||
|
||
if not os.path.exists(os.path.expanduser('~/.vnc/xstartup')): | ||
vnc_args.extend(['-xstartup', os.path.join(HERE, 'share/xstartup')]) | ||
|
||
vnc_command = shlex.join( | ||
vnc_args | ||
+ [ | ||
'-verbose', | ||
'-geometry', | ||
'1680x1050', | ||
'-SecurityTypes', | ||
'None', | ||
'-fg', | ||
] | ||
) | ||
|
||
return ( | ||
[ | ||
'websockify', | ||
'-v', | ||
'--heartbeat', | ||
'30', | ||
'{port}', | ||
] | ||
+ socket_args | ||
+ ['--', '/bin/sh', '-c', f'cd {os.getcwd()} && {vnc_command}'] | ||
) | ||
|
||
|
||
class DesktopHandler(SuperviseAndProxyHandler): | ||
def __init__(self, *args, **kwargs): | ||
super().__init__(*args, **kwargs) | ||
self.command = get_websockify_command() | ||
|
||
@web.authenticated | ||
async def http_get(self, *args, **kwargs): | ||
template_params = { | ||
'base_url': self.base_url, | ||
} | ||
template_params.update(self.serverapp.jinja_template_vars) | ||
self.write(jinja_env.get_template("index.html").render(**template_params)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
from pathlib import Path | ||
|
||
from jupyter_server.base.handlers import AuthenticatedFileHandler | ||
from jupyter_server.utils import url_path_join | ||
from jupyter_server_proxy.handlers import AddSlashHandler | ||
|
||
from .handlers import DesktopHandler | ||
|
||
HERE = Path(__file__).parent | ||
|
||
|
||
def load_jupyter_server_extension(server_app): | ||
""" | ||
Called during notebook start | ||
""" | ||
base_url = server_app.web_app.settings["base_url"] | ||
|
||
server_app.web_app.add_handlers( | ||
".*", | ||
[ | ||
( | ||
url_path_join(base_url, "/desktop/static/(.*)"), | ||
AuthenticatedFileHandler, | ||
{"path": (str(HERE / "static"))}, | ||
), | ||
(url_path_join(base_url, "/desktop"), AddSlashHandler), | ||
(url_path_join(base_url, "/desktop/()"), DesktopHandler, {'state': {}}), | ||
], | ||
) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters