• Ian Stuart's picture

    Modifying the presentation of a Notebook Server

    Ian Stuart / August 23, 2017
  • The task is to alter the main Notebook Server page: to remove some content and add others.

    Locations for changes to JupyterHub page

    1. Remove Clusters tab
    2. Remove the logout button
    3. Remove the Terminal option from the "new" menu
    4. Add a graphic for our service
    5. Add a graphic to indicate which Kernel is running

    The Notebook Server (as the Notebooks themselves) are Python apps, and use the Jinja Template Engine (http://jinja.pocoo.org/) for presentation, so some of this is relatively easy.

    We are running Notrebook Servers in Docker Containers, therefore the changes are done using Dockerfile rather than Puppet, however the concept is easy to impliment using either system.

    Modifying the template

    Tasks 1-3 are all taken care of by manipulating the template, there are three parts to this:

    1. Tell the notebook server where it can find extra templates,
    2. Setting up some extra template files, and
    3. Getting those extra templates into the Notebook.

    Telling the sever where to find extra template files.

    In jupyter_notebook_config.py, add the line

    c.NotebookApp.extra_template_paths = ['/srv/templates/']

    Create the extra template files

    Create the ./resources/templates directory next to your Dockerfile.

    In templates/, add 2 files:

    1. Hand edit page.html to read:
      {% extends "templates/page.html" %}
      {% block params %}
      {% endblock %}
      {% block header_buttons %}
        <span id="login_widget"></span>
      {% endblock %}
    2. Copy https://github.com/jupyter/notebook/blob/master/notebook/templates/tree.... and edit:
      • Around line 25: remove the <li> for the clusters
      • Around line 70: remove the whole section relating to Terminals

    Getting the files into the Docker Container

    Add to the Dockerfile:

        USER root  # switches to root user, you may not need this
        COPY resources/templates/ /srv/templates/
        RUN chmod a+rX /srv/templates

    Adding a service logo

    Adding a service logo can be done two ways: you can replace the Jupyter logo (there's documentation for older notebooks here) or you can add your own logo (and this add functionality to that image.) We are doing the latter.

    Edit page.html :

    {% block header_buttons %}
      <span id="login_widget">
        <a href="wherever" ><img src="/custom/my-logo.png" alt="My logo" class="my-logo"></a>
    {% endblock %}

    We now need to add the graphic, and the new style class

    If you look at the post Changing the location of a Notebooks configuration file you will see we are using an alternative location for our Config Dir, you may need to change things to suit your own environment.

    1. In the same directory as your Dockerfile, create the directory jupyter/custom/
    2. Move jupyter_notebook_config.py into jupyter/
    3. Copy my-logo.png into jupyter/custom/
    4. Create a file called custom.css in jupyter/custom, and add the css class definition for my-logo
    5. Edit Dockerfile and change:
      COPY jupyter_notebook_config.py /etc/jupyter/
      COPY jupyter/ /etc/jupyter/

    Adding a kernel logo

    Adding a kernel logo proved a bit trickier: we need to pass in some parameters/information to the Jinja template... and it turns out that Notebook v5.0 can do this!

    Getting the parameters into the Template system.

    In jupyter_notebook_config.py, add the line

    c.NotebookApp.jinja_template_vars = {'kernel_graphic' : 'geosciences.png',
                                         'kernel_alt_tag' : 'Geosciences Kernel'}

    (the jinja_template_vars is specific, the dictionary can be whatever you want)

    Copy the geosciences.png file to the jupyter/custom directory

    Edit the extra template files

    Edit page.html to pick up the new parameters:

    {% block params %}
    {% endblock %}
    {% block header_buttons %}
      <span id="login_widget">
        <a href="wherever" ><img src="/custom/my-logo.png" alt="My logo" class="my-logo"></a>
        <img src="/custom/{{kernel_graphic}}" alt="{{kernel_alt_tag}}" class="kernel-logo"/>
    {% endblock %}

    Edit custom.css to add the kernel-logo class

    Being Clever when building Containers on Containers

    One of the great features of Docker Containers is that you can base a new one on an existing one - Jupyter themselves do this: they have a base-notebook, then a minimal-notebook based off base, then base scripy- and r-notebooks off minimal.... and so on.

    Extending jupyter_notebook_config.py

    The catch is that we don't want to keep replacing the jupyter_notebook_config.py file each time, but extend it.

    As before, we need to put our kernel logo in jupyter/custom/ directory, however we want to append to the jupyter_notebook_config.py file

    Create a file called jupyter_notebook_config_extra.py in jupyter/, and set it's contents to:

    c.NotebookApp.jinja_template_vars = {'kernel_graphic' : 'Rlogo.png',
                                         'kernel_alt_tag' : 'R-language Kernel'}

    (note there is a blank line at the start of this file. That blank line is important!)

    Now add to the Dockerfile:

    USER root  # you may not need this
    COPY jupyter/ /etc/jupyter/
    RUN cat /etc/jupyter/jupyter_notebook_config_extra.py >> /etc/jupyter/jupyter_notebook_config.py

    The new Docker Image will inherit from any previous image(s), however it will set the Jinja variables to the last set in the Dockerfile.

    Failure to include the blank like

    If you don't have a blank line in the config file you append, then the chances are that the first additional line will just add to the end of the last line of the previous file, and that will almost certainly make the Dockerfile syntactically invalid.