mirror of
https://github.com/jupyterhub/jupyterhub.git
synced 2025-10-08 18:44:10 +00:00
Compare commits
85 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 | ||
![]() |
7793176b65 | ||
![]() |
bf32599d5d | ||
![]() |
01a31c894c | ||
![]() |
1e9cf23302 | ||
![]() |
555969141e | ||
![]() |
a938982bdc | ||
![]() |
17b54fee6a | ||
![]() |
60a153718d | ||
![]() |
9e1e382c37 | ||
![]() |
d72a96ec17 | ||
![]() |
5f845e78f1 | ||
![]() |
0d7e608a64 | ||
![]() |
15c5f152f8 | ||
![]() |
6d13893f16 | ||
![]() |
7e35de2577 | ||
![]() |
ec78503d1e | ||
![]() |
7d0bc1a112 | ||
![]() |
98e4531b44 | ||
![]() |
bb92058fbf | ||
![]() |
a5c59d6550 | ||
![]() |
f14be3df65 | ||
![]() |
3f7a32c990 | ||
![]() |
a8d8fc02e7 | ||
![]() |
0713fa209e | ||
![]() |
850f430ad6 | ||
![]() |
4026ed87e8 | ||
![]() |
f57d196e33 | ||
![]() |
ca9dc3a179 |
@@ -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:
|
# Notes:
|
||||||
# - Status and logs from dependabot are provided at
|
# - Status and logs from dependabot are provided at
|
||||||
@@ -8,8 +8,9 @@ version: 2
|
|||||||
updates:
|
updates:
|
||||||
# Maintain dependencies in our GitHub Workflows
|
# Maintain dependencies in our GitHub Workflows
|
||||||
- package-ecosystem: github-actions
|
- package-ecosystem: github-actions
|
||||||
directory: "/"
|
directory: /
|
||||||
|
labels: [ci]
|
||||||
schedule:
|
schedule:
|
||||||
interval: weekly
|
interval: monthly
|
||||||
time: "05:00"
|
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
|
runs-on: ubuntu-20.04
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- 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
|
- uses: actions/setup-python@v4
|
||||||
with:
|
with:
|
||||||
python-version: "3.9"
|
python-version: "3.9"
|
||||||
@@ -72,3 +77,31 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cd docs
|
cd docs
|
||||||
make linkcheck
|
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
|
fi
|
||||||
if [ "${{ matrix.db }}" == "mysql" ]; then
|
if [ "${{ matrix.db }}" == "mysql" ]; then
|
||||||
echo "MYSQL_HOST=127.0.0.1" >> $GITHUB_ENV
|
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
|
fi
|
||||||
if [ "${{ matrix.ssl }}" == "ssl" ]; then
|
if [ "${{ matrix.ssl }}" == "ssl" ]; then
|
||||||
echo "SSL_ENABLED=1" >> $GITHUB_ENV
|
echo "SSL_ENABLED=1" >> $GITHUB_ENV
|
||||||
@@ -164,7 +164,9 @@ jobs:
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
if [ "${{ matrix.main_dependencies }}" != "" ]; then
|
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
|
pip install --upgrade --pre sqlalchemy
|
||||||
fi
|
fi
|
||||||
if [ "${{ matrix.legacy_notebook }}" != "" ]; then
|
if [ "${{ matrix.legacy_notebook }}" != "" ]; then
|
||||||
@@ -175,7 +177,7 @@ jobs:
|
|||||||
pip install "jupyter_server==${{ matrix.jupyter_server }}"
|
pip install "jupyter_server==${{ matrix.jupyter_server }}"
|
||||||
fi
|
fi
|
||||||
if [ "${{ matrix.db }}" == "mysql" ]; then
|
if [ "${{ matrix.db }}" == "mysql" ]; then
|
||||||
pip install mysql-connector-python
|
pip install mysqlclient
|
||||||
fi
|
fi
|
||||||
if [ "${{ matrix.db }}" == "postgres" ]; then
|
if [ "${{ matrix.db }}" == "postgres" ]; then
|
||||||
pip install psycopg2-binary
|
pip install psycopg2-binary
|
||||||
@@ -246,7 +248,7 @@ jobs:
|
|||||||
|
|
||||||
- name: build images
|
- name: build images
|
||||||
run: |
|
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-onbuild onbuild
|
||||||
docker build -t jupyterhub/jupyterhub:alpine -f dockerfiles/Dockerfile.alpine .
|
docker build -t jupyterhub/jupyterhub:alpine -f dockerfiles/Dockerfile.alpine .
|
||||||
docker build -t jupyterhub/singleuser singleuser
|
docker build -t jupyterhub/singleuser singleuser
|
||||||
|
@@ -24,7 +24,7 @@ repos:
|
|||||||
|
|
||||||
# Autoformat: Python code
|
# Autoformat: Python code
|
||||||
- repo: https://github.com/PyCQA/autoflake
|
- repo: https://github.com/PyCQA/autoflake
|
||||||
rev: v2.0.1
|
rev: v2.0.2
|
||||||
hooks:
|
hooks:
|
||||||
- id: autoflake
|
- id: autoflake
|
||||||
# args ref: https://github.com/PyCQA/autoflake#advanced-usage
|
# args ref: https://github.com/PyCQA/autoflake#advanced-usage
|
||||||
@@ -39,13 +39,13 @@ repos:
|
|||||||
|
|
||||||
# Autoformat: Python code
|
# Autoformat: Python code
|
||||||
- repo: https://github.com/psf/black
|
- repo: https://github.com/psf/black
|
||||||
rev: 23.1.0
|
rev: 23.3.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: black
|
- id: black
|
||||||
|
|
||||||
# Autoformat: markdown, yaml, javascript (see the file .prettierignore)
|
# Autoformat: markdown, yaml, javascript (see the file .prettierignore)
|
||||||
- repo: https://github.com/pre-commit/mirrors-prettier
|
- repo: https://github.com/pre-commit/mirrors-prettier
|
||||||
rev: v3.0.0-alpha.4
|
rev: v3.0.0-alpha.6
|
||||||
hooks:
|
hooks:
|
||||||
- id: prettier
|
- id: prettier
|
||||||
|
|
||||||
|
94
Dockerfile
94
Dockerfile
@@ -24,11 +24,11 @@
|
|||||||
ARG BASE_IMAGE=ubuntu:22.04
|
ARG BASE_IMAGE=ubuntu:22.04
|
||||||
FROM $BASE_IMAGE AS builder
|
FROM $BASE_IMAGE AS builder
|
||||||
|
|
||||||
USER root
|
ENV DEBIAN_FRONTEND=noninteractive
|
||||||
|
WORKDIR /src/jupyterhub
|
||||||
|
|
||||||
ENV DEBIAN_FRONTEND noninteractive
|
RUN apt update -q \
|
||||||
RUN apt-get update \
|
&& apt install -yq --no-install-recommends \
|
||||||
&& apt-get install -yq --no-install-recommends \
|
|
||||||
build-essential \
|
build-essential \
|
||||||
ca-certificates \
|
ca-certificates \
|
||||||
locales \
|
locales \
|
||||||
@@ -38,66 +38,54 @@ RUN apt-get update \
|
|||||||
python3-venv \
|
python3-venv \
|
||||||
nodejs \
|
nodejs \
|
||||||
npm \
|
npm \
|
||||||
&& apt-get clean \
|
&& apt clean \
|
||||||
&& rm -rf /var/lib/apt/lists/*
|
&& rm -rf /var/lib/apt/lists/* \
|
||||||
|
&& python3 -m pip install --no-cache-dir --upgrade setuptools pip build wheel \
|
||||||
RUN python3 -m pip install --upgrade setuptools pip build wheel
|
&& npm install --global yarn
|
||||||
RUN npm install --global yarn
|
|
||||||
|
|
||||||
# copy everything except whats in .dockerignore, its a
|
# copy everything except whats in .dockerignore, its a
|
||||||
# compromise between needing to rebuild and maintaining
|
# compromise between needing to rebuild and maintaining
|
||||||
# what needs to be part of the build
|
# what needs to be part of the build
|
||||||
COPY . /src/jupyterhub/
|
COPY . .
|
||||||
WORKDIR /src/jupyterhub
|
ARG PIP_CACHE_DIR=/tmp/pip-cache
|
||||||
|
RUN --mount=type=cache,target=${PIP_CACHE_DIR} \
|
||||||
# Build client component packages (they will be copied into ./share and
|
python3 -m build --wheel \
|
||||||
# packaged with the built wheel.)
|
&& python3 -m pip wheel --wheel-dir wheelhouse dist/*.whl
|
||||||
RUN python3 -m build --wheel
|
|
||||||
RUN python3 -m pip wheel --wheel-dir wheelhouse dist/*.whl
|
|
||||||
|
|
||||||
|
|
||||||
FROM $BASE_IMAGE
|
FROM $BASE_IMAGE
|
||||||
|
ENV DEBIAN_FRONTEND=noninteractive \
|
||||||
USER root
|
SHELL=/bin/bash \
|
||||||
|
|
||||||
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 \
|
|
||||||
LC_ALL=en_US.UTF-8 \
|
LC_ALL=en_US.UTF-8 \
|
||||||
LANG=en_US.UTF-8 \
|
LANG=en_US.UTF-8 \
|
||||||
LANGUAGE=en_US.UTF-8
|
LANGUAGE=en_US.UTF-8 \
|
||||||
|
PYTHONDONTWRITEBYTECODE=1
|
||||||
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/
|
|
||||||
|
|
||||||
EXPOSE 8000
|
EXPOSE 8000
|
||||||
|
|
||||||
LABEL maintainer="Jupyter Project <jupyter@googlegroups.com>"
|
LABEL maintainer="Jupyter Project <jupyter@googlegroups.com>"
|
||||||
LABEL org.jupyter.service="jupyterhub"
|
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"]
|
CMD ["jupyterhub"]
|
||||||
|
@@ -12,7 +12,7 @@ print(f"DATA_FILES_PATH={DATA_FILES_PATH}", end=" ")
|
|||||||
DATA_FILES_PATH = Path(DATA_FILES_PATH)
|
DATA_FILES_PATH = Path(DATA_FILES_PATH)
|
||||||
assert DATA_FILES_PATH.is_dir(), DATA_FILES_PATH
|
assert DATA_FILES_PATH.is_dir(), DATA_FILES_PATH
|
||||||
for subpath in (
|
for subpath in (
|
||||||
"templates/page.html",
|
"templates/spawn.html",
|
||||||
"static/css/style.min.css",
|
"static/css/style.min.css",
|
||||||
"static/components/jquery/dist/jquery.js",
|
"static/components/jquery/dist/jquery.js",
|
||||||
"static/js/admin-react.js",
|
"static/js/admin-react.js",
|
||||||
@@ -28,6 +28,7 @@ for subpath in (
|
|||||||
"alembic.ini",
|
"alembic.ini",
|
||||||
"alembic/versions/833da8570507_rbac.py",
|
"alembic/versions/833da8570507_rbac.py",
|
||||||
"event-schemas/server-actions/v1.yaml",
|
"event-schemas/server-actions/v1.yaml",
|
||||||
|
"singleuser/templates/page.html",
|
||||||
):
|
):
|
||||||
path = jupyterhub_path / subpath
|
path = jupyterhub_path / subpath
|
||||||
assert path.is_file(), path
|
assert path.is_file(), path
|
||||||
|
@@ -6,7 +6,7 @@ info:
|
|||||||
description: The REST API for JupyterHub
|
description: The REST API for JupyterHub
|
||||||
license:
|
license:
|
||||||
name: BSD-3-Clause
|
name: BSD-3-Clause
|
||||||
version: 4.0.0b1
|
version: 4.0.0
|
||||||
servers:
|
servers:
|
||||||
- url: /hub/api
|
- url: /hub/api
|
||||||
security:
|
security:
|
||||||
|
@@ -50,6 +50,7 @@ myst_heading_anchors = 2
|
|||||||
|
|
||||||
myst_enable_extensions = [
|
myst_enable_extensions = [
|
||||||
# available extensions: https://myst-parser.readthedocs.io/en/latest/syntax/optional.html
|
# available extensions: https://myst-parser.readthedocs.io/en/latest/syntax/optional.html
|
||||||
|
"attrs_inline",
|
||||||
"colon_fence",
|
"colon_fence",
|
||||||
"deflist",
|
"deflist",
|
||||||
"fieldlist",
|
"fieldlist",
|
||||||
@@ -185,6 +186,7 @@ linkcheck_ignore = [
|
|||||||
"https://github.com/jupyterhub/jupyterhub/pull/", # too many PRs in changelog
|
"https://github.com/jupyterhub/jupyterhub/pull/", # too many PRs in changelog
|
||||||
"https://github.com/jupyterhub/jupyterhub/compare/", # too many comparisons in changelog
|
"https://github.com/jupyterhub/jupyterhub/compare/", # too many comparisons in changelog
|
||||||
r"https?://(localhost|127.0.0.1).*", # ignore localhost references in auto-links
|
r"https?://(localhost|127.0.0.1).*", # ignore localhost references in auto-links
|
||||||
|
r".*/rest-api.html#.*", # ignore javascript-resolved internal rest-api links
|
||||||
r"https://jupyter.chameleoncloud.org", # FIXME: ignore (presumably) short-term SSL issue
|
r"https://jupyter.chameleoncloud.org", # FIXME: ignore (presumably) short-term SSL issue
|
||||||
]
|
]
|
||||||
linkcheck_anchors_ignore = [
|
linkcheck_anchors_ignore = [
|
||||||
@@ -233,8 +235,12 @@ ogp_use_first_image = True
|
|||||||
# If you are basing changes off another branch/ commit, always change back
|
# If you are basing changes off another branch/ commit, always change back
|
||||||
# rediraffe_branch to main before pushing your changes upstream.
|
# rediraffe_branch to main before pushing your changes upstream.
|
||||||
#
|
#
|
||||||
rediraffe_branch = "main"
|
rediraffe_branch = os.environ.get("REDIRAFFE_BRANCH", "main")
|
||||||
rediraffe_redirects = "redirects.txt"
|
rediraffe_redirects = "redirects.txt"
|
||||||
|
|
||||||
|
# allow 80% match for autogenerated redirects
|
||||||
|
rediraffe_auto_redirect_perc = 80
|
||||||
|
|
||||||
# rediraffe_redirects = {
|
# rediraffe_redirects = {
|
||||||
# "old-file": "new-folder/new-file-name",
|
# "old-file": "new-folder/new-file-name",
|
||||||
# }
|
# }
|
||||||
|
@@ -82,7 +82,7 @@ Additionally, there is usually _very_ little load on the database itself.
|
|||||||
By far the most taxing activity on the database is the 'list all users' endpoint, primarily used by the [idle-culling service](https://github.com/jupyterhub/jupyterhub-idle-culler).
|
By far the most taxing activity on the database is the 'list all users' endpoint, primarily used by the [idle-culling service](https://github.com/jupyterhub/jupyterhub-idle-culler).
|
||||||
Database-based optimizations have been added to make even these operations feasible for large numbers of users:
|
Database-based optimizations have been added to make even these operations feasible for large numbers of users:
|
||||||
|
|
||||||
1. State filtering on [GET /users](jupyterhub-rest-API) with `?state=active`,
|
1. State filtering on [GET /hub/api/users?state=active](../reference/rest-api.html#/default/get_users){.external},
|
||||||
which limits the number of results in the query to only the relevant subset (added in JupyterHub 1.3), rather than all users.
|
which limits the number of results in the query to only the relevant subset (added in JupyterHub 1.3), rather than all users.
|
||||||
2. [Pagination](api-pagination) of all list endpoints, allowing the request of a large number of resources to be more fairly balanced with other Hub activities across multiple requests (added in 2.0).
|
2. [Pagination](api-pagination) of all list endpoints, allowing the request of a large number of resources to be more fairly balanced with other Hub activities across multiple requests (added in 2.0).
|
||||||
|
|
||||||
@@ -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][].
|
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.
|
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
|
[sqlalchemy]: https://www.sqlalchemy.org
|
||||||
|
|
||||||
### Default backend: SQLite
|
### 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
|
- `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
|
- `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
|
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).
|
where traditional RDBMS may be a better choice](https://sqlite.org/whentouse.html).
|
||||||
|
|
||||||
### Picking your database backend (PostgreSQL, MySQL)
|
### 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
|
## Notes and Tips
|
||||||
|
|
||||||
@@ -132,14 +140,25 @@ multiple processes which might try to access the file at the same time.
|
|||||||
### PostgreSQL
|
### PostgreSQL
|
||||||
|
|
||||||
We recommend using PostgreSQL for production if you are unsure whether to use
|
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
|
MySQL or PostgreSQL or if you do not have a strong preference.
|
||||||
additional configuration required for MySQL that is not needed for PostgreSQL.
|
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
|
### MySQL / MariaDB
|
||||||
|
|
||||||
- You should use the `pymysql` sqlalchemy provider (the other one, MySQLdb,
|
- 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)
|
||||||
isn't available for py3).
|
- You also need to set `pool_recycle` to some value (typically 60 - 300, JupyterHub will default to 60)
|
||||||
- You also need to set `pool_recycle` to some value (typically 60 - 300)
|
|
||||||
which depends on your MySQL setup. This is necessary since MySQL kills
|
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
|
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
|
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
|
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
|
default, as well as have a default `DYNAMIC` `row_format` and pose no trouble
|
||||||
to users.
|
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
|
# 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.
|
# For more information, see rediraffe configuration in the conf.py file.
|
||||||
|
|
||||||
|
|
||||||
"changelog.md" "reference/changelog.md"
|
"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/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/api-only.md" "howto/api-only.md"
|
||||||
"reference/config-ghoauth.md" "howto/configuration/config-ghoauth.md"
|
"reference/config-ghoauth.md" "howto/configuration/config-ghoauth.md"
|
||||||
"reference/config-proxy.md" "howto/configuration/config-proxy.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/proxy.md" "howto/proxy.md"
|
||||||
"reference/templates.md" "howto/templates.md"
|
"reference/templates.md" "howto/templates.md"
|
||||||
"quickstart-docker.md" "tutorial/quickstart-docker.md"
|
|
||||||
"reference/config-examples.md" "howto/index.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-sudo.md" "howto/configuration/config-sudo.md"
|
||||||
"reference/config-user-env.md" "howto/configuration/config-user-env.md"
|
"reference/config-user-env.md" "howto/configuration/config-user-env.md"
|
||||||
"reference/rest.md" "howto/rest.md"
|
"reference/rest.md" "howto/rest.md"
|
||||||
"reference/separate-proxy.md" "howto/separate-proxy.md"
|
"reference/separate-proxy.md" "howto/separate-proxy.md"
|
||||||
"admin/upgrading.md" "howto/upgrading.md"
|
"reference/server-api.md" "tutorial/server-api.md"
|
||||||
"installation-basics.md" "tutorial/installation-basics.md"
|
"reference/websecurity.md" "explanation/websecurity.md"
|
||||||
"quickstart.md" "tutorial/quickstart.md"
|
|
||||||
"events/index.md" "reference/event-logging.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.
|
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:
|
You can set the config:
|
||||||
|
|
||||||
|
@@ -8,7 +8,9 @@ command line for details.
|
|||||||
|
|
||||||
## [Unreleased]
|
## [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.
|
4.0 is a major release, but a small one.
|
||||||
There are three major changes that _should_ be invisible to most users:
|
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.
|
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
|
#### API and Breaking Changes
|
||||||
|
|
||||||
@@ -33,18 +35,23 @@ In addition to these, thanks to contributions from this years Outreachy interns,
|
|||||||
|
|
||||||
#### New features added
|
#### 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))
|
- 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))
|
- 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))
|
- 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
|
#### 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))
|
- 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))
|
||||||
- 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))
|
|
||||||
- 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))
|
- 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
|
#### 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))
|
- 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))
|
||||||
- avoid logging error when browsers send invalid cookies [#4356](https://github.com/jupyterhub/jupyterhub/pull/4356) ([@minrk](https://github.com/minrk), [@manics](https://github.com/manics), [@consideRatio](https://github.com/consideRatio))
|
- avoid logging error when browsers send invalid cookies [#4356](https://github.com/jupyterhub/jupyterhub/pull/4356) ([@minrk](https://github.com/minrk), [@manics](https://github.com/manics), [@consideRatio](https://github.com/consideRatio))
|
||||||
- test and fix deprecated load_groups list [#4299](https://github.com/jupyterhub/jupyterhub/pull/4299) ([@minrk](https://github.com/minrk), [@manics](https://github.com/manics))
|
- test and fix deprecated load_groups list [#4299](https://github.com/jupyterhub/jupyterhub/pull/4299) ([@minrk](https://github.com/minrk), [@manics](https://github.com/manics))
|
||||||
@@ -55,6 +62,16 @@ In addition to these, thanks to contributions from this years Outreachy interns,
|
|||||||
|
|
||||||
#### Maintenance and upkeep improvements
|
#### 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))
|
||||||
- temporary fix: pin base-notebook tag [#4348](https://github.com/jupyterhub/jupyterhub/pull/4348) ([@minrk](https://github.com/minrk))
|
- temporary fix: pin base-notebook tag [#4348](https://github.com/jupyterhub/jupyterhub/pull/4348) ([@minrk](https://github.com/minrk))
|
||||||
- simplify some async fixtures [#4332](https://github.com/jupyterhub/jupyterhub/pull/4332) ([@minrk](https://github.com/minrk), [@GeorgianaElena](https://github.com/GeorgianaElena), [@Sheila-nk](https://github.com/Sheila-nk))
|
- simplify some async fixtures [#4332](https://github.com/jupyterhub/jupyterhub/pull/4332) ([@minrk](https://github.com/minrk), [@GeorgianaElena](https://github.com/GeorgianaElena), [@Sheila-nk](https://github.com/Sheila-nk))
|
||||||
- Selenium: adding new cases that covered Admin UI page [#4328](https://github.com/jupyterhub/jupyterhub/pull/4328) ([@mouse1203](https://github.com/mouse1203), [@minrk](https://github.com/minrk))
|
- Selenium: adding new cases that covered Admin UI page [#4328](https://github.com/jupyterhub/jupyterhub/pull/4328) ([@mouse1203](https://github.com/mouse1203), [@minrk](https://github.com/minrk))
|
||||||
@@ -76,9 +93,19 @@ In addition to these, thanks to contributions from this years Outreachy interns,
|
|||||||
|
|
||||||
#### Documentation improvements
|
#### 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))
|
||||||
|
- changelog for 4.0 beta [#4375](https://github.com/jupyterhub/jupyterhub/pull/4375) ([@minrk](https://github.com/minrk), [@consideRatio](https://github.com/consideRatio))
|
||||||
- Getting started link broken [#4374](https://github.com/jupyterhub/jupyterhub/pull/4374) ([@3coins](https://github.com/3coins), [@minrk](https://github.com/minrk))
|
- Getting started link broken [#4374](https://github.com/jupyterhub/jupyterhub/pull/4374) ([@3coins](https://github.com/3coins), [@minrk](https://github.com/minrk))
|
||||||
|
- add collaboration accounts tutorial [#4373](https://github.com/jupyterhub/jupyterhub/pull/4373) ([@minrk](https://github.com/minrk), [@fperez](https://github.com/fperez), [@ryanlovett](https://github.com/ryanlovett))
|
||||||
- Updated the top-level index file [#4368](https://github.com/jupyterhub/jupyterhub/pull/4368) ([@alwasega](https://github.com/alwasega), [@sgibson91](https://github.com/sgibson91))
|
- Updated the top-level index file [#4368](https://github.com/jupyterhub/jupyterhub/pull/4368) ([@alwasega](https://github.com/alwasega), [@sgibson91](https://github.com/sgibson91))
|
||||||
|
- JupyterHub sphinx theme [#4363](https://github.com/jupyterhub/jupyterhub/pull/4363) ([@minrk](https://github.com/minrk), [@choldgraf](https://github.com/choldgraf))
|
||||||
- Remove PDF links from README.md [#4358](https://github.com/jupyterhub/jupyterhub/pull/4358) ([@pnasrat](https://github.com/pnasrat), [@manics](https://github.com/manics))
|
- Remove PDF links from README.md [#4358](https://github.com/jupyterhub/jupyterhub/pull/4358) ([@pnasrat](https://github.com/pnasrat), [@manics](https://github.com/manics))
|
||||||
|
- add singleuser explanation doc [#4357](https://github.com/jupyterhub/jupyterhub/pull/4357) ([@minrk](https://github.com/minrk), [@consideRatio](https://github.com/consideRatio))
|
||||||
- Updates to the documentation Contribution section [#4355](https://github.com/jupyterhub/jupyterhub/pull/4355) ([@alwasega](https://github.com/alwasega), [@sgibson91](https://github.com/sgibson91), [@minrk](https://github.com/minrk))
|
- Updates to the documentation Contribution section [#4355](https://github.com/jupyterhub/jupyterhub/pull/4355) ([@alwasega](https://github.com/alwasega), [@sgibson91](https://github.com/sgibson91), [@minrk](https://github.com/minrk))
|
||||||
- Restructured references section of the docs [#4343](https://github.com/jupyterhub/jupyterhub/pull/4343) ([@alwasega](https://github.com/alwasega), [@minrk](https://github.com/minrk), [@sgibson91](https://github.com/sgibson91))
|
- Restructured references section of the docs [#4343](https://github.com/jupyterhub/jupyterhub/pull/4343) ([@alwasega](https://github.com/alwasega), [@minrk](https://github.com/minrk), [@sgibson91](https://github.com/sgibson91))
|
||||||
- Document use of pytest-asyncio in JupyterHub test suite [#4341](https://github.com/jupyterhub/jupyterhub/pull/4341) ([@Sheila-nk](https://github.com/Sheila-nk), [@minrk](https://github.com/minrk), [@alwasega](https://github.com/alwasega))
|
- Document use of pytest-asyncio in JupyterHub test suite [#4341](https://github.com/jupyterhub/jupyterhub/pull/4341) ([@Sheila-nk](https://github.com/Sheila-nk), [@minrk](https://github.com/minrk), [@alwasega](https://github.com/alwasega))
|
||||||
@@ -107,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.
|
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).
|
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-02-27&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-02-27&type=Issues)) | @ajcollett ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aajcollett+updated%3A2022-12-05..2023-02-27&type=Issues)) | @ajpower ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aajpower+updated%3A2022-12-05..2023-02-27&type=Issues)) | @alwasega ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aalwasega+updated%3A2022-12-05..2023-02-27&type=Issues)) | @betatim ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Abetatim+updated%3A2022-12-05..2023-02-27&type=Issues)) | @bl-aire ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Abl-aire+updated%3A2022-12-05..2023-02-27&type=Issues)) | @choldgraf ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Acholdgraf+updated%3A2022-12-05..2023-02-27&type=Issues)) | @consideRatio ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3AconsideRatio+updated%3A2022-12-05..2023-02-27&type=Issues)) | @dependabot ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Adependabot+updated%3A2022-12-05..2023-02-27&type=Issues)) | @GeorgianaElena ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3AGeorgianaElena+updated%3A2022-12-05..2023-02-27&type=Issues)) | @julietKiloRomeo ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3AjulietKiloRomeo+updated%3A2022-12-05..2023-02-27&type=Issues)) | @ktaletsk ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aktaletsk+updated%3A2022-12-05..2023-02-27&type=Issues)) | @manics ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Amanics+updated%3A2022-12-05..2023-02-27&type=Issues)) | @meeseeksdev ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Ameeseeksdev+updated%3A2022-12-05..2023-02-27&type=Issues)) | @meeseeksmachine ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Ameeseeksmachine+updated%3A2022-12-05..2023-02-27&type=Issues)) | @minrk ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Aminrk+updated%3A2022-12-05..2023-02-27&type=Issues)) | @mouse1203 ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Amouse1203+updated%3A2022-12-05..2023-02-27&type=Issues)) | @naatebarber ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Anaatebarber+updated%3A2022-12-05..2023-02-27&type=Issues)) | @pnasrat ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Apnasrat+updated%3A2022-12-05..2023-02-27&type=Issues)) | @pre-commit-ci ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Apre-commit-ci+updated%3A2022-12-05..2023-02-27&type=Issues)) | @sgibson91 ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Asgibson91+updated%3A2022-12-05..2023-02-27&type=Issues)) | @shaneknapp ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Ashaneknapp+updated%3A2022-12-05..2023-02-27&type=Issues)) | @Sheila-nk ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3ASheila-nk+updated%3A2022-12-05..2023-02-27&type=Issues)) | @stevejpurves ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Astevejpurves+updated%3A2022-12-05..2023-02-27&type=Issues)) | @TaofeeqatDev ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3ATaofeeqatDev+updated%3A2022-12-05..2023-02-27&type=Issues)) | @vladfreeze ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Avladfreeze+updated%3A2022-12-05..2023-02-27&type=Issues)) | @yuvipanda ([activity](https://github.com/search?q=repo%3Ajupyterhub%2Fjupyterhub+involves%3Ayuvipanda+updated%3A2022-12-05..2023-02-27&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
|
## 3.1
|
||||||
|
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
# Event logging and telemetry
|
# 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
|
## How to emit events
|
||||||
|
|
||||||
|
@@ -83,7 +83,6 @@ easy to do with RStudio too.
|
|||||||
- Slurm job dispatched on Crestone compute cluster
|
- Slurm job dispatched on Crestone compute cluster
|
||||||
- log troubleshooting
|
- log troubleshooting
|
||||||
- Profiles in IPython Clusters tab
|
- Profiles in IPython Clusters tab
|
||||||
- [Parallel Processing with JupyterHub tutorial](https://curc.readthedocs.io/en/latest/gateways/parallel-programming-jupyter.html)
|
|
||||||
|
|
||||||
### George Washington University
|
### George Washington University
|
||||||
|
|
||||||
|
@@ -124,6 +124,8 @@ for project_name, project in project_config["projects"].items():
|
|||||||
|
|
||||||
The `members` step could be skipped if group membership is managed by the authenticator, or handled via the admin UI later, in which case we only need to handle group _creation_ and role assignment.
|
The `members` step could be skipped if group membership is managed by the authenticator, or handled via the admin UI later, in which case we only need to handle group _creation_ and role assignment.
|
||||||
|
|
||||||
|
This configuration code runs when jupyterhub starts up, and as noted above, users and groups cannot have their role assignments change without restarting JupyterHub. If new collaboration groups are created (within configuration, via the admin page, or via the Authenticator), the hub will need to be restarted in order for it to load roles for those new groups.
|
||||||
|
|
||||||
### Distinguishing collaborative servers
|
### Distinguishing collaborative servers
|
||||||
|
|
||||||
Finally, we want to enable RTC only on the collaborative user servers (and _only_ the collaborative user servers),
|
Finally, we want to enable RTC only on the collaborative user servers (and _only_ the collaborative user servers),
|
||||||
|
@@ -37,7 +37,6 @@
|
|||||||
"react-dom": "^17.0.1",
|
"react-dom": "^17.0.1",
|
||||||
"react-icons": "^4.1.0",
|
"react-icons": "^4.1.0",
|
||||||
"react-multi-select-component": "^3.0.7",
|
"react-multi-select-component": "^3.0.7",
|
||||||
"react-object-table-viewer": "^1.0.7",
|
|
||||||
"react-redux": "^7.2.2",
|
"react-redux": "^7.2.2",
|
||||||
"react-router": "^5.2.0",
|
"react-router": "^5.2.0",
|
||||||
"react-router-dom": "^5.2.0",
|
"react-router-dom": "^5.2.0",
|
||||||
@@ -68,7 +67,7 @@
|
|||||||
"prettier": "^2.2.1",
|
"prettier": "^2.2.1",
|
||||||
"sinon": "^13.0.1",
|
"sinon": "^13.0.1",
|
||||||
"style-loader": "^2.0.0",
|
"style-loader": "^2.0.0",
|
||||||
"webpack": "^5.6.0",
|
"webpack": "^5.76.0",
|
||||||
"webpack-cli": "^4.10.0",
|
"webpack-cli": "^4.10.0",
|
||||||
"webpack-dev-server": "^4.9.3"
|
"webpack-dev-server": "^4.9.3"
|
||||||
}
|
}
|
||||||
|
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 { useSelector, useDispatch } from "react-redux";
|
||||||
import { debounce } from "lodash";
|
import { debounce } from "lodash";
|
||||||
import PropTypes from "prop-types";
|
import PropTypes from "prop-types";
|
||||||
@@ -12,7 +12,7 @@ import {
|
|||||||
CardGroup,
|
CardGroup,
|
||||||
Collapse,
|
Collapse,
|
||||||
} from "react-bootstrap";
|
} from "react-bootstrap";
|
||||||
import ReactObjectTableViewer from "react-object-table-viewer";
|
import ReactObjectTableViewer from "../ReactObjectTableViewer/ReactObjectTableViewer";
|
||||||
|
|
||||||
import { Link } from "react-router-dom";
|
import { Link } from "react-router-dom";
|
||||||
import { FaSort, FaSortUp, FaSortDown } from "react-icons/fa";
|
import { FaSort, FaSortUp, FaSortDown } from "react-icons/fa";
|
||||||
@@ -29,6 +29,13 @@ const AccessServerButton = ({ url }) => (
|
|||||||
</a>
|
</a>
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const RowListItem = ({ text }) => (
|
||||||
|
<span className="server-dashboard-row-list-item">{text}</span>
|
||||||
|
);
|
||||||
|
RowListItem.propTypes = {
|
||||||
|
text: PropTypes.string,
|
||||||
|
};
|
||||||
|
|
||||||
const ServerDashboard = (props) => {
|
const ServerDashboard = (props) => {
|
||||||
let base_url = window.base_url || "/";
|
let base_url = window.base_url || "/";
|
||||||
// sort methods
|
// sort methods
|
||||||
@@ -236,8 +243,13 @@ const ServerDashboard = (props) => {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
if (Array.isArray(value)) {
|
if (Array.isArray(value)) {
|
||||||
// cast arrays (e.g. roles, groups) to string
|
value = (
|
||||||
value = value.sort().join(", ");
|
<Fragment>
|
||||||
|
{value.sort().flatMap((v) => (
|
||||||
|
<RowListItem text={v} />
|
||||||
|
))}
|
||||||
|
</Fragment>
|
||||||
|
);
|
||||||
}
|
}
|
||||||
result[key] = value;
|
result[key] = value;
|
||||||
return result;
|
return result;
|
||||||
|
@@ -30,3 +30,11 @@
|
|||||||
tr.noborder > td {
|
tr.noborder > td {
|
||||||
border: none !important;
|
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"
|
uncontrollable "^7.2.1"
|
||||||
warning "^4.0.3"
|
warning "^4.0.3"
|
||||||
|
|
||||||
react-dom@17.0.2, react-dom@^17.0.1:
|
react-dom@^17.0.1:
|
||||||
version "17.0.2"
|
version "17.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23"
|
resolved "https://registry.yarnpkg.com/react-dom/-/react-dom-17.0.2.tgz#ecffb6845e3ad8dbfcdc498f0d0a939736502c23"
|
||||||
integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==
|
integrity sha512-s4h96KtLDUQlsENhMn1ar8t2bEa+q/YAtj8pPPdIjPDGBDIVNsrD9aXNWqspUe6AzKCIG0C1HZZLqLV7qpOBGA==
|
||||||
@@ -6004,14 +6004,6 @@ react-multi-select-component@^3.0.7:
|
|||||||
dependencies:
|
dependencies:
|
||||||
goober "^2.0.30"
|
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:
|
react-redux@^7.2.2:
|
||||||
version "7.2.8"
|
version "7.2.8"
|
||||||
resolved "https://registry.yarnpkg.com/react-redux/-/react-redux-7.2.8.tgz#a894068315e65de5b1b68899f9c6ee0923dd28de"
|
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"
|
loose-envify "^1.4.0"
|
||||||
prop-types "^15.6.2"
|
prop-types "^15.6.2"
|
||||||
|
|
||||||
react@^17.0.1, react@^17.0.2:
|
react@^17.0.1:
|
||||||
version "17.0.2"
|
version "17.0.2"
|
||||||
resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"
|
resolved "https://registry.yarnpkg.com/react/-/react-17.0.2.tgz#d0b5cc516d29eb3eee383f75b62864cfb6800037"
|
||||||
integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==
|
integrity sha512-gnhPt75i/dq/z3/6q/0asP78D0u592D5L1pd7M8P+dck6Fu/jJeL6iVVK23fptSUZj8Vjf++7wXA8UNclGQcbA==
|
||||||
@@ -7423,10 +7415,10 @@ webpack-sources@^3.2.3:
|
|||||||
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde"
|
resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde"
|
||||||
integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
|
integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w==
|
||||||
|
|
||||||
webpack@^5.6.0:
|
webpack@^5.76.0:
|
||||||
version "5.74.0"
|
version "5.76.0"
|
||||||
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.74.0.tgz#02a5dac19a17e0bb47093f2be67c695102a55980"
|
resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.76.0.tgz#f9fb9fb8c4a7dbdcd0d56a98e56b8a942ee2692c"
|
||||||
integrity sha512-A2InDwnhhGN4LYctJj6M1JEaGL7Luj6LOmyBHjcI8529cm5p6VXiTIW2sn6ffvEAKmveLzvu4jrihwXtPojlAA==
|
integrity sha512-l5sOdYBDunyf72HW8dF23rFtWq/7Zgvt/9ftMof71E/yUb1YLOBmTgA2K4vQthB3kotMrSj609txVE0dnr2fjA==
|
||||||
dependencies:
|
dependencies:
|
||||||
"@types/eslint-scope" "^3.7.3"
|
"@types/eslint-scope" "^3.7.3"
|
||||||
"@types/estree" "^0.0.51"
|
"@types/estree" "^0.0.51"
|
||||||
|
@@ -2,7 +2,7 @@
|
|||||||
# Copyright (c) Jupyter Development Team.
|
# Copyright (c) Jupyter Development Team.
|
||||||
# Distributed under the terms of the Modified BSD License.
|
# Distributed under the terms of the Modified BSD License.
|
||||||
# version_info updated by running `tbump`
|
# version_info updated by running `tbump`
|
||||||
version_info = (4, 0, 0, "b1", "")
|
version_info = (4, 0, 0, "", "")
|
||||||
|
|
||||||
# pep 440 version: no dot before beta/rc, but before .dev
|
# pep 440 version: no dot before beta/rc, but before .dev
|
||||||
# 0.1.0rc1
|
# 0.1.0rc1
|
||||||
|
@@ -8,6 +8,7 @@ from datetime import datetime, timedelta
|
|||||||
|
|
||||||
import alembic.command
|
import alembic.command
|
||||||
import alembic.config
|
import alembic.config
|
||||||
|
import sqlalchemy
|
||||||
from alembic.script import ScriptDirectory
|
from alembic.script import ScriptDirectory
|
||||||
from sqlalchemy import (
|
from sqlalchemy import (
|
||||||
Boolean,
|
Boolean,
|
||||||
@@ -902,10 +903,12 @@ def register_ping_connection(engine):
|
|||||||
https://docs.sqlalchemy.org/en/rel_1_1/core/pooling.html#disconnect-handling-pessimistic
|
https://docs.sqlalchemy.org/en/rel_1_1/core/pooling.html#disconnect-handling-pessimistic
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@event.listens_for(engine, "engine_connect")
|
# listeners are normally registered as a decorator,
|
||||||
def ping_connection(connection, branch=None):
|
# but we need two different signatures to avoid SAWarning:
|
||||||
# TODO: remove unused branch arg when we require sqlalchemy 2.0
|
# The argument signature for the "ConnectionEvents.engine_connect" event listener has changed
|
||||||
|
# while we support sqla 1.4 and 2.0.
|
||||||
|
# @event.listens_for(engine, "engine_connect")
|
||||||
|
def ping_connection(connection):
|
||||||
# turn off "close with result". This flag is only used with
|
# turn off "close with result". This flag is only used with
|
||||||
# "connectionless" execution, otherwise will be False in any case
|
# "connectionless" execution, otherwise will be False in any case
|
||||||
save_should_close_with_result = connection.should_close_with_result
|
save_should_close_with_result = connection.should_close_with_result
|
||||||
@@ -939,6 +942,17 @@ def register_ping_connection(engine):
|
|||||||
# restore "close with result"
|
# restore "close with result"
|
||||||
connection.should_close_with_result = save_should_close_with_result
|
connection.should_close_with_result = save_should_close_with_result
|
||||||
|
|
||||||
|
# sqla v1/v2 compatible invocation of @event.listens_for:
|
||||||
|
def ping_connection_v1(connection, branch=None):
|
||||||
|
"""sqlalchemy < 2.0 compatibility"""
|
||||||
|
return ping_connection(connection)
|
||||||
|
|
||||||
|
if int(sqlalchemy.__version__.split(".", 1)[0]) >= 2:
|
||||||
|
listener = ping_connection
|
||||||
|
else:
|
||||||
|
listener = ping_connection_v1
|
||||||
|
event.listens_for(engine, "engine_connect")(listener)
|
||||||
|
|
||||||
|
|
||||||
def check_db_revision(engine):
|
def check_db_revision(engine):
|
||||||
"""Check the JupyterHub database revision
|
"""Check the JupyterHub database revision
|
||||||
|
@@ -52,7 +52,7 @@ def get_default_roles():
|
|||||||
'description': 'Post activity only',
|
'description': 'Post activity only',
|
||||||
'scopes': [
|
'scopes': [
|
||||||
'users:activity!user',
|
'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}")
|
scopes.append(f"access:servers!server={self.user.name}/{self.name}")
|
||||||
return sorted(set(scopes))
|
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(
|
will_resume = Bool(
|
||||||
False,
|
False,
|
||||||
help="""Whether the Spawner will resume on next start
|
help="""Whether the Spawner will resume on next start
|
||||||
|
@@ -3,6 +3,7 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import json
|
import json
|
||||||
from functools import partial
|
from functools import partial
|
||||||
|
from urllib.parse import parse_qs, urlparse
|
||||||
|
|
||||||
import pytest
|
import pytest
|
||||||
from selenium.common.exceptions import (
|
from selenium.common.exceptions import (
|
||||||
@@ -921,8 +922,15 @@ async def test_oauth_page(
|
|||||||
service_url = url_path_join(public_url(app, service) + 'owhoami/?arg=x')
|
service_url = url_path_join(public_url(app, service) + 'owhoami/?arg=x')
|
||||||
await in_thread(browser.get, (service_url))
|
await in_thread(browser.get, (service_url))
|
||||||
expected_client_id = service.name
|
expected_client_id = service.name
|
||||||
expected_redirect_url = app.base_url + f"servises/{service.name}/oauth_callback"
|
expected_redirect_url = url_path_join(
|
||||||
assert expected_client_id, expected_redirect_url in browser.current_url
|
app.base_url + f"services/{service.name}/oauth_callback"
|
||||||
|
)
|
||||||
|
# decode the URL
|
||||||
|
query_params = parse_qs(urlparse(browser.current_url).query)
|
||||||
|
query_params = parse_qs(urlparse(query_params['next'][0]).query)
|
||||||
|
|
||||||
|
assert f"service-{expected_client_id}" == query_params['client_id'][0]
|
||||||
|
assert expected_redirect_url == query_params['redirect_uri'][0]
|
||||||
|
|
||||||
# login user
|
# login user
|
||||||
await login(browser, user.name, pass_w=str(user.name))
|
await login(browser, user.name, pass_w=str(user.name))
|
||||||
@@ -1092,7 +1100,6 @@ async def test_start_stop_all_servers_on_admin_page(app, browser, admin_user):
|
|||||||
btns = {
|
btns = {
|
||||||
class_name: get_users_buttons(browser, class_name) for class_name in class_names
|
class_name: get_users_buttons(browser, class_name) for class_name in class_names
|
||||||
}
|
}
|
||||||
print(btns)
|
|
||||||
assert (
|
assert (
|
||||||
len(btns["start-button"])
|
len(btns["start-button"])
|
||||||
== len(btns["secondary"])
|
== len(btns["secondary"])
|
||||||
|
@@ -35,7 +35,7 @@ def generate_old_db(env_dir, hub_version, db_url):
|
|||||||
pkgs.append('sqlalchemy<2')
|
pkgs.append('sqlalchemy<2')
|
||||||
|
|
||||||
if 'mysql' in db_url:
|
if 'mysql' in db_url:
|
||||||
pkgs.append('mysql-connector-python')
|
pkgs.append('mysqlclient')
|
||||||
elif 'postgres' in db_url:
|
elif 'postgres' in db_url:
|
||||||
pkgs.append('psycopg2-binary')
|
pkgs.append('psycopg2-binary')
|
||||||
check_call([env_pip, 'install'] + pkgs)
|
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)
|
orm_server_token = orm.APIToken.find(app.db, server_token)
|
||||||
assert orm_server_token
|
assert orm_server_token
|
||||||
|
|
||||||
server_role = orm.Role.find(app.db, 'server')
|
# resolve `!server` filter in server role
|
||||||
assert set(server_role.scopes) == set(orm_server_token.scopes)
|
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 orm_server_token.user.name == user.name
|
||||||
assert user.api_tokens == [orm_server_token]
|
assert user.api_tokens == [orm_server_token]
|
||||||
|
@@ -20,7 +20,7 @@ from ..objects import Hub, Server
|
|||||||
from ..scopes import access_scopes
|
from ..scopes import access_scopes
|
||||||
from ..spawner import LocalProcessSpawner, Spawner
|
from ..spawner import LocalProcessSpawner, Spawner
|
||||||
from ..user import User
|
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 .mocking import public_url
|
||||||
from .test_api import add_user
|
from .test_api import add_user
|
||||||
from .utils import async_requests
|
from .utils import async_requests
|
||||||
@@ -336,6 +336,12 @@ async def test_spawner_insert_api_token(app):
|
|||||||
assert found
|
assert found
|
||||||
assert found.user.name == user.name
|
assert found.user.name == user.name
|
||||||
assert user.api_tokens == [found]
|
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()
|
await user.stop()
|
||||||
|
|
||||||
|
|
||||||
@@ -361,6 +367,58 @@ async def test_spawner_bad_api_token(app):
|
|||||||
assert other_user.api_tokens == []
|
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):
|
async def test_spawner_delete_server(app):
|
||||||
"""Test deleting spawner.server
|
"""Test deleting spawner.server
|
||||||
|
|
||||||
|
@@ -53,3 +53,16 @@ def test_sync_groups(app, user, group_names):
|
|||||||
assert user.orm_user in group.users
|
assert user.orm_user in group.users
|
||||||
else:
|
else:
|
||||||
assert user.orm_user not in group.users
|
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.httputil import urlencode
|
||||||
from tornado.log import app_log
|
from tornado.log import app_log
|
||||||
|
|
||||||
from . import orm
|
from . import orm, roles, scopes
|
||||||
from ._version import __version__, _check_version
|
from ._version import __version__, _check_version
|
||||||
from .crypto import CryptKeeper, EncryptionUnavailable, InvalidToken, decrypt, encrypt
|
from .crypto import CryptKeeper, EncryptionUnavailable, InvalidToken, decrypt, encrypt
|
||||||
from .metrics import RUNNING_SERVERS, TOTAL_USERS
|
from .metrics import RUNNING_SERVERS, TOTAL_USERS
|
||||||
@@ -588,7 +588,7 @@ class User:
|
|||||||
if not server_name:
|
if not server_name:
|
||||||
return self.url
|
return self.url
|
||||||
else:
|
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=''):
|
def progress_url(self, server_name=''):
|
||||||
"""API URL for progress endpoint for a server with a given 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)
|
orm_server = orm.Server(base_url=base_url)
|
||||||
db.add(orm_server)
|
db.add(orm_server)
|
||||||
note = "Server at %s" % base_url
|
note = "Server at %s" % base_url
|
||||||
api_token = self.new_api_token(note=note, roles=['server'])
|
|
||||||
db.commit()
|
db.commit()
|
||||||
|
|
||||||
spawner = self.get_spawner(server_name, replace_failed=True)
|
spawner = self.get_spawner(server_name, replace_failed=True)
|
||||||
spawner.server = server = Server(orm_server=orm_server)
|
spawner.server = server = Server(orm_server=orm_server)
|
||||||
assert spawner.orm_spawner.server is 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
|
# pass requesting handler to the spawner
|
||||||
# e.g. for processing GET params
|
# e.g. for processing GET params
|
||||||
spawner.handler = handler
|
spawner.handler = handler
|
||||||
@@ -808,6 +858,7 @@ class User:
|
|||||||
spawner.api_token,
|
spawner.api_token,
|
||||||
generated=False,
|
generated=False,
|
||||||
note="retrieved from spawner %s" % server_name,
|
note="retrieved from spawner %s" % server_name,
|
||||||
|
scopes=resolved_scopes,
|
||||||
)
|
)
|
||||||
# update OAuth client secret with updated API token
|
# update OAuth client secret with updated API token
|
||||||
if oauth_provider:
|
if oauth_provider:
|
||||||
|
@@ -229,9 +229,10 @@ async def exponential_backoff(
|
|||||||
# add some random jitter to improve performance
|
# add some random jitter to improve performance
|
||||||
# this prevents overloading any single tornado loop iteration with
|
# this prevents overloading any single tornado loop iteration with
|
||||||
# too many things
|
# too many things
|
||||||
dt = min(max_wait, remaining, random.uniform(0, start_wait * scale))
|
limit = min(max_wait, start_wait * scale)
|
||||||
if dt < max_wait:
|
if limit < max_wait:
|
||||||
scale *= scale_factor
|
scale *= scale_factor
|
||||||
|
dt = min(remaining, random.uniform(0, limit))
|
||||||
await asyncio.sleep(dt)
|
await asyncio.sleep(dt)
|
||||||
raise asyncio.TimeoutError(fail_message)
|
raise asyncio.TimeoutError(fail_message)
|
||||||
|
|
||||||
|
@@ -43,7 +43,7 @@ target_version = [
|
|||||||
github_url = "https://github.com/jupyterhub/jupyterhub"
|
github_url = "https://github.com/jupyterhub/jupyterhub"
|
||||||
|
|
||||||
[tool.tbump.version]
|
[tool.tbump.version]
|
||||||
current = "4.0.0b1"
|
current = "4.0.0"
|
||||||
|
|
||||||
# Example of a semver regexp.
|
# Example of a semver regexp.
|
||||||
# Make sure this matches current_version before
|
# Make sure this matches current_version before
|
||||||
|
2
setup.py
2
setup.py
@@ -55,7 +55,7 @@ def get_package_data():
|
|||||||
'alembic/*',
|
'alembic/*',
|
||||||
'alembic/versions/*',
|
'alembic/versions/*',
|
||||||
'event-schemas/*/*.yaml',
|
'event-schemas/*/*.yaml',
|
||||||
'jupyterhub/singleuser/templates/*.html',
|
'singleuser/templates/*.html',
|
||||||
]
|
]
|
||||||
return package_data
|
return package_data
|
||||||
|
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
# Build as jupyterhub/singleuser
|
# Build as jupyterhub/singleuser
|
||||||
# Run with the DockerSpawner in JupyterHub
|
# Run with the DockerSpawner in JupyterHub
|
||||||
|
|
||||||
ARG BASE_IMAGE=jupyter/base-notebook:2023-01-30
|
ARG BASE_IMAGE=jupyter/base-notebook
|
||||||
FROM $BASE_IMAGE
|
FROM $BASE_IMAGE
|
||||||
MAINTAINER Project Jupyter <jupyter@googlegroups.com>
|
MAINTAINER Project Jupyter <jupyter@googlegroups.com>
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user