I haven't found that much info on JupyterHub Groups, as far as I understand it though, there are two groups "admin", and non-admin. Or, rather, there is perhaps only one group "admin". Perhaps one could add a new group - "dashboard-users", whose users would only be allowed to list/launch already existing dashboards, and not a "normal" server. The UI would then reflect your group belonging, as it does with showing the "Admin" tab already.
However, for starters, I would be perfectly fine with some slightly hacky way to throw an error at the non-techies, if they disregard my instructions and actually click these "My Server".
What I'm thinking right now is to experiment with passing in the user name to the notebook server (Docker container), and then have a hook that checks a hard-coded allowed list of users, e.g. in /usr/local/bin/before-notebook.d
, and if not allowed, refuses to start, somehow... It won't be pretty, but might do for now.
I am following the issue with great interest, and I definitely think it could be a very useful feature for many.
Thank you so much for this project, it's going to improve my quality of life at work, immensely, I'm sure!
I was able to implement a hack to disallow "non-technical" users from launching anything but dashboard servers, by adding a script to /usr/local/bin/before-notebook.d/disallow-non-techies.sh like this:
#!/bin/bash
ALLOWED_NB_USERS=(AxelTLarsson)
function assert_allowed_user() {
if [[ ! " ${ALLOWED_NB_USERS[@]} " =~ " ${GITHUB_USER} " ]]; then
# whatever you want to do when array doesn't contain value
echo "${GITHUB_USER} is not allowed to start a normal notebook server"
exit 1
fi
}
if [ -z ${GITHUB_USER+x} ]; then
# If $GITHUB_USER is not set, we are attempting to start a voila (dashboard) server
# => go ahead
echo "Allowing dashboard start"
else
# If GITHUB_USER is not set, we are starting a normal notebook server, check if that is ok
assert_allowed_user
fi
It's not very pretty, and relies on the presence of $GITHUB_USER to determine if it's launching a normal server or a dashboard. So far, it seems to work alright, but I'm eagerly awaiting any development that will obviate this hack.
In your config add:
c.CDSDashboardsConfig.spawn_allow_group = 'spawners-group'
(or whatever named group you want, it will be created if it doesn't exist)
this group will be the only people allowed to spawn servers or create dashboards.
Alternatively use:
c.CDSDashboardsConfig.spawn_block_group = 'viewers-group'
to specify a list of users who should only be viewers - useful if most new users are developers.
Hey all, I'm really enjoying using dashboards. But I've recently run into an issue...When I start a dashboard, then stop the server, and finally restart the server, I sometimes get the following error:
ERROR:tornado.application:Uncaught exception GET /user/****/dash-testsds/ (****)
HTTPServerRequest(protocol='http', host='****:8888', method='GET', uri='/user/****/dash-testsds/', version='HTTP/1.1', remote_ip='****')
Traceback (most recent call last):
File "/opt/amazon/lib/python3.7/site-packages/tornado/web.py", line 1592, in _execute
result = yield result
File "/opt/amazon/lib/python3.7/site-packages/tornado/gen.py", line 1133, in run
value = future.result()
File "/usr/local/lib/python3.7/site-packages/jhsingle_native_proxy/websocket.py", line 103, in get
return await self.http_get(*args, **kwargs)
File "/usr/local/lib/python3.7/site-packages/jhsingle_native_proxy/proxyhandlers.py", line 718, in http_get
return await self.proxy(self.port, path)
File "/usr/local/lib/python3.7/site-packages/jhsingle_native_proxy/proxyhandlers.py", line 712, in proxy
return await self.oauth_proxy(port, path)
TypeError: object NoneType can't be used in 'await' expression
Any thoughts why this is happening?
Hi! I've found that when I use rather memory-intensive widgets with voila, I get failed page loads (timeout from Tornado). I found the following error in my logs when this happens:
ERROR:asyncio:Task exception was never retrieved
future: <Task finished name='Task-33' coro=<SuperviseAndProxyHandler.ensure_process.<locals>.pipe_output() done, defined at /opt/conda/lib/python3.8/site-packages/jhsingle_native_proxy/proxyhandlers.py:645> exception=ValueError('Separator is found, but chunk is longer than limit')>
Traceback (most recent call last):
File "/opt/conda/lib/python3.8/asyncio/streams.py", line 540, in readline
line = await self.readuntil(sep)
File "/opt/conda/lib/python3.8/asyncio/streams.py", line 635, in readuntil
raise exceptions.LimitOverrunError(
asyncio.exceptions.LimitOverrunError: Separator is found, but chunk is longer than limit
During handling of the above exception, another exception occurred:
Traceback (most recent call last):
File "/opt/conda/lib/python3.8/site-packages/jhsingle_native_proxy/proxyhandlers.py", line 650, in pipe_output
line = await stream.readline()
File "/opt/conda/lib/python3.8/asyncio/streams.py", line 549, in readline
raise ValueError(e.args[0])
ValueError: Separator is found, but chunk is longer than limit
Any ideas on this?
Error report from ContainDS Dashboards
Command Running:
python3 -m plotlydash_tornado_cmd.main /home/jupyter-justina/./Network/main.py --port=49503
Error output:
Fetching Plotly Dash script /home/jupyter-justina/./Network/main.py
CWD to /home/jupyter-justina/./Network
Importing user Dash app
Standard output:
Traceback (most recent call last):
File "/opt/tljh/user/lib/python3.7/runpy.py", line 193, in _run_module_as_main
"__main__", mod_spec)
File "/opt/tljh/user/lib/python3.7/runpy.py", line 85, in _run_code
exec(code, run_globals)
File "/opt/tljh/user/lib/python3.7/site-packages/plotlydash_tornado_cmd/main.py", line 88, in <module>
run()
File "/opt/tljh/user/lib/python3.7/site-packages/click/core.py", line 829, in __call__
return self.main(*args, **kwargs)
File "/opt/tljh/user/lib/python3.7/site-packages/click/core.py", line 782, in main
rv = self.invoke(ctx)
File "/opt/tljh/user/lib/python3.7/site-packages/click/core.py", line 1066, in invoke
return ctx.invoke(self.callback, **ctx.params)
File "/opt/tljh/user/lib/python3.7/site-packages/click/core.py", line 610, in invoke
return callback(*args, **kwargs)
File "/opt/tljh/user/lib/python3.7/site-packages/plotlydash_tornado_cmd/main.py", line 75, in run
app = make_app(command, server_name, debug)
File "/opt/tljh/user/lib/python3.7/site-packages/plotlydash_tornado_cmd/main.py", line 35, in make_app
spec.loader.exec_module(userscript)
File "<frozen importlib._bootstrap_external>", line 728, in exec_module
File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
File "/home/jupyter-justina/./Network/main.py", line 11, in <module>
import layout
ModuleNotFoundError: No module named 'layout'
@danlester : Getting back to integration of ContainDS Dashboards this year. We've successfully embedded jupyterhub in an iframe; however, I am unable to get dashboards to display in the iframe due to its violating the Content Security Policy. To get around this issue in notebooks served by JupyterHub as well as JupyterHub itself, I've followed jupyterhub/jupyterhub#379, adding the following to my config.yaml
:
single-user: |
c.JupyterHub.tornado_settings = { 'headers': { 'Content-Security-Policy': 'frame-ancestors self https://*.domain.name/'}}
hub: |
c.JupyterHub.tornado_settings = { 'headers': { 'Content-Security-Policy': 'frame-ancestors self https://*.domain.name/'}}
spawner: |
c.Spawner.args = ['--NotebookApp.tornado_settings={"headers":{"Content-Security-Policy": "frame-ancestors * self https://*.domain.name/"}}']
(where, to be clear, in reality, domain.name substituted appropriately for our custom domain).
However, CDashboards proxies voila via jhsingle-native-proxy, so we cannot pass --NotebookApp.tornado_setting
to voila. An voila-dashboards/voila#609 on the Voila GH indicates that embedding Voila in an iframe should be able to succeed if I were to replace the last line of the above with the following instead:
c.Spawner.args = ['{--}Voila.tornado_settings="{\'headers\':{\'Content-Security-Policy\': \"frame-ancestors * self https://*.domain.name/\"}}"']
This is an improvement (of sorts), since it now passes the setting to tornado, but I end up with the following error:
Traceback (most recent call last):
File "/opt/conda/lib/python3.8/site-packages/tornado/web.py", line 1704, in _execute
result = await result
File "/opt/conda/lib/python3.8/site-packages/jhsingle_native_proxy/websocket.py", line 103, in get
return await self.http_get(*args, **kwargs)
File "/opt/conda/lib/python3.8/site-packages/jhsingle_native_proxy/proxyhandlers.py", line 724, in http_get
return await self.proxy(self.port, path)
File "/opt/conda/lib/python3.8/site-packages/jhsingle_native_proxy/proxyhandlers.py", line 718, in proxy
return await self.oauth_proxy(port, path)
File "/opt/conda/lib/python3.8/site-packages/jhsingle_native_proxy/proxyhandlers.py", line 673, in oauth_proxy
return await self.core_proxy(port, path)
File "/opt/conda/lib/python3.8/site-packages/jhsingle_native_proxy/proxyhandlers.py", line 704, in core_proxy
if not await self.ensure_process():
File "/opt/conda/lib/python3.8/site-packages/jhsingle_native_proxy/proxyhandlers.py", line 603, in ensure_process
cmd = self.get_cmd()
File "/opt/conda/lib/python3.8/site-packages/jhsingle_native_proxy/proxyhandlers.py", line 840, in get_cmd
return self._render_template(command)
File "/opt/conda/lib/python3.8/site-packages/jhsingle_native_proxy/proxyhandlers.py", line 827, in _render_template
return [self._render_template(v) for v in value]
File "/opt/conda/lib/python3.8/site-packages/jhsingle_native_proxy/proxyhandlers.py", line 827, in <listcomp>
return [self._render_template(v) for v in value]
File "/opt/conda/lib/python3.8/site-packages/jhsingle_native_proxy/proxyhandlers.py", line 825, in _render_template
return value.format(**args)
KeyError: "'headers'"
Any idea what I can do to fix this?
Great to hear from you!
As I think you suggest, it would be ideal if jhsingle-native-proxy itself would accept the tornado_settings directly (so Voila doesn't even need to be involved). For one thing, in that case you would pass something more like ['--tornado_settings=... instead of it being a level deeper and need quite so much escaping.
It might also be able to apply to more than just Voila in one go. By the way, you might also be interested in seeing how to modify the Voila 'launcher' directly here: https://cdsdashboards.readthedocs.io/en/stable/chapters/customization/customlaunchers.html
(The same approach for adding your own presentation type can be used to modify one of the built-in ones.)
It might still be possible to fix your problem directly. The problem is that jhsingle-native-proxy is attempting to substitute a fixed list of variables into the args string, namely:
{
'port': self.port,
'base_url': self.base_url,
'presentation_path': self.presentation_path,
'presentation_basename': self.presentation_basename,
'presentation_dirname': self.presentation_dirname,
'origin_host': self.origin_host,
'-': '-',
'--': '--'
}
So for example, {port} is replaced with the value of port - this is just using python format function.
However, it encounters {\'headers... which it thinks is starting a variable substituiton that doesn't exist.
I haven't tried it but maybe:
c.Spawner.args = ['{--}Voila.tornado_settings="{{\'headers\':{{\'Content-Security-Policy\': \"frame-ancestors * self https://*.domain.name/\"}}}}"']
but there might be more escaping needed in the ' and " or something yet!
Thanks so much! I was able to get this working. In fact less rather than more escaping was needed. The double quotes surrounding the dict needed to be removed else the tornado_settings are interpreted as a string (parsed to a list) rather than a dict.
This fix frees me up to make progress with further exploratory work using voila. I'll get back in touch if I encounter problems with configuring rshiny.
Makes sense - that's why you would need to solve this for Voila specifically rather than in c.Spawner.args.
Instead of updating c.Spawner.args, try something like this:
c.VariableMixin.extra_presentation_launchers = {
'voila': {
'args': ['--destport=0', 'python3', '{-}m','voila', '{presentation_path}',
'{--}port={port}',
'{--}no-browser',
'{--}Voila.base_url={base_url}/',
'{--}Voila.server_url=/',
'{--}Voila.tornado_settings={{\'headers\':{{\'Content-Security-Policy\': \"frame-ancestors * self https://*.domain.name/\"}}}}'
]
}
}
To construct this, I have borrowed the voila.args component of the cdsdashboards code here and just added your tornado_settings line.