Skip to content

Commit fd1ecc0

Browse files
authored
Merge pull request #113 from Zsailer/frontends
Open ExtensionApps at extension's landing page
2 parents 46843e8 + 9885528 commit fd1ecc0

File tree

2 files changed

+81
-20
lines changed

2 files changed

+81
-20
lines changed

jupyter_server/extension/application.py

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,38 @@ def static_url_prefix(self):
140140
help=_("The default URL to redirect to from `/`")
141141
)
142142

143+
custom_display_url = Unicode(u'', config=True,
144+
help=_("""Override URL shown to users.
145+
146+
Replace actual URL, including protocol, address, port and base URL,
147+
with the given value when displaying URL to the users. Do not change
148+
the actual connection URL. If authentication token is enabled, the
149+
token is added to the custom URL automatically.
150+
151+
This option is intended to be used when the URL to display to the user
152+
cannot be determined reliably by the Jupyter server (proxified
153+
or containerized setups for example).""")
154+
)
155+
156+
@default('custom_display_url')
157+
def _default_custom_display_url(self):
158+
"""URL to display to the user."""
159+
# Get url from server.
160+
url = url_path_join(self.serverapp.base_url, self.default_url)
161+
return self.serverapp.get_url(self.serverapp.ip, url)
162+
163+
def _write_browser_open_file(self, url, fh):
164+
"""Use to hijacks the server's browser-open file and open at
165+
the extension's homepage.
166+
"""
167+
# Ignore server's url
168+
del url
169+
path = url_path_join(self.serverapp.base_url, self.default_url)
170+
url = self.serverapp.get_url(path=path)
171+
jinja2_env = self.serverapp.web_app.settings['jinja2_env']
172+
template = jinja2_env.get_template('browser-open.html')
173+
fh.write(template.render(open_url=url))
174+
143175
def initialize_settings(self):
144176
"""Override this method to add handling of settings."""
145177
pass
@@ -256,15 +288,21 @@ def initialize(self, serverapp, argv=[]):
256288
self._prepare_settings()
257289
self._prepare_handlers()
258290

259-
def start(self, **kwargs):
291+
def start(self):
260292
"""Start the underlying Jupyter server.
261293
262294
Server should be started after extension is initialized.
263295
"""
264-
# Start the browser at this extensions default_url.
265-
self.serverapp.default_url = self.default_url
296+
# Override the browser open file to
297+
# Override the server's display url to show extension's display URL.
298+
self.serverapp.custom_display_url = self.custom_display_url
299+
# Override the server's default option and open a broswer window.
300+
self.serverapp.open_browser = True
301+
# Hijack the server's browser-open file to land on
302+
# the extensions home page.
303+
self.serverapp._write_browser_open_file = self._write_browser_open_file
266304
# Start the server.
267-
self.serverapp.start(**kwargs)
305+
self.serverapp.start()
268306

269307
def stop(self):
270308
"""Stop the underlying Jupyter server.

jupyter_server/serverapp.py

Lines changed: 39 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import time
3232
import warnings
3333
import webbrowser
34+
import urllib
3435

3536
from base64 import encodebytes
3637
from jinja2 import Environment, FileSystemLoader
@@ -1357,31 +1358,53 @@ def init_webapp(self):
13571358
@property
13581359
def display_url(self):
13591360
if self.custom_display_url:
1360-
url = self.custom_display_url
1361-
if not url.endswith('/'):
1362-
url += '/'
1361+
parts = urllib.parse.urlparse(self.custom_display_url)
1362+
path = parts.path
1363+
ip = parts.hostname
13631364
else:
1365+
path = None
13641366
if self.ip in ('', '0.0.0.0'):
13651367
ip = "%s" % socket.gethostname()
13661368
else:
13671369
ip = self.ip
1368-
url = self._url(ip)
1370+
1371+
token = None
13691372
if self.token:
13701373
# Don't log full token if it came from config
13711374
token = self.token if self._token_generated else '...'
1372-
url = (url_concat(url, {'token': token})
1373-
+ '\n or '
1374-
+ url_concat(self._url('127.0.0.1'), {'token': token}))
1375+
1376+
url = (
1377+
self.get_url(ip=ip, path=path, token=token)
1378+
+ '\n or '
1379+
+ self.get_url(ip='127.0.0.1', path=path, token=token)
1380+
)
13751381
return url
13761382

13771383
@property
13781384
def connection_url(self):
13791385
ip = self.ip if self.ip else 'localhost'
1380-
return self._url(ip)
1386+
return self.get_url(ip=ip)
13811387

1382-
def _url(self, ip):
1383-
proto = 'https' if self.certfile else 'http'
1384-
return "%s://%s:%i%s" % (proto, ip, self.port, self.base_url)
1388+
def get_url(self, ip=None, path=None, token=None):
1389+
"""Build a url for the application.
1390+
"""
1391+
if not ip:
1392+
ip = self.ip
1393+
if not path:
1394+
path = url_path_join(self.base_url, self.default_url)
1395+
# Build query string.
1396+
if self.token:
1397+
token = urllib.parse.urlencode({'token': token})
1398+
# Build the URL Parts to dump.
1399+
urlparts = urllib.parse.ParseResult(
1400+
scheme='https' if self.certfile else 'http',
1401+
netloc="{ip}:{port}".format(ip=ip, port=self.port),
1402+
path=path,
1403+
params=None,
1404+
query=token,
1405+
fragment=None
1406+
)
1407+
return urlparts.geturl()
13851408

13861409
def init_terminals(self):
13871410
if not self.terminals_enabled:
@@ -1429,7 +1452,7 @@ def _confirm_exit(self):
14291452
"""
14301453
info = self.log.info
14311454
info(_('interrupted'))
1432-
print(self.notebook_info())
1455+
print(self.running_server_info())
14331456
yes = _('y')
14341457
no = _('n')
14351458
sys.stdout.write(_("Shutdown this Jupyter server (%s/[%s])? ") % (yes, no))
@@ -1457,7 +1480,7 @@ def _signal_stop(self, sig, frame):
14571480
self.io_loop.add_callback_from_signal(self.io_loop.stop)
14581481

14591482
def _signal_info(self, sig, frame):
1460-
print(self.notebook_info())
1483+
print(self.running_server_info())
14611484

14621485
def init_components(self):
14631486
"""Check the components submodule, and warn if it's unclean"""
@@ -1586,7 +1609,7 @@ def cleanup_kernels(self):
15861609
self.log.info(kernel_msg % n_kernels)
15871610
self.kernel_manager.shutdown_all()
15881611

1589-
def notebook_info(self, kernel_count=True):
1612+
def running_server_info(self, kernel_count=True):
15901613
"Return the current working directory and the server url information"
15911614
info = self.contents_manager.info_string() + "\n"
15921615
if kernel_count:
@@ -1715,7 +1738,7 @@ def start(self):
17151738
self.exit(1)
17161739

17171740
info = self.log.info
1718-
for line in self.notebook_info(kernel_count=False).split("\n"):
1741+
for line in self.running_server_info(kernel_count=False).split("\n"):
17191742
info(line)
17201743
info(_("Use Control-C to stop this server and shut down all kernels (twice to skip confirmation)."))
17211744
if 'dev' in jupyter_server.__version__:
@@ -1735,7 +1758,7 @@ def start(self):
17351758
# with auth info.
17361759
self.log.critical('\n'.join([
17371760
'\n',
1738-
'To access the notebook, open this file in a browser:',
1761+
'To access the server, open this file in a browser:',
17391762
' %s' % urljoin('file:', pathname2url(self.browser_open_file)),
17401763
'Or copy and paste one of these URLs:',
17411764
' %s' % self.display_url,

0 commit comments

Comments
 (0)