mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-08 10:34:10 +00:00
Compare commits
61 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
10e7ab96e5 | ||
![]() |
40f519544f | ||
![]() |
076c14dce6 | ||
![]() |
e223ce59e1 | ||
![]() |
ad833755e1 | ||
![]() |
142978b4d8 | ||
![]() |
e3cab48039 | ||
![]() |
203f4a5855 | ||
![]() |
cfc27db43d | ||
![]() |
e2a8557083 | ||
![]() |
d5478b1f21 | ||
![]() |
cf19af6f1c | ||
![]() |
1342f00d8e | ||
![]() |
1e49b4379b | ||
![]() |
a5d563217c | ||
![]() |
b1ac3b82dc | ||
![]() |
75ebe40f86 | ||
![]() |
69d711929a | ||
![]() |
4c12872dbf | ||
![]() |
21cee1be31 | ||
![]() |
00c782fd40 | ||
![]() |
b3f9635ecc | ||
![]() |
8c10fb285e | ||
![]() |
8a3f5d8f2e | ||
![]() |
7b496a5b4a | ||
![]() |
64e7705053 | ||
![]() |
6502b50576 | ||
![]() |
861347cce0 | ||
![]() |
43d4b65250 | ||
![]() |
e53ce19fcc | ||
![]() |
e603ff8274 | ||
![]() |
22b15f0ecf | ||
![]() |
c48c5bce99 | ||
![]() |
fa11d7e3c6 | ||
![]() |
7e3f29d033 | ||
![]() |
b7827687a8 | ||
![]() |
0beb4639a3 | ||
![]() |
b010c9501e | ||
![]() |
295e92270b | ||
![]() |
e42066f1c9 | ||
![]() |
1d29fcbfb2 | ||
![]() |
bdbfbb7e32 | ||
![]() |
42314ed75b | ||
![]() |
d8141692ab | ||
![]() |
025db2f9f3 | ||
![]() |
3985140377 | ||
![]() |
6886384ca3 | ||
![]() |
4a7fe8648a | ||
![]() |
7383c0cf60 | ||
![]() |
83186e02a2 | ||
![]() |
c6b4577c0a | ||
![]() |
73b1922c17 | ||
![]() |
1430e02fa8 | ||
![]() |
9ef09a288a | ||
![]() |
4a093be938 | ||
![]() |
64a253dbef | ||
![]() |
54877025ca | ||
![]() |
555969141e | ||
![]() |
a938982bdc | ||
![]() |
60a153718d | ||
![]() |
d72a96ec17 |
@@ -1,4 +1,4 @@
|
||||
# dependabot.yml reference: https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
||||
# dependabot.yaml reference: https://docs.github.com/en/code-security/dependabot/dependabot-version-updates/configuration-options-for-the-dependabot.yml-file
|
||||
#
|
||||
# Notes:
|
||||
# - Status and logs from dependabot are provided at
|
||||
@@ -8,8 +8,9 @@ version: 2
|
||||
updates:
|
||||
# Maintain dependencies in our GitHub Workflows
|
||||
- package-ecosystem: github-actions
|
||||
directory: "/"
|
||||
directory: /
|
||||
labels: [ci]
|
||||
schedule:
|
||||
interval: weekly
|
||||
interval: monthly
|
||||
time: "05:00"
|
||||
timezone: "Etc/UTC"
|
||||
timezone: Etc/UTC
|
33
.github/workflows/test-docs.yml
vendored
33
.github/workflows/test-docs.yml
vendored
@@ -49,6 +49,11 @@ jobs:
|
||||
runs-on: ubuntu-20.04
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
with:
|
||||
# make rediraffecheckdiff requires git history to compare current
|
||||
# commit with the main branch and previous releases.
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: actions/setup-python@v4
|
||||
with:
|
||||
python-version: "3.9"
|
||||
@@ -72,3 +77,31 @@ jobs:
|
||||
run: |
|
||||
cd docs
|
||||
make linkcheck
|
||||
|
||||
# make rediraffecheckdiff compares files for different changesets
|
||||
# these diff targets aren't always available
|
||||
# - compare with base ref (usually 'main', always on 'origin') for pull requests
|
||||
# - only compare with tags when running against jupyterhub/jupyterhub
|
||||
# to avoid errors on forks, which often lack tags
|
||||
- name: check redirects for this PR
|
||||
if: github.event_name == 'pull_request'
|
||||
run: |
|
||||
cd docs
|
||||
export REDIRAFFE_BRANCH=origin/${{ github.base_ref }}
|
||||
make rediraffecheckdiff
|
||||
|
||||
# this should check currently published 'stable' links for redirects
|
||||
- name: check redirects since last release
|
||||
if: github.repository == 'jupyterhub/jupyterhub'
|
||||
run: |
|
||||
cd docs
|
||||
export REDIRAFFE_BRANCH=$(git describe --tags --abbrev=0)
|
||||
make rediraffecheckdiff
|
||||
|
||||
# longer-term redirect check (fixed version) for older links
|
||||
- name: check redirects since 3.0.0
|
||||
if: github.repository == 'jupyterhub/jupyterhub'
|
||||
run: |
|
||||
cd docs
|
||||
export REDIRAFFE_BRANCH=3.0.0
|
||||
make rediraffecheckdiff
|
||||
|
10
.github/workflows/test.yml
vendored
10
.github/workflows/test.yml
vendored
@@ -114,7 +114,7 @@ jobs:
|
||||
fi
|
||||
if [ "${{ matrix.db }}" == "mysql" ]; then
|
||||
echo "MYSQL_HOST=127.0.0.1" >> $GITHUB_ENV
|
||||
echo "JUPYTERHUB_TEST_DB_URL=mysql+mysqlconnector://root@127.0.0.1:3306/jupyterhub" >> $GITHUB_ENV
|
||||
echo "JUPYTERHUB_TEST_DB_URL=mysql+mysqldb://root@127.0.0.1:3306/jupyterhub" >> $GITHUB_ENV
|
||||
fi
|
||||
if [ "${{ matrix.ssl }}" == "ssl" ]; then
|
||||
echo "SSL_ENABLED=1" >> $GITHUB_ENV
|
||||
@@ -164,7 +164,9 @@ jobs:
|
||||
fi
|
||||
|
||||
if [ "${{ matrix.main_dependencies }}" != "" ]; then
|
||||
pip install git+https://github.com/ipython/traitlets#egg=traitlets --force
|
||||
# Tests are broken:
|
||||
# https://github.com/jupyterhub/jupyterhub/issues/4418
|
||||
# pip install git+https://github.com/ipython/traitlets#egg=traitlets --force
|
||||
pip install --upgrade --pre sqlalchemy
|
||||
fi
|
||||
if [ "${{ matrix.legacy_notebook }}" != "" ]; then
|
||||
@@ -175,7 +177,7 @@ jobs:
|
||||
pip install "jupyter_server==${{ matrix.jupyter_server }}"
|
||||
fi
|
||||
if [ "${{ matrix.db }}" == "mysql" ]; then
|
||||
pip install mysql-connector-python
|
||||
pip install mysqlclient
|
||||
fi
|
||||
if [ "${{ matrix.db }}" == "postgres" ]; then
|
||||
pip install psycopg2-binary
|
||||
@@ -246,7 +248,7 @@ jobs:
|
||||
|
||||
- name: build images
|
||||
run: |
|
||||
docker build -t jupyterhub/jupyterhub .
|
||||
DOCKER_BUILDKIT=1 docker build -t jupyterhub/jupyterhub .
|
||||
docker build -t jupyterhub/jupyterhub-onbuild onbuild
|
||||
docker build -t jupyterhub/jupyterhub:alpine -f dockerfiles/Dockerfile.alpine .
|
||||
docker build -t jupyterhub/singleuser singleuser
|
||||
|
@@ -24,7 +24,7 @@ repos:
|
||||
|
||||
# Autoformat: Python code
|
||||
- repo: https://github.com/PyCQA/autoflake
|
||||
rev: v2.0.1
|
||||
rev: v2.0.2
|
||||
hooks:
|
||||
- id: autoflake
|
||||
# args ref: https://github.com/PyCQA/autoflake#advanced-usage
|
||||
@@ -39,7 +39,7 @@ repos:
|
||||
|
||||
# Autoformat: Python code
|
||||
- repo: https://github.com/psf/black
|
||||
rev: 23.1.0
|
||||
rev: 23.3.0
|
||||
hooks:
|
||||
- id: black
|
||||
|
||||
|
94
Dockerfile
94
Dockerfile
@@ -24,11 +24,11 @@
|
||||
ARG BASE_IMAGE=ubuntu:22.04
|
||||
FROM $BASE_IMAGE AS builder
|
||||
|
||||
USER root
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
WORKDIR /src/jupyterhub
|
||||
|
||||
ENV DEBIAN_FRONTEND noninteractive
|
||||
RUN apt-get update \
|
||||
&& apt-get install -yq --no-install-recommends \
|
||||
RUN apt update -q \
|
||||
&& apt install -yq --no-install-recommends \
|
||||
build-essential \
|
||||
ca-certificates \
|
||||
locales \
|
||||
@@ -38,66 +38,54 @@ RUN apt-get update \
|
||||
python3-venv \
|
||||
nodejs \
|
||||
npm \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
RUN python3 -m pip install --upgrade setuptools pip build wheel
|
||||
RUN npm install --global yarn
|
||||
|
||||
&& apt clean \
|
||||
&& rm -rf /var/lib/apt/lists/* \
|
||||
&& python3 -m pip install --no-cache-dir --upgrade setuptools pip build wheel \
|
||||
&& npm install --global yarn
|
||||
# copy everything except whats in .dockerignore, its a
|
||||
# compromise between needing to rebuild and maintaining
|
||||
# what needs to be part of the build
|
||||
COPY . /src/jupyterhub/
|
||||
WORKDIR /src/jupyterhub
|
||||
|
||||
# Build client component packages (they will be copied into ./share and
|
||||
# packaged with the built wheel.)
|
||||
RUN python3 -m build --wheel
|
||||
RUN python3 -m pip wheel --wheel-dir wheelhouse dist/*.whl
|
||||
|
||||
COPY . .
|
||||
ARG PIP_CACHE_DIR=/tmp/pip-cache
|
||||
RUN --mount=type=cache,target=${PIP_CACHE_DIR} \
|
||||
python3 -m build --wheel \
|
||||
&& python3 -m pip wheel --wheel-dir wheelhouse dist/*.whl
|
||||
|
||||
FROM $BASE_IMAGE
|
||||
|
||||
USER root
|
||||
|
||||
ENV DEBIAN_FRONTEND=noninteractive
|
||||
|
||||
RUN apt-get update \
|
||||
&& apt-get install -yq --no-install-recommends \
|
||||
ca-certificates \
|
||||
curl \
|
||||
gnupg \
|
||||
locales \
|
||||
python3-pip \
|
||||
python3-pycurl \
|
||||
nodejs \
|
||||
npm \
|
||||
&& apt-get clean \
|
||||
&& rm -rf /var/lib/apt/lists/*
|
||||
|
||||
ENV SHELL=/bin/bash \
|
||||
ENV DEBIAN_FRONTEND=noninteractive \
|
||||
SHELL=/bin/bash \
|
||||
LC_ALL=en_US.UTF-8 \
|
||||
LANG=en_US.UTF-8 \
|
||||
LANGUAGE=en_US.UTF-8
|
||||
|
||||
RUN locale-gen $LC_ALL
|
||||
|
||||
# always make sure pip is up to date!
|
||||
RUN python3 -m pip install --no-cache --upgrade setuptools pip
|
||||
|
||||
RUN npm install -g configurable-http-proxy@^4.2.0 \
|
||||
&& rm -rf ~/.npm
|
||||
|
||||
# install the wheels we built in the first stage
|
||||
COPY --from=builder /src/jupyterhub/wheelhouse /tmp/wheelhouse
|
||||
RUN python3 -m pip install --no-cache /tmp/wheelhouse/*
|
||||
|
||||
RUN mkdir -p /srv/jupyterhub/
|
||||
WORKDIR /srv/jupyterhub/
|
||||
LANGUAGE=en_US.UTF-8 \
|
||||
PYTHONDONTWRITEBYTECODE=1
|
||||
|
||||
EXPOSE 8000
|
||||
|
||||
LABEL maintainer="Jupyter Project <jupyter@googlegroups.com>"
|
||||
LABEL org.jupyter.service="jupyterhub"
|
||||
|
||||
WORKDIR /srv/jupyterhub
|
||||
|
||||
RUN apt update -q \
|
||||
&& apt install -yq --no-install-recommends \
|
||||
ca-certificates \
|
||||
curl \
|
||||
gnupg \
|
||||
locales \
|
||||
python-is-python3 \
|
||||
python3-pip \
|
||||
python3-pycurl \
|
||||
nodejs \
|
||||
npm \
|
||||
&& locale-gen $LC_ALL \
|
||||
&& npm install -g configurable-http-proxy@^4.2.0 \
|
||||
# clean cache and logs
|
||||
&& rm -rf /var/lib/apt/lists/* /var/log/* /var/tmp/* ~/.npm \
|
||||
&& find / -type d -name '__pycache__' -prune -exec rm -rf {} \;
|
||||
# install the wheels we built in the first stage
|
||||
RUN --mount=type=cache,from=builder,source=/src/jupyterhub/wheelhouse,target=/tmp/wheelhouse \
|
||||
# always make sure pip is up to date!
|
||||
python3 -m pip install --no-compile --no-cache-dir --upgrade setuptools pip \
|
||||
&& python3 -m pip install --no-compile --no-cache-dir /tmp/wheelhouse/*
|
||||
|
||||
CMD ["jupyterhub"]
|
||||
|
@@ -6,7 +6,7 @@ info:
|
||||
description: The REST API for JupyterHub
|
||||
license:
|
||||
name: BSD-3-Clause
|
||||
version: 4.0.0b2
|
||||
version: 4.0.0
|
||||
servers:
|
||||
- url: /hub/api
|
||||
security:
|
||||
|
@@ -235,8 +235,12 @@ ogp_use_first_image = True
|
||||
# If you are basing changes off another branch/ commit, always change back
|
||||
# rediraffe_branch to main before pushing your changes upstream.
|
||||
#
|
||||
rediraffe_branch = "main"
|
||||
rediraffe_branch = os.environ.get("REDIRAFFE_BRANCH", "main")
|
||||
rediraffe_redirects = "redirects.txt"
|
||||
|
||||
# allow 80% match for autogenerated redirects
|
||||
rediraffe_auto_redirect_perc = 80
|
||||
|
||||
# rediraffe_redirects = {
|
||||
# "old-file": "new-folder/new-file-name",
|
||||
# }
|
||||
|
@@ -95,8 +95,14 @@ The Hub and its database are not involved in most requests to single-user server
|
||||
|
||||
JupyterHub supports a variety of database backends via [SQLAlchemy][].
|
||||
The default is sqlite, which works great for many cases, but you should be able to use many backends supported by SQLAlchemy.
|
||||
Usually, this will mean PostgreSQL or MySQL, both of which are well tested with JupyterHub.
|
||||
Usually, this will mean PostgreSQL or MySQL, both of which are officially supported and well tested with JupyterHub, but others may work as well.
|
||||
See [SQLAlchemy's docs][sqlalchemy-dialect] for how to connect to different database backends.
|
||||
Doing so generally involves:
|
||||
|
||||
1. installing a Python package that provides a client implementation, and
|
||||
2. setting [](JupyterHub.db_url) to connect to your database with the specified implementation
|
||||
|
||||
[sqlalchemy-dialect]: https://docs.sqlalchemy.org/en/20/dialects/
|
||||
[sqlalchemy]: https://www.sqlalchemy.org
|
||||
|
||||
### Default backend: SQLite
|
||||
@@ -109,14 +115,16 @@ For production systems, SQLite has some disadvantages when used with JupyterHub:
|
||||
|
||||
- `upgrade-db` may not always work, and you may need to start with a fresh database
|
||||
- `downgrade-db` **will not** work if you want to rollback to an earlier
|
||||
version, so backup the `jupyterhub.sqlite` file before upgrading
|
||||
version, so backup the `jupyterhub.sqlite` file before upgrading (JupyterHub automatically creates a date-stamped backup file when upgrading sqlite)
|
||||
|
||||
The sqlite documentation provides a helpful page about [when to use SQLite and
|
||||
where traditional RDBMS may be a better choice](https://sqlite.org/whentouse.html).
|
||||
|
||||
### Picking your database backend (PostgreSQL, MySQL)
|
||||
|
||||
When running a long term deployment or a production system, we recommend using a full-fledged relational database, such as [PostgreSQL](https://www.postgresql.org) or [MySQL](https://www.mysql.com), that supports the SQL `ALTER TABLE` statement.
|
||||
When running a long term deployment or a production system, we recommend using a full-fledged relational database, such as [PostgreSQL](https://www.postgresql.org) or [MySQL](https://www.mysql.com), that supports the SQL `ALTER TABLE` statement, which is used in some database upgrade steps.
|
||||
|
||||
In general, you select your database backend with [](JupyterHub.db_url), and can further configure it (usually not necessary) with [](JupyterHub.db_kwargs).
|
||||
|
||||
## Notes and Tips
|
||||
|
||||
@@ -132,14 +140,25 @@ multiple processes which might try to access the file at the same time.
|
||||
### PostgreSQL
|
||||
|
||||
We recommend using PostgreSQL for production if you are unsure whether to use
|
||||
MySQL or PostgreSQL or if you do not have a strong preference. There is
|
||||
additional configuration required for MySQL that is not needed for PostgreSQL.
|
||||
MySQL or PostgreSQL or if you do not have a strong preference.
|
||||
There is additional configuration required for MySQL that is not needed for PostgreSQL.
|
||||
|
||||
For example, to connect to a postgres database with psycopg2:
|
||||
|
||||
1. install psycopg2: `pip instal psycopg2` (or `psycopg2-binary` to avoid compilation, which is [not recommended for production][psycopg2-binary])
|
||||
2. set authentication via environment variables `PGUSER` and `PGPASSWORD`
|
||||
3. configure [](JupyterHub.db_url):
|
||||
|
||||
```python
|
||||
c.JupyterHub.db_url = "postgres+psycopg2://my-postgres-server:5432/my-db-name"
|
||||
```
|
||||
|
||||
[psycopg2-binary]: https://www.psycopg.org/docs/install.html#psycopg-vs-psycopg-binary
|
||||
|
||||
### MySQL / MariaDB
|
||||
|
||||
- You should use the `pymysql` sqlalchemy provider (the other one, MySQLdb,
|
||||
isn't available for py3).
|
||||
- You also need to set `pool_recycle` to some value (typically 60 - 300)
|
||||
- You should probably use the `pymysql` or `mysqlclient` sqlalchemy provider, or another backend [recommended by sqlalchemy](https://docs.sqlalchemy.org/en/20/dialects/mysql.html#dialect-mysql)
|
||||
- You also need to set `pool_recycle` to some value (typically 60 - 300, JupyterHub will default to 60)
|
||||
which depends on your MySQL setup. This is necessary since MySQL kills
|
||||
connections serverside if they've been idle for a while, and the connection
|
||||
from the hub will be idle for longer than most connections. This behavior
|
||||
@@ -153,3 +172,12 @@ additional configuration required for MySQL that is not needed for PostgreSQL.
|
||||
correctly. Later versions of MariaDB and MySQL should set these values by
|
||||
default, as well as have a default `DYNAMIC` `row_format` and pose no trouble
|
||||
to users.
|
||||
|
||||
For example, to connect to a mysql database with mysqlclient:
|
||||
|
||||
1. install mysqlclient: `pip install mysqlclient`
|
||||
2. configure [](JupyterHub.db_url):
|
||||
|
||||
```python
|
||||
c.JupyterHub.db_url = "mysql+mysqldb://myuser:mypassword@my-sql-server:3306/my-db-name"
|
||||
```
|
||||
|
@@ -1,23 +1,45 @@
|
||||
# This file contains rediraffe redirects as generated from the docs/source/conf.py file
|
||||
# For more information, see rediraffe configuration in the conf.py file.
|
||||
|
||||
|
||||
"changelog.md" "reference/changelog.md"
|
||||
"contributor-list.md" "contributing/contributor-list.md"
|
||||
"gallery-jhub-deployments.md" "reference/gallery-jhub-deployments.md"
|
||||
"installation-basics.md" "tutorial/installation-basics.md"
|
||||
"quickstart.md" "tutorial/quickstart.md"
|
||||
"quickstart-docker.md" "tutorial/quickstart-docker.md"
|
||||
"troubleshooting.md" "faq/troubleshooting.md"
|
||||
|
||||
"admin/capacity-planning.md" "explanation/capacity-planning.md"
|
||||
"admin/log-messages.md" "howto/log-messages.md"
|
||||
"admin/upgrading.md" "howto/upgrading.md"
|
||||
|
||||
"events/index.md" "reference/event-logging.md"
|
||||
|
||||
"getting-started/authenticators-users-basics.md" "tutorial/getting-started/authenticators-users-basics.md"
|
||||
"getting-started/config-basics.md" "tutorial/getting-started/config-basics.md"
|
||||
"getting-started/faq.md" "faq/faq.md"
|
||||
"getting-started/institutional-faq.md" "faq/institutional-faq.md"
|
||||
"getting-started/networking-basics.md" "tutorial/getting-started/networking-basics.md"
|
||||
"getting-started/services-basics.md" "tutorial/getting-started/services-basics.md"
|
||||
"getting-started/spawners-basics.md" "tutorial/getting-started/spawners-basics.md"
|
||||
|
||||
"reference/api-only.md" "howto/api-only.md"
|
||||
"reference/config-ghoauth.md" "howto/configuration/config-ghoauth.md"
|
||||
"reference/config-proxy.md" "howto/configuration/config-proxy.md"
|
||||
"admin/log-messages.md" "howto/log-messages.md"
|
||||
"reference/database.md" "explanation/database.md"
|
||||
"reference/oauth.md" "explanation/oauth.md"
|
||||
"reference/proxy.md" "howto/proxy.md"
|
||||
"reference/templates.md" "howto/templates.md"
|
||||
"quickstart-docker.md" "tutorial/quickstart-docker.md"
|
||||
"reference/config-examples.md" "howto/index.md"
|
||||
"getting-started/institutional-faq.md" "faq/institutional-faq.md"
|
||||
"troubleshooting.md" "faq/troubleshooting.md"
|
||||
"reference/config-sudo.md" "howto/configuration/config-sudo.md"
|
||||
"reference/config-user-env.md" "howto/configuration/config-user-env.md"
|
||||
"reference/rest.md" "howto/rest.md"
|
||||
"reference/separate-proxy.md" "howto/separate-proxy.md"
|
||||
"admin/upgrading.md" "howto/upgrading.md"
|
||||
"installation-basics.md" "tutorial/installation-basics.md"
|
||||
"quickstart.md" "tutorial/quickstart.md"
|
||||
"events/index.md" "reference/event-logging.md"
|
||||
"reference/server-api.md" "tutorial/server-api.md"
|
||||
"reference/websecurity.md" "explanation/websecurity.md"
|
||||
|
||||
# -- JupyterHub 4.0 --
|
||||
# redirects above are up-to-date as of JupyterHub 4.0
|
||||
# add future redirects below
|
||||
# (e.g. with `make rediraffewritediff`)
|
||||
|
@@ -273,7 +273,7 @@ c.Spawner.auth_state_hook = auth_state_hook
|
||||
:::
|
||||
|
||||
Some identity providers may have their own concept of group membership that you would like to preserve in JupyterHub.
|
||||
This is now possible with `Authenticator.managed_groups`.
|
||||
This is now possible with `Authenticator.manage_groups`.
|
||||
|
||||
You can set the config:
|
||||
|
||||
|
@@ -8,7 +8,9 @@ command line for details.
|
||||
|
||||
## [Unreleased]
|
||||
|
||||
### 4.0 (beta) - 2023-XX-YY
|
||||
## 4.0
|
||||
|
||||
### 4.0.0 - 2023-04-20
|
||||
|
||||
4.0 is a major release, but a small one.
|
||||
There are three major changes that _should_ be invisible to most users:
|
||||
@@ -24,7 +26,7 @@ There are three major changes that _should_ be invisible to most users:
|
||||
|
||||
In addition to these, thanks to contributions from this years Outreachy interns, we have reorganized the documentation according to [diataxis](https://diataxis.fr), improved accessibility of JupyterHub pages, and improved testing.
|
||||
|
||||
([full changelog](https://github.com/jupyterhub/jupyterhub/compare/3.1.0...HEAD))
|
||||
([full changelog](https://github.com/jupyterhub/jupyterhub/compare/3.1.0...4.0.0))
|
||||
|
||||
#### API and Breaking Changes
|
||||
|
||||
@@ -33,18 +35,21 @@ In addition to these, thanks to contributions from this years Outreachy interns,
|
||||
|
||||
#### New features added
|
||||
|
||||
- add Spawner.server_token_scopes config [#4400](https://github.com/jupyterhub/jupyterhub/pull/4400) ([@minrk](https://github.com/minrk), [@consideRatio](https://github.com/consideRatio))
|
||||
- Make singleuser server-extension default [#4354](https://github.com/jupyterhub/jupyterhub/pull/4354) ([@minrk](https://github.com/minrk), [@consideRatio](https://github.com/consideRatio))
|
||||
- singleuser auth as server extension [#3888](https://github.com/jupyterhub/jupyterhub/pull/3888) ([@minrk](https://github.com/minrk), [@consideRatio](https://github.com/consideRatio))
|
||||
- Dynamic table for changing customizable properties of groups [#3651](https://github.com/jupyterhub/jupyterhub/pull/3651) ([@vladfreeze](https://github.com/vladfreeze), [@minrk](https://github.com/minrk), [@naatebarber](https://github.com/naatebarber), [@manics](https://github.com/manics))
|
||||
|
||||
#### Enhancements made
|
||||
|
||||
- admin page: improve display of long lists (groups, etc.) [#4417](https://github.com/jupyterhub/jupyterhub/pull/4417) ([@manics](https://github.com/manics), [@minrk](https://github.com/minrk), [@ryanlovett](https://github.com/ryanlovett))
|
||||
- add a few more buckets for server_spawn_duration_seconds [#4352](https://github.com/jupyterhub/jupyterhub/pull/4352) ([@shaneknapp](https://github.com/shaneknapp), [@yuvipanda](https://github.com/yuvipanda))
|
||||
- Improve contrast on muted text [#4326](https://github.com/jupyterhub/jupyterhub/pull/4326) ([@bl-aire](https://github.com/bl-aire), [@minrk](https://github.com/minrk))
|
||||
- Standardize styling on input fields by moving common form CSS to page.less [#4294](https://github.com/jupyterhub/jupyterhub/pull/4294) ([@minrk](https://github.com/minrk), [@consideRatio](https://github.com/consideRatio))
|
||||
|
||||
#### Bugs fixed
|
||||
|
||||
- make sure named server URLs include trailing slash [#4402](https://github.com/jupyterhub/jupyterhub/pull/4402) ([@minrk](https://github.com/minrk), [@manics](https://github.com/manics))
|
||||
- fix inclusion of singleuser/templates/page.html in wheel [#4387](https://github.com/jupyterhub/jupyterhub/pull/4387) ([@consideRatio](https://github.com/consideRatio), [@minrk](https://github.com/minrk))
|
||||
- exponential_backoff: preserve jitter when max_wait is reached [#4383](https://github.com/jupyterhub/jupyterhub/pull/4383) ([@minrk](https://github.com/minrk), [@manics](https://github.com/manics))
|
||||
- admin panel: fix condition for start/stop buttons on user servers [#4365](https://github.com/jupyterhub/jupyterhub/pull/4365) ([@minrk](https://github.com/minrk), [@consideRatio](https://github.com/consideRatio))
|
||||
@@ -57,6 +62,13 @@ In addition to these, thanks to contributions from this years Outreachy interns,
|
||||
|
||||
#### Maintenance and upkeep improvements
|
||||
|
||||
- add remaining redirects for docs reorg [#4423](https://github.com/jupyterhub/jupyterhub/pull/4423) ([@minrk](https://github.com/minrk), [@consideRatio](https://github.com/consideRatio))
|
||||
- Disable dev traitlets [#4419](https://github.com/jupyterhub/jupyterhub/pull/4419) ([@manics](https://github.com/manics), [@consideRatio](https://github.com/consideRatio))
|
||||
- dependabot: rename to .yaml [#4409](https://github.com/jupyterhub/jupyterhub/pull/4409) ([@consideRatio](https://github.com/consideRatio))
|
||||
- dependabot: fix syntax error of not using quotes for ##:## [#4408](https://github.com/jupyterhub/jupyterhub/pull/4408) ([@consideRatio](https://github.com/consideRatio))
|
||||
- dependabot: monthly updates of github actions [#4403](https://github.com/jupyterhub/jupyterhub/pull/4403) ([@consideRatio](https://github.com/consideRatio), [@minrk](https://github.com/minrk))
|
||||
- Refresh 4.0 changelog [#4396](https://github.com/jupyterhub/jupyterhub/pull/4396) ([@minrk](https://github.com/minrk), [@consideRatio](https://github.com/consideRatio))
|
||||
- Reduce size of jupyterhub image [#4394](https://github.com/jupyterhub/jupyterhub/pull/4394) ([@alekseyolg](https://github.com/alekseyolg), [@minrk](https://github.com/minrk))
|
||||
- Selenium: updating test_oauth_page [#4393](https://github.com/jupyterhub/jupyterhub/pull/4393) ([@mouse1203](https://github.com/mouse1203), [@minrk](https://github.com/minrk))
|
||||
- avoid warning on engine_connect listener [#4392](https://github.com/jupyterhub/jupyterhub/pull/4392) ([@minrk](https://github.com/minrk), [@consideRatio](https://github.com/consideRatio), [@manics](https://github.com/manics))
|
||||
- remove pin from singleuser [#4379](https://github.com/jupyterhub/jupyterhub/pull/4379) ([@minrk](https://github.com/minrk), [@consideRatio](https://github.com/consideRatio), [@mathbunnyru](https://github.com/mathbunnyru))
|
||||
@@ -81,6 +93,9 @@ In addition to these, thanks to contributions from this years Outreachy interns,
|
||||
|
||||
#### Documentation improvements
|
||||
|
||||
- Fix variable spelling. [#4398](https://github.com/jupyterhub/jupyterhub/pull/4398) ([@ryanlovett](https://github.com/ryanlovett), [@manics](https://github.com/manics))
|
||||
- Remove bracket around link text without address [#4416](https://github.com/jupyterhub/jupyterhub/pull/4416) ([@crazytan](https://github.com/crazytan), [@minrk](https://github.com/minrk))
|
||||
- add some more detail and examples to database doc [#4399](https://github.com/jupyterhub/jupyterhub/pull/4399) ([@minrk](https://github.com/minrk), [@consideRatio](https://github.com/consideRatio))
|
||||
- Add emphasis about role loading and hub restarts. [#4390](https://github.com/jupyterhub/jupyterhub/pull/4390) ([@ryanlovett](https://github.com/ryanlovett), [@minrk](https://github.com/minrk), [@consideRatio](https://github.com/consideRatio))
|
||||
- Re-enable links to REST API [#4386](https://github.com/jupyterhub/jupyterhub/pull/4386) ([@minrk](https://github.com/minrk), [@manics](https://github.com/manics))
|
||||
- reduce nested hierarchy in docs organization [#4377](https://github.com/jupyterhub/jupyterhub/pull/4377) ([@alwasega](https://github.com/alwasega), [@minrk](https://github.com/minrk))
|
||||
@@ -119,9 +134,9 @@ In addition to these, thanks to contributions from this years Outreachy interns,
|
||||
The following people contributed discussions, new ideas, code and documentation contributions, and review.
|
||||
See [our definition of contributors](https://github-activity.readthedocs.io/en/latest/#how-does-this-tool-define-contributions-in-the-reports).
|
||||
|
||||
([GitHub contributors page for this release](https://github.com/jupyterhub/jupyterhub/graphs/contributors?from=2022-12-05&to=2023-03-15&type=c))
|
||||
([GitHub contributors page for this release](https://github.com/jupyterhub/jupyterhub/graphs/contributors?from=2022-12-05&to=2023-04-20&type=c))
|
||||
|
||||
@3coins ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3A3coins+updated%3A2022-12-05..2023-03-15&type=Issues)) | @ajcollett ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aajcollett+updated%3A2022-12-05..2023-03-15&type=Issues)) | @ajpower ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aajpower+updated%3A2022-12-05..2023-03-15&type=Issues)) | @alwasega ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aalwasega+updated%3A2022-12-05..2023-03-15&type=Issues)) | @betatim ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Abetatim+updated%3A2022-12-05..2023-03-15&type=Issues)) | @bl-aire ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Abl-aire+updated%3A2022-12-05..2023-03-15&type=Issues)) | @choldgraf ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Acholdgraf+updated%3A2022-12-05..2023-03-15&type=Issues)) | @consideRatio ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3AconsideRatio+updated%3A2022-12-05..2023-03-15&type=Issues)) | @dependabot ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Adependabot+updated%3A2022-12-05..2023-03-15&type=Issues)) | @fperez ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Afperez+updated%3A2022-12-05..2023-03-15&type=Issues)) | @GeorgianaElena ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3AGeorgianaElena+updated%3A2022-12-05..2023-03-15&type=Issues)) | @julietKiloRomeo ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3AjulietKiloRomeo+updated%3A2022-12-05..2023-03-15&type=Issues)) | @ktaletsk ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aktaletsk+updated%3A2022-12-05..2023-03-15&type=Issues)) | @manics ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Amanics+updated%3A2022-12-05..2023-03-15&type=Issues)) | @mathbunnyru ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Amathbunnyru+updated%3A2022-12-05..2023-03-15&type=Issues)) | @meeseeksdev ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Ameeseeksdev+updated%3A2022-12-05..2023-03-15&type=Issues)) | @meeseeksmachine ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Ameeseeksmachine+updated%3A2022-12-05..2023-03-15&type=Issues)) | @minrk ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aminrk+updated%3A2022-12-05..2023-03-15&type=Issues)) | @mouse1203 ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Amouse1203+updated%3A2022-12-05..2023-03-15&type=Issues)) | @naatebarber ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Anaatebarber+updated%3A2022-12-05..2023-03-15&type=Issues)) | @pnasrat ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Apnasrat+updated%3A2022-12-05..2023-03-15&type=Issues)) | @pre-commit-ci ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Apre-commit-ci+updated%3A2022-12-05..2023-03-15&type=Issues)) | @ryanlovett ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aryanlovett+updated%3A2022-12-05..2023-03-15&type=Issues)) | @sgibson91 ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Asgibson91+updated%3A2022-12-05..2023-03-15&type=Issues)) | @shaneknapp ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Ashaneknapp+updated%3A2022-12-05..2023-03-15&type=Issues)) | @Sheila-nk ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3ASheila-nk+updated%3A2022-12-05..2023-03-15&type=Issues)) | @stevejpurves ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Astevejpurves+updated%3A2022-12-05..2023-03-15&type=Issues)) | @TaofeeqatDev ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3ATaofeeqatDev+updated%3A2022-12-05..2023-03-15&type=Issues)) | @vladfreeze ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Avladfreeze+updated%3A2022-12-05..2023-03-15&type=Issues)) | @yuvipanda ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Ayuvipanda+updated%3A2022-12-05..2023-03-15&type=Issues))
|
||||
@3coins ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3A3coins+updated%3A2022-12-05..2023-04-20&type=Issues)) | @ajcollett ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aajcollett+updated%3A2022-12-05..2023-04-20&type=Issues)) | @ajpower ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aajpower+updated%3A2022-12-05..2023-04-20&type=Issues)) | @alekseyolg ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aalekseyolg+updated%3A2022-12-05..2023-04-20&type=Issues)) | @alwasega ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aalwasega+updated%3A2022-12-05..2023-04-20&type=Issues)) | @betatim ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Abetatim+updated%3A2022-12-05..2023-04-20&type=Issues)) | @bl-aire ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Abl-aire+updated%3A2022-12-05..2023-04-20&type=Issues)) | @choldgraf ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Acholdgraf+updated%3A2022-12-05..2023-04-20&type=Issues)) | @consideRatio ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3AconsideRatio+updated%3A2022-12-05..2023-04-20&type=Issues)) | @crazytan ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Acrazytan+updated%3A2022-12-05..2023-04-20&type=Issues)) | @dependabot ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Adependabot+updated%3A2022-12-05..2023-04-20&type=Issues)) | @fperez ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Afperez+updated%3A2022-12-05..2023-04-20&type=Issues)) | @GeorgianaElena ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3AGeorgianaElena+updated%3A2022-12-05..2023-04-20&type=Issues)) | @julietKiloRomeo ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3AjulietKiloRomeo+updated%3A2022-12-05..2023-04-20&type=Issues)) | @ktaletsk ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aktaletsk+updated%3A2022-12-05..2023-04-20&type=Issues)) | @manics ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Amanics+updated%3A2022-12-05..2023-04-20&type=Issues)) | @mathbunnyru ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Amathbunnyru+updated%3A2022-12-05..2023-04-20&type=Issues)) | @meeseeksdev ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Ameeseeksdev+updated%3A2022-12-05..2023-04-20&type=Issues)) | @meeseeksmachine ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Ameeseeksmachine+updated%3A2022-12-05..2023-04-20&type=Issues)) | @minrk ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aminrk+updated%3A2022-12-05..2023-04-20&type=Issues)) | @mouse1203 ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Amouse1203+updated%3A2022-12-05..2023-04-20&type=Issues)) | @naatebarber ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Anaatebarber+updated%3A2022-12-05..2023-04-20&type=Issues)) | @pnasrat ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Apnasrat+updated%3A2022-12-05..2023-04-20&type=Issues)) | @pre-commit-ci ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Apre-commit-ci+updated%3A2022-12-05..2023-04-20&type=Issues)) | @ryanlovett ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aryanlovett+updated%3A2022-12-05..2023-04-20&type=Issues)) | @sgibson91 ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Asgibson91+updated%3A2022-12-05..2023-04-20&type=Issues)) | @shaneknapp ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Ashaneknapp+updated%3A2022-12-05..2023-04-20&type=Issues)) | @Sheila-nk ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3ASheila-nk+updated%3A2022-12-05..2023-04-20&type=Issues)) | @stevejpurves ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Astevejpurves+updated%3A2022-12-05..2023-04-20&type=Issues)) | @TaofeeqatDev ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3ATaofeeqatDev+updated%3A2022-12-05..2023-04-20&type=Issues)) | @vladfreeze ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Avladfreeze+updated%3A2022-12-05..2023-04-20&type=Issues)) | @yuvipanda ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Ayuvipanda+updated%3A2022-12-05..2023-04-20&type=Issues))
|
||||
|
||||
## 3.1
|
||||
|
||||
|
@@ -1,6 +1,6 @@
|
||||
# Event logging and telemetry
|
||||
|
||||
JupyterHub can be configured to record structured events from a running server using Jupyter's [Telemetry System]. The types of events that JupyterHub emits are defined by [JSON schemas] listed at the bottom of this [page].
|
||||
JupyterHub can be configured to record structured events from a running server using Jupyter's [Telemetry System]. The types of events that JupyterHub emits are defined by [JSON schemas] listed at the bottom of this page.
|
||||
|
||||
## How to emit events
|
||||
|
||||
|
@@ -37,7 +37,6 @@
|
||||
"react-dom": "^17.0.1",
|
||||
"react-icons": "^4.1.0",
|
||||
"react-multi-select-component": "^3.0.7",
|
||||
"react-object-table-viewer": "^1.0.7",
|
||||
"react-redux": "^7.2.2",
|
||||
"react-router": "^5.2.0",
|
||||
"react-router-dom": "^5.2.0",
|
||||
|
21
jsx/src/components/ReactObjectTableViewer/LICENSE
Normal file
21
jsx/src/components/ReactObjectTableViewer/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
The MIT License (MIT)
|
||||
|
||||
Copyright (c) 2021 jinkwon.lee<uzmystic@gmail.com>
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
@@ -0,0 +1,65 @@
|
||||
// Originally copied from
|
||||
// https://github.com/jinkwon/react-object-table-viewer/blob/f29827028fad547a0a17e044567cf1486849fb7a/src/ReactObjectTableViewer.tsx
|
||||
import React from "react";
|
||||
import PropTypes from "prop-types";
|
||||
|
||||
const ReactObjectTableViewer = (props) => {
|
||||
const opt = props;
|
||||
|
||||
const data = opt.data;
|
||||
const keys = Object.keys(data || {}) || [];
|
||||
|
||||
return (
|
||||
<table
|
||||
className={opt.className}
|
||||
style={{
|
||||
...opt.style,
|
||||
}}
|
||||
>
|
||||
<tbody>
|
||||
{keys.map((k, key) => {
|
||||
const val = data[k];
|
||||
const isObject = typeof val === "object";
|
||||
const isElement = React.isValidElement(val);
|
||||
|
||||
return (
|
||||
<tr key={key}>
|
||||
<th
|
||||
style={{
|
||||
...opt.keyStyle,
|
||||
}}
|
||||
>
|
||||
{k}
|
||||
</th>
|
||||
{isObject && (
|
||||
<td>
|
||||
{isElement && val}
|
||||
{!isElement && <ReactObjectTableViewer {...opt} data={val} />}
|
||||
</td>
|
||||
)}
|
||||
{!isObject && (
|
||||
<td
|
||||
style={{
|
||||
whiteSpace: "nowrap",
|
||||
...opt.valueStyle,
|
||||
}}
|
||||
>{`${val}`}</td>
|
||||
)}
|
||||
</tr>
|
||||
);
|
||||
})}
|
||||
</tbody>
|
||||
</table>
|
||||
);
|
||||
};
|
||||
|
||||
ReactObjectTableViewer.propTypes = {
|
||||
data: PropTypes.object,
|
||||
style: PropTypes.objectOf(PropTypes.string),
|
||||
keyStyle: PropTypes.objectOf(PropTypes.string),
|
||||
valueStyle: PropTypes.objectOf(PropTypes.string),
|
||||
className: PropTypes.string,
|
||||
layout: PropTypes.string,
|
||||
};
|
||||
|
||||
export default ReactObjectTableViewer;
|
@@ -1,4 +1,4 @@
|
||||
import React, { useEffect, useState } from "react";
|
||||
import React, { useEffect, useState, Fragment } from "react";
|
||||
import { useSelector, useDispatch } from "react-redux";
|
||||
import { debounce } from "lodash";
|
||||
import PropTypes from "prop-types";
|
||||
@@ -12,7 +12,7 @@ import {
|
||||
CardGroup,
|
||||
Collapse,
|
||||
} from "react-bootstrap";
|
||||
import ReactObjectTableViewer from "react-object-table-viewer";
|
||||
import ReactObjectTableViewer from "../ReactObjectTableViewer/ReactObjectTableViewer";
|
||||
|
||||
import { Link } from "react-router-dom";
|
||||
import { FaSort, FaSortUp, FaSortDown } from "react-icons/fa";
|
||||
@@ -29,6 +29,13 @@ const AccessServerButton = ({ url }) => (
|
||||
</a>
|
||||
);
|
||||
|
||||
const RowListItem = ({ text }) => (
|
||||
<span className="server-dashboard-row-list-item">{text}</span>
|
||||
);
|
||||
RowListItem.propTypes = {
|
||||
text: PropTypes.string,
|
||||
};
|
||||
|
||||
const ServerDashboard = (props) => {
|
||||
let base_url = window.base_url || "/";
|
||||
// sort methods
|
||||
@@ -236,8 +243,13 @@ const ServerDashboard = (props) => {
|
||||
break;
|
||||
}
|
||||
if (Array.isArray(value)) {
|
||||
// cast arrays (e.g. roles, groups) to string
|
||||
value = value.sort().join(", ");
|
||||
value = (
|
||||
<Fragment>
|
||||
{value.sort().flatMap((v) => (
|
||||
<RowListItem text={v} />
|
||||
))}
|
||||
</Fragment>
|
||||
);
|
||||
}
|
||||
result[key] = value;
|
||||
return result;
|
||||
|
@@ -30,3 +30,11 @@
|
||||
tr.noborder > td {
|
||||
border: none !important;
|
||||
}
|
||||
|
||||
.server-dashboard-row-list-item {
|
||||
display: inline-block;
|
||||
padding: 0 5px;
|
||||
margin: 2px;
|
||||
border: 1px solid #ddd;
|
||||
border-radius: 2px;
|
||||
}
|
||||
|
@@ -5963,7 +5963,7 @@ react-bootstrap@^2.1.1:
|
||||
uncontrollable "^7.2.1"
|
||||
warning "^4.0.3"
|
||||
|
||||
react-dom@17.0.2, react-dom@^17.0.1:
|
||||
react-dom@^17.0.1:
|
||||
version "17.0.2"
|
||||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23"
|
||||
integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==
|
||||
@@ -6004,14 +6004,6 @@ react-multi-select-component@^3.0.7:
|
||||
dependencies:
|
||||
goober "^2.0.30"
|
||||
|
||||
react-object-table-viewer@^1.0.7:
|
||||
version "1.0.7"
|
||||
resolved "https://registry.yarnpkg.com/react-object-table-viewer/-/react-object-table-viewer-1.0.7.tgz#31816021fa4526641c6b66bd9433ec9b78c2e472"
|
||||
integrity sha512-OezCet8+BmEdJJHO5WGPFPRWXxw4Ls6HsV4Uh1kRPlmRXLOTNqWt/ZHmH8NhTl1BA9HkdhEegKVqc2b61wDMLg==
|
||||
dependencies:
|
||||
react "^17.0.2"
|
||||
react-dom "17.0.2"
|
||||
|
||||
react-redux@^7.2.2:
|
||||
version "7.2.8"
|
||||
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.8.tgz#a894068315e65de5b1b68899f9c6ee0923dd28de"
|
||||
@@ -6081,7 +6073,7 @@ react-transition-group@^4.4.2:
|
||||
loose-envify "^1.4.0"
|
||||
prop-types "^15.6.2"
|
||||
|
||||
react@^17.0.1, react@^17.0.2:
|
||||
react@^17.0.1:
|
||||
version "17.0.2"
|
||||
resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"
|
||||
integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==
|
||||
|
@@ -2,7 +2,7 @@
|
||||
# Copyright (c) Jupyter Development Team.
|
||||
# Distributed under the terms of the Modified BSD License.
|
||||
# version_info updated by running `tbump`
|
||||
version_info = (4, 0, 0, "b2", "")
|
||||
version_info = (4, 0, 0, "", "")
|
||||
|
||||
# pep 440 version: no dot before beta/rc, but before .dev
|
||||
# 0.1.0rc1
|
||||
|
@@ -52,7 +52,7 @@ def get_default_roles():
|
||||
'description': 'Post activity only',
|
||||
'scopes': [
|
||||
'users:activity!user',
|
||||
'access:servers!user',
|
||||
'access:servers!server',
|
||||
],
|
||||
},
|
||||
{
|
||||
|
@@ -382,6 +382,23 @@ class Spawner(LoggingConfigurable):
|
||||
scopes.append(f"access:servers!server={self.user.name}/{self.name}")
|
||||
return sorted(set(scopes))
|
||||
|
||||
server_token_scopes = Union(
|
||||
[List(Unicode()), Callable()],
|
||||
help="""The list of scopes to request for $JUPYTERHUB_API_TOKEN
|
||||
|
||||
If not specified, the scopes in the `server` role will be used
|
||||
(unchanged from pre-4.0).
|
||||
|
||||
If callable, will be called with the Spawner instance as its sole argument
|
||||
(JupyterHub user available as spawner.user).
|
||||
|
||||
JUPYTERHUB_API_TOKEN will be assigned the _subset_ of these scopes
|
||||
that are held by the user (as in oauth_client_allowed_scopes).
|
||||
|
||||
.. versionadded:: 4.0
|
||||
""",
|
||||
).tag(config=True)
|
||||
|
||||
will_resume = Bool(
|
||||
False,
|
||||
help="""Whether the Spawner will resume on next start
|
||||
|
@@ -35,7 +35,7 @@ def generate_old_db(env_dir, hub_version, db_url):
|
||||
pkgs.append('sqlalchemy<2')
|
||||
|
||||
if 'mysql' in db_url:
|
||||
pkgs.append('mysql-connector-python')
|
||||
pkgs.append('mysqlclient')
|
||||
elif 'postgres' in db_url:
|
||||
pkgs.append('psycopg2-binary')
|
||||
check_call([env_pip, 'install'] + pkgs)
|
||||
|
@@ -848,8 +848,12 @@ async def test_server_token_role(app):
|
||||
orm_server_token = orm.APIToken.find(app.db, server_token)
|
||||
assert orm_server_token
|
||||
|
||||
server_role = orm.Role.find(app.db, 'server')
|
||||
assert set(server_role.scopes) == set(orm_server_token.scopes)
|
||||
# resolve `!server` filter in server role
|
||||
server_role_scopes = {
|
||||
s.replace("!server", f"!server={user.name}/")
|
||||
for s in orm.Role.find(app.db, "server").scopes
|
||||
}
|
||||
assert set(orm_server_token.scopes) == server_role_scopes
|
||||
|
||||
assert orm_server_token.user.name == user.name
|
||||
assert user.api_tokens == [orm_server_token]
|
||||
|
@@ -20,7 +20,7 @@ from ..objects import Hub, Server
|
||||
from ..scopes import access_scopes
|
||||
from ..spawner import LocalProcessSpawner, Spawner
|
||||
from ..user import User
|
||||
from ..utils import AnyTimeoutError, new_token, url_path_join
|
||||
from ..utils import AnyTimeoutError, maybe_future, new_token, url_path_join
|
||||
from .mocking import public_url
|
||||
from .test_api import add_user
|
||||
from .utils import async_requests
|
||||
@@ -336,6 +336,12 @@ async def test_spawner_insert_api_token(app):
|
||||
assert found
|
||||
assert found.user.name == user.name
|
||||
assert user.api_tokens == [found]
|
||||
# resolve `!server` filter in server role
|
||||
server_role_scopes = {
|
||||
s.replace("!server", f"!server={user.name}/")
|
||||
for s in orm.Role.find(app.db, "server").scopes
|
||||
}
|
||||
assert set(found.scopes) == server_role_scopes
|
||||
await user.stop()
|
||||
|
||||
|
||||
@@ -361,6 +367,58 @@ async def test_spawner_bad_api_token(app):
|
||||
assert other_user.api_tokens == []
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"have_scopes, request_scopes, expected_scopes",
|
||||
[
|
||||
(["self"], ["inherit"], ["inherit"]),
|
||||
(["self"], [], ["access:servers!server=USER/", "users:activity!user"]),
|
||||
(
|
||||
["self"],
|
||||
["admin:groups", "read:servers!server"],
|
||||
["users:activity!user", "read:servers!server=USER/"],
|
||||
),
|
||||
(
|
||||
["self", "read:groups!group=x", "users:activity"],
|
||||
["admin:groups", "users:activity"],
|
||||
["read:groups!group=x", "read:groups:name!group=x", "users:activity"],
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_server_token_scopes(
|
||||
app, username, create_user_with_scopes, have_scopes, request_scopes, expected_scopes
|
||||
):
|
||||
"""Token provided by spawner is not in the db
|
||||
|
||||
Insert token into db as a user-provided token.
|
||||
"""
|
||||
db = app.db
|
||||
|
||||
# apply templating
|
||||
def _format_scopes(scopes):
|
||||
if callable(scopes):
|
||||
|
||||
async def get_scopes(*args):
|
||||
return _format_scopes(await maybe_future(scopes(*args)))
|
||||
|
||||
return get_scopes
|
||||
|
||||
return [s.replace("USER", username) for s in scopes]
|
||||
|
||||
have_scopes = _format_scopes(have_scopes)
|
||||
request_scopes = _format_scopes(request_scopes)
|
||||
expected_scopes = _format_scopes(expected_scopes)
|
||||
|
||||
user = create_user_with_scopes(*have_scopes, name=username)
|
||||
spawner = user.spawner
|
||||
spawner.server_token_scopes = request_scopes
|
||||
|
||||
await user.spawn()
|
||||
orm_token = orm.APIToken.find(db, spawner.api_token)
|
||||
assert orm_token
|
||||
assert set(orm_token.scopes) == set(expected_scopes)
|
||||
await user.stop()
|
||||
|
||||
|
||||
async def test_spawner_delete_server(app):
|
||||
"""Test deleting spawner.server
|
||||
|
||||
|
@@ -53,3 +53,16 @@ def test_sync_groups(app, user, group_names):
|
||||
assert user.orm_user in group.users
|
||||
else:
|
||||
assert user.orm_user not in group.users
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"server_name, path",
|
||||
[
|
||||
("", ""),
|
||||
("name", "name/"),
|
||||
("næme", "n%C3%A6me/"),
|
||||
],
|
||||
)
|
||||
def test_server_url(app, user, server_name, path):
|
||||
user_url = user.url
|
||||
assert user.server_url(server_name) == user_url + path
|
||||
|
@@ -13,7 +13,7 @@ from tornado import gen, web
|
||||
from tornado.httputil import urlencode
|
||||
from tornado.log import app_log
|
||||
|
||||
from . import orm
|
||||
from . import orm, roles, scopes
|
||||
from ._version import __version__, _check_version
|
||||
from .crypto import CryptKeeper, EncryptionUnavailable, InvalidToken, decrypt, encrypt
|
||||
from .metrics import RUNNING_SERVERS, TOTAL_USERS
|
||||
@@ -588,7 +588,7 @@ class User:
|
||||
if not server_name:
|
||||
return self.url
|
||||
else:
|
||||
return url_path_join(self.url, url_escape_path(server_name))
|
||||
return url_path_join(self.url, url_escape_path(server_name), "/")
|
||||
|
||||
def progress_url(self, server_name=''):
|
||||
"""API URL for progress endpoint for a server with a given name"""
|
||||
@@ -673,13 +673,63 @@ class User:
|
||||
orm_server = orm.Server(base_url=base_url)
|
||||
db.add(orm_server)
|
||||
note = "Server at %s" % base_url
|
||||
api_token = self.new_api_token(note=note, roles=['server'])
|
||||
db.commit()
|
||||
|
||||
spawner = self.get_spawner(server_name, replace_failed=True)
|
||||
spawner.server = server = Server(orm_server=orm_server)
|
||||
assert spawner.orm_spawner.server is orm_server
|
||||
|
||||
requested_scopes = spawner.server_token_scopes
|
||||
if callable(requested_scopes):
|
||||
requested_scopes = await maybe_future(requested_scopes(spawner))
|
||||
if not requested_scopes:
|
||||
# nothing requested, default to 'server' role
|
||||
requested_scopes = orm.Role.find(db, "server").scopes
|
||||
requested_scopes = set(requested_scopes)
|
||||
# resolve !server filter, which won't resolve elsewhere,
|
||||
# because this token is not owned by the server's own oauth client
|
||||
server_filter = f"={self.name}/{server_name}"
|
||||
requested_scopes = {
|
||||
scope + server_filter if scope.endswith("!server") else scope
|
||||
for scope in requested_scopes
|
||||
}
|
||||
# ensure activity scope is requested, since activity doesn't work without
|
||||
activity_scope = "users:activity!user"
|
||||
if not {activity_scope, "users:activity", "inherit"}.intersection(
|
||||
requested_scopes
|
||||
):
|
||||
self.log.warning(
|
||||
f"Adding required scope {activity_scope} to server token, missing from Spawner.server_token_scopes. Please make sure to add it!"
|
||||
)
|
||||
requested_scopes |= {activity_scope}
|
||||
|
||||
have_scopes = roles.roles_to_scopes(roles.get_roles_for(self.orm_user))
|
||||
have_scopes |= {"inherit"}
|
||||
jupyterhub_client = (
|
||||
db.query(orm.OAuthClient)
|
||||
.filter_by(
|
||||
identifier="jupyterhub",
|
||||
)
|
||||
.one()
|
||||
)
|
||||
|
||||
resolved_scopes, excluded_scopes = scopes._resolve_requested_scopes(
|
||||
requested_scopes, have_scopes, self.orm_user, jupyterhub_client, db
|
||||
)
|
||||
if excluded_scopes:
|
||||
# what level should this be?
|
||||
# for admins-get-more use case, this is going to happen for most users
|
||||
# but for misconfiguration, folks will want to know!
|
||||
self.log.debug(
|
||||
"Not assigning requested scopes for %s: requested=%s, assigned=%s, excluded=%s",
|
||||
spawner._log_name,
|
||||
requested_scopes,
|
||||
resolved_scopes,
|
||||
excluded_scopes,
|
||||
)
|
||||
|
||||
api_token = self.new_api_token(note=note, scopes=resolved_scopes)
|
||||
|
||||
# pass requesting handler to the spawner
|
||||
# e.g. for processing GET params
|
||||
spawner.handler = handler
|
||||
@@ -808,6 +858,7 @@ class User:
|
||||
spawner.api_token,
|
||||
generated=False,
|
||||
note="retrieved from spawner %s" % server_name,
|
||||
scopes=resolved_scopes,
|
||||
)
|
||||
# update OAuth client secret with updated API token
|
||||
if oauth_provider:
|
||||
|
@@ -43,7 +43,7 @@ target_version = [
|
||||
github_url = "https://github.com/jupyterhub/jupyterhub"
|
||||
|
||||
[tool.tbump.version]
|
||||
current = "4.0.0b2"
|
||||
current = "4.0.0"
|
||||
|
||||
# Example of a semver regexp.
|
||||
# Make sure this matches current_version before
|
||||
|
Reference in New Issue
Block a user