sync with main

This commit is contained in:
Min RK
2025-08-06 21:18:21 -07:00
35 changed files with 4821 additions and 2318 deletions

View File

@@ -53,5 +53,12 @@ updates:
- "*-loader" - "*-loader"
update-types: update-types:
- major - major
# group major bumps of jest-related dependencies
jsx-jest:
patterns:
- "*jest*"
- "*test*"
update-types:
- major
schedule: schedule:
interval: monthly interval: monthly

View File

@@ -16,7 +16,7 @@ ci:
repos: repos:
# autoformat and lint Python code # autoformat and lint Python code
- repo: https://github.com/astral-sh/ruff-pre-commit - repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.11.12 rev: v0.12.7
hooks: hooks:
- id: ruff - id: ruff
types_or: types_or:
@@ -29,8 +29,8 @@ repos:
- jupyter - jupyter
# 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/rbubley/mirrors-prettier
rev: v4.0.0-alpha.8 rev: v3.6.2
hooks: hooks:
- id: prettier - id: prettier
exclude: .*/templates/.*|docs/source/_static/rest-api.yml|docs/source/rbac/scope-table.md exclude: .*/templates/.*|docs/source/_static/rest-api.yml|docs/source/rbac/scope-table.md

View File

@@ -58,7 +58,6 @@ for administration of the Hub and its users.
- A Linux/Unix based system - A Linux/Unix based system
- [Python](https://www.python.org/downloads/) 3.8 or greater - [Python](https://www.python.org/downloads/) 3.8 or greater
- [nodejs/npm](https://www.npmjs.com/) - [nodejs/npm](https://www.npmjs.com/)
- If you are using **`conda`**, the nodejs and npm dependencies will be installed for - If you are using **`conda`**, the nodejs and npm dependencies will be installed for
you by conda. you by conda.

View File

@@ -62,18 +62,19 @@ paths:
properties: properties:
class: class:
type: string type: string
description: The Python class currently active for JupyterHub description: The Python class currently active for
Authentication JupyterHub Authentication
version: version:
type: string type: string
description: The version of the currently active Authenticator description: The version of the currently active
Authenticator
spawner: spawner:
type: object type: object
properties: properties:
class: class:
type: string type: string
description: The Python class currently active for spawning description: The Python class currently active for
single-user notebook servers spawning single-user notebook servers
version: version:
type: string type: string
description: The version of the currently active Spawner description: The version of the currently active Spawner
@@ -256,8 +257,8 @@ paths:
parameters: parameters:
- $ref: "#/components/parameters/userName" - $ref: "#/components/parameters/userName"
requestBody: requestBody:
description: Updated user info. At least one key to be updated (name or admin) description: Updated user info. At least one key to be updated (name or
is required. admin) is required.
content: content:
application/json: application/json:
schema: schema:
@@ -265,12 +266,12 @@ paths:
properties: properties:
name: name:
type: string type: string
description: the new name (optional, if another key is updated i.e. description: the new name (optional, if another key is updated
admin) i.e. admin)
admin: admin:
type: boolean type: boolean
description: update admin (optional, if another key is updated i.e. description: update admin (optional, if another key is updated
name) i.e. name)
required: true required: true
responses: responses:
200: 200:
@@ -286,8 +287,8 @@ paths:
post: post:
operationId: post-user-activity operationId: post-user-activity
summary: Notify Hub of activity for a given user summary: Notify Hub of activity for a given user
description: Notify the Hub of activity by the user, e.g. accessing a service description: Notify the Hub of activity by the user, e.g. accessing a
or (more likely) actively using a server. service or (more likely) actively using a server.
parameters: parameters:
- $ref: "#/components/parameters/userName" - $ref: "#/components/parameters/userName"
requestBody: requestBody:
@@ -366,8 +367,8 @@ paths:
description: The user's notebook server has started description: The user's notebook server has started
content: {} content: {}
202: 202:
description: The user's notebook server has not yet started, but has been description: The user's notebook server has not yet started, but has
requested been requested
content: {} content: {}
security: security:
- oauth2: - oauth2:
@@ -380,8 +381,8 @@ paths:
- $ref: "#/components/parameters/userName" - $ref: "#/components/parameters/userName"
responses: responses:
202: 202:
description: The user's notebook server has not yet stopped as it is taking description: The user's notebook server has not yet stopped as it is
a while to stop taking a while to stop
content: {} content: {}
204: 204:
description: The user's notebook server has stopped description: The user's notebook server has stopped
@@ -412,8 +413,8 @@ paths:
description: The user's notebook named-server has started description: The user's notebook named-server has started
content: {} content: {}
202: 202:
description: The user's notebook named-server has not yet started, but has description: The user's notebook named-server has not yet started, but
been requested has been requested
content: {} content: {}
security: security:
- oauth2: - oauth2:
@@ -448,8 +449,8 @@ paths:
required: false required: false
responses: responses:
202: 202:
description: The user's notebook named-server has not yet stopped as it description: The user's notebook named-server has not yet stopped as
is taking a while to stop it is taking a while to stop
content: {} content: {}
204: 204:
description: The user's notebook named-server has stopped description: The user's notebook named-server has stopped
@@ -462,8 +463,8 @@ paths:
get: get:
operationId: get-user-shared operationId: get-user-shared
summary: List servers shared with user summary: List servers shared with user
description: Returns list of Shares granting the user access to servers owned description: Returns list of Shares granting the user access to servers
by others (new in 5.0) owned by others (new in 5.0)
parameters: parameters:
- $ref: "#/components/parameters/userName" - $ref: "#/components/parameters/userName"
@@ -576,11 +577,13 @@ paths:
expires_in: expires_in:
type: number type: number
example: 3600 example: 3600
description: lifetime (in seconds) after which the requested token description: lifetime (in seconds) after which the requested
will expire. Omit, or specify null or 0 for no expiration. token will expire. Omit, or specify null or 0 for no
expiration.
note: note:
type: string type: string
description: A note attached to the token for future bookkeeping description: A note attached to the token for future
bookkeeping
roles: roles:
type: array type: array
description: | description: |
@@ -758,7 +761,8 @@ paths:
- $ref: "#/components/parameters/sharedServerName" - $ref: "#/components/parameters/sharedServerName"
responses: responses:
200: 200:
description: The permissions granted to members of `group` on `owner/server` description: The permissions granted to members of `group` on
`owner/server`
content: content:
application/json: application/json:
schema: schema:
@@ -1173,7 +1177,8 @@ paths:
description: | description: |
The full URL for accepting the code, The full URL for accepting the code,
if JupyterHub.public_url configuration is defined. if JupyterHub.public_url configuration is defined.
example: https://hub.example.org/hub/accept-share?code=abc123 example:
https://hub.example.org/hub/accept-share?code=abc123
security: security:
- oauth2: - oauth2:
- shares - shares
@@ -1250,8 +1255,8 @@ paths:
get: get:
operationId: get-proxy operationId: get-proxy
summary: Get the proxy's routing table summary: Get the proxy's routing table
description: A convenience alias for getting the routing table directly from description: A convenience alias for getting the routing table directly
the proxy from the proxy
parameters: parameters:
- $ref: "#/components/parameters/paginationOffset" - $ref: "#/components/parameters/paginationOffset"
- $ref: "#/components/parameters/paginationLimit" - $ref: "#/components/parameters/paginationLimit"
@@ -1262,8 +1267,8 @@ paths:
application/json: application/json:
schema: schema:
type: object type: object
description: configurable-http-proxy routing table (see configurable-http-proxy description: configurable-http-proxy routing table (see
docs for details) configurable-http-proxy docs for details)
security: security:
- oauth2: - oauth2:
- proxy - proxy
@@ -1282,8 +1287,8 @@ paths:
summary: Notify the Hub about a new proxy summary: Notify the Hub about a new proxy
description: Notifies the Hub of a new proxy to use. description: Notifies the Hub of a new proxy to use.
requestBody: requestBody:
description: Any values that have changed for the new proxy. All keys are description: Any values that have changed for the new proxy. All keys
optional. are optional.
content: content:
application/json: application/json:
schema: schema:
@@ -1374,8 +1379,8 @@ paths:
get: get:
operationId: get-auth-cookie operationId: get-auth-cookie
summary: Identify a user from a cookie summary: Identify a user from a cookie
description: Used by single-user notebook servers to hand off cookie authentication description: Used by single-user notebook servers to hand off cookie
to the Hub authentication to the Hub
parameters: parameters:
- name: cookie_name - name: cookie_name
in: path in: path
@@ -1499,12 +1504,12 @@ paths:
properties: properties:
proxy: proxy:
type: boolean type: boolean
description: Whether the proxy should be shutdown as well (default description: Whether the proxy should be shutdown as well
from Hub config) (default from Hub config)
servers: servers:
type: boolean type: boolean
description: Whether users' notebook servers should be shutdown description: Whether users' notebook servers should be
as well (default from Hub config) shutdown as well (default from Hub config)
required: false required: false
responses: responses:
202: 202:
@@ -1648,8 +1653,8 @@ components:
type: string type: string
server: server:
type: string type: string
description: The user's notebook server's base URL, if running; null if description: The user's notebook server's base URL, if running; null
not. if not.
pending: pending:
type: string type: string
description: The currently pending action, if any description: The currently pending action, if any
@@ -1680,8 +1685,8 @@ components:
properties: properties:
name: name:
type: string type: string
description: The server's name. The user's default server has an empty name description: The server's name. The user's default server has an empty
('') name ('')
ready: ready:
type: boolean type: boolean
description: | description: |
@@ -1743,14 +1748,14 @@ components:
state: state:
type: object type: object
properties: {} properties: {}
description: Arbitrary internal state from this server's spawner. Only available description: Arbitrary internal state from this server's spawner. Only
on the hub's users list or get-user-by-name method, and only with admin:users:server_state available on the hub's users list or get-user-by-name method, and
scope. None otherwise. only with admin:users:server_state scope. None otherwise.
user_options: user_options:
type: object type: object
properties: {} properties: {}
description: User specified options for the user's spawned instance of a description: User specified options for the user's spawned instance of
single-user server. a single-user server.
RequestIdentity: RequestIdentity:
description: | description: |
The model for the entity making the request. The model for the entity making the request.
@@ -1918,8 +1923,8 @@ components:
items: items:
type: string type: string
group: group:
description: the group being shared with (exactly one of 'user' or 'group' description: the group being shared with (exactly one of 'user' or
will be non-null, the other will be null) 'group' will be non-null, the other will be null)
type: type:
- object - object
- "null" - "null"
@@ -1927,8 +1932,8 @@ components:
name: name:
type: string type: string
user: user:
description: the user being shared with (exactly one of 'user' or 'group' description: the user being shared with (exactly one of 'user' or
will be non-null, the other will be null) 'group' will be non-null, the other will be null)
type: type:
- object - object
- "null" - "null"
@@ -1941,8 +1946,8 @@ components:
format: date-time format: date-time
ShareCode: ShareCode:
description: A single sharing code. There is at most one of these objects per description: A single sharing code. There is at most one of these objects
(server, user) or (server, group) combination. per (server, user) or (server, group) combination.
type: object type: object
properties: properties:
server: server:
@@ -1977,37 +1982,41 @@ components:
properties: properties:
id: id:
type: string type: string
description: The id of the API token. Used for modifying or deleting the description: The id of the API token. Used for modifying or deleting
token. the token.
user: user:
type: string type: string
description: The user that owns a token (undefined if owned by a service) description: The user that owns a token (undefined if owned by a
service)
service: service:
type: string type: string
description: The service that owns the token (undefined of owned by a user) description: The service that owns the token (undefined of owned by a
user)
roles: roles:
type: array type: array
description: Deprecated in JupyterHub 3, always an empty list. Tokens have description: Deprecated in JupyterHub 3, always an empty list. Tokens
'scopes' starting from JupyterHub 3. have 'scopes' starting from JupyterHub 3.
items: items:
type: string type: string
scopes: scopes:
type: array type: array
description: List of scopes this token has been assigned. New in JupyterHub description: List of scopes this token has been assigned. New in
3. In JupyterHub 2.x, tokens were assigned 'roles' instead of scopes. JupyterHub 3. In JupyterHub 2.x, tokens were assigned 'roles'
instead of scopes.
items: items:
type: string type: string
note: note:
type: string type: string
description: A note about the token, typically describing what it was created description: A note about the token, typically describing what it was
for. created for.
created: created:
type: string type: string
description: Timestamp when this token was created description: Timestamp when this token was created
format: date-time format: date-time
expires_at: expires_at:
type: string type: string
description: Timestamp when this token expires. Null if there is no expiry. description: Timestamp when this token expires. Null if there is no
expiry.
format: date-time format: date-time
last_activity: last_activity:
type: string type: string
@@ -2030,41 +2039,45 @@ components:
properties: properties:
token: token:
type: string type: string
description: The token itself. Only present in responses to requests for description: The token itself. Only present in responses to requests
a new token. for a new token.
id: id:
type: string type: string
description: The id of the API token. Used for modifying or deleting the description: The id of the API token. Used for modifying or deleting
token. the token.
user: user:
type: string type: string
description: The user that owns a token (undefined if owned by a service) description: The user that owns a token (undefined if owned by a
service)
service: service:
type: string type: string
description: The service that owns the token (undefined of owned by a user) description: The service that owns the token (undefined of owned by a
user)
roles: roles:
type: array type: array
description: Deprecated in JupyterHub 3, always an empty list. Tokens have description: Deprecated in JupyterHub 3, always an empty list. Tokens
'scopes' starting from JupyterHub 3. have 'scopes' starting from JupyterHub 3.
items: items:
type: string type: string
scopes: scopes:
type: array type: array
description: List of scopes this token has been assigned. New in JupyterHub description: List of scopes this token has been assigned. New in
3. In JupyterHub 2.x, tokens were assigned 'roles' instead of scopes. JupyterHub 3. In JupyterHub 2.x, tokens were assigned 'roles'
instead of scopes.
items: items:
type: string type: string
note: note:
type: string type: string
description: A note about the token, typically describing what it was created description: A note about the token, typically describing what it was
for. created for.
created: created:
type: string type: string
description: Timestamp when this token was created description: Timestamp when this token was created
format: date-time format: date-time
expires_at: expires_at:
type: string type: string
description: Timestamp when this token expires. Null if there is no expiry. description: Timestamp when this token expires. Null if there is no
expiry.
format: date-time format: date-time
last_activity: last_activity:
type: string type: string
@@ -2094,22 +2107,23 @@ components:
tokenUrl: /hub/api/oauth2/token tokenUrl: /hub/api/oauth2/token
scopes: scopes:
(no_scope): Identify the owner of the requesting entity. (no_scope): Identify the owner of the requesting entity.
self: The users own resources _(metascope for users, resolves to (no_scope) self: The users own resources _(metascope for users, resolves to
for services)_ (no_scope) for services)_
inherit: Everything that the token-owning entity can access _(metascope inherit: Everything that the token-owning entity can access
for tokens)_ _(metascope for tokens)_
admin-ui: Access the admin page. Permission to take actions via the admin admin-ui: Access the admin page. Permission to take actions via the
page granted separately. admin page granted separately.
admin:users: Read, modify, create, and delete users and their authentication admin:users: Read, modify, create, and delete users and their
state, not including their servers or tokens. This is an extremely privileged authentication state, not including their servers or tokens. This
scope and should be considered tantamount to superuser. is an extremely privileged scope and should be considered
tantamount to superuser.
admin:auth_state: Read a users authentication state. admin:auth_state: Read a users authentication state.
users: Read and write permissions to user models (excluding servers, tokens users: Read and write permissions to user models (excluding servers,
and authentication state). tokens and authentication state).
delete:users: Delete users. delete:users: Delete users.
list:users: List users, including at least their names. list:users: List users, including at least their names.
read:users: Read user models (including the URL of the default server read:users: Read user models (including the URL of the default
if it is running). server if it is running).
read:users:name: Read names of users. read:users:name: Read names of users.
read:users:groups: Read users group membership. read:users:groups: Read users group membership.
read:users:activity: Read time of last user activity. read:users:activity: Read time of last user activity.
@@ -2118,24 +2132,25 @@ components:
read:roles:services: Read service role assignments. read:roles:services: Read service role assignments.
read:roles:groups: Read group role assignments. read:roles:groups: Read group role assignments.
users:activity: Update time of last user activity. users:activity: Update time of last user activity.
admin:servers: Read, start, stop, create and delete user servers and their admin:servers: Read, start, stop, create and delete user servers and
state. their state.
admin:server_state: Read and write users server state. admin:server_state: Read and write users server state.
servers: Start and stop user servers. servers: Start and stop user servers.
read:servers: Read users names and their server models (excluding the read:servers: Read users names and their server models (excluding
server state). the server state).
delete:servers: Stop and delete users' servers. delete:servers: Stop and delete users' servers.
tokens: Read, write, create and delete user tokens. tokens: Read, write, create and delete user tokens.
read:tokens: Read user tokens. read:tokens: Read user tokens.
admin:groups: Read and write group information, create and delete groups. admin:groups: Read and write group information, create and delete
groups.
groups: 'Read and write group information, including adding/removing any groups: 'Read and write group information, including adding/removing any
users to/from groups. Note: adding users to groups may affect permissions.' users to/from groups. Note: adding users to groups may affect permissions.'
list:groups: List groups, including at least their names. list:groups: List groups, including at least their names.
read:groups: Read group models. read:groups: Read group models.
read:groups:name: Read group names. read:groups:name: Read group names.
delete:groups: Delete groups. delete:groups: Delete groups.
admin:services: Create, read, update, delete services, not including services admin:services: Create, read, update, delete services, not including
defined from config files. services defined from config files.
list:services: List services, including at least their names. list:services: List services, including at least their names.
read:services: Read service models. read:services: Read service models.
read:services:name: Read service names. read:services:name: Read service names.
@@ -2148,7 +2163,7 @@ components:
read:groups:shares: Read servers shared with a group. read:groups:shares: Read servers shared with a group.
read:shares: Read information about shared access to servers. read:shares: Read information about shared access to servers.
shares: Manage access to shared servers. shares: Manage access to shared servers.
proxy: Read information about the proxys routing table, sync the Hub proxy: Read information about the proxys routing table, sync the
with the proxy and notify the Hub about a new proxy. Hub with the proxy and notify the Hub about a new proxy.
shutdown: Shutdown the hub. shutdown: Shutdown the hub.
read:metrics: Read prometheus metrics. read:metrics: Read prometheus metrics.

View File

@@ -262,6 +262,7 @@ html_static_path = ["_static"]
html_theme = "jupyterhub_sphinx_theme" html_theme = "jupyterhub_sphinx_theme"
html_theme_options = { html_theme_options = {
"header_links_before_dropdown": 6,
"icon_links": [ "icon_links": [
{ {
"name": "GitHub", "name": "GitHub",
@@ -297,7 +298,10 @@ linkcheck_ignore = [
r"https://github.com/jupyterhub/jupyterhub/security/advisories/.*", r"https://github.com/jupyterhub/jupyterhub/security/advisories/.*",
# Occasionally blocks CI checks with 403 # Occasionally blocks CI checks with 403
r"https://www\.mysql\.com", r"https://www\.mysql\.com",
# Occasionally blocks CI checks with SSL error
r"https://mediaspace\.msu\.edu/.*",
] ]
linkcheck_anchors_ignore = [ linkcheck_anchors_ignore = [
"/#!", "/#!",
"/#%21", "/#%21",

View File

@@ -12,9 +12,9 @@ Everyone in the Jupyter community is welcome to bring ideas and questions there.
We recommend that you first use our Discourse as all past and current discussions on it are archived and searchable. Thus, all discussions remain useful and accessible to the whole community. We recommend that you first use our Discourse as all past and current discussions on it are archived and searchable. Thus, all discussions remain useful and accessible to the whole community.
## Gitter ## Zulip
We use [our Gitter channel](https://gitter.im/jupyterhub/jupyterhub) for online, real-time text chat; a place for more ephemeral discussions. When you're not on Discourse, you can stop here to have other discussions on the fly. We use [Jupyter instance](https://jupyter.zulipchat.com/) of [Zulip](https://zulip.com/) for online, real-time text chat; a place for more ephemeral discussions. When you're not on Discourse, you can stop at Zulip to have other discussions on the fly.
## Github Issues ## Github Issues

View File

@@ -8,7 +8,7 @@ you get set up on how to contribute to JupyterHub's documentation.
## Building documentation locally ## Building documentation locally
We use [sphinx](https://www.sphinx-doc.org) to build our documentation. It takes We use [sphinx](https://www.sphinx-doc.org) to build our documentation. It takes
our documentation source files (written in [markdown](https://daringfireball.net/projects/markdown/) or [reStructuredText](https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html) & our documentation source files (written in [markdown](https://daringfireball.net/projects/markdown/) or [reStructuredText](https://www.sphinx-doc.org/en/master/usage/restructuredtext/basics.html) and
stored under the `docs/source` directory) and converts it into various stored under the `docs/source` directory) and converts it into various
formats for people to read. To make sure the documentation you write or formats for people to read. To make sure the documentation you write or
change renders correctly, it is good practice to test it locally. change renders correctly, it is good practice to test it locally.

View File

@@ -3,7 +3,7 @@
# Contributing # Contributing
We want you to contribute to JupyterHub in ways that are most exciting We want you to contribute to JupyterHub in ways that are most exciting
and useful to you. We value documentation, testing, bug reporting & code equally, and useful to you. We value documentation, testing, bug reporting and code equally,
and are glad to have your contributions in whatever form you wish. and are glad to have your contributions in whatever form you wish.
Be sure to first check our [Code of Conduct](https://github.com/jupyter/governance/blob/HEAD/conduct/code_of_conduct.md) Be sure to first check our [Code of Conduct](https://github.com/jupyter/governance/blob/HEAD/conduct/code_of_conduct.md)

View File

@@ -5,34 +5,34 @@
## System requirements ## System requirements
JupyterHub can only run on macOS or Linux operating systems. If you are JupyterHub can only run on macOS or Linux operating systems. If you are
using Windows, we recommend using [VirtualBox](https://virtualbox.org) using Windows, we recommend using [VirtualBox](https://www.virtualbox.org)
or a similar system to run [Ubuntu Linux](https://ubuntu.com) for or a similar system to run [Ubuntu Linux](https://ubuntu.com) for
development. development.
### Install Python ### Install Python
JupyterHub is written in the [Python](https://python.org) programming language and JupyterHub is written in the [Python](https://www.python.org) programming language and
requires you have at least version {{python_min}} installed locally. If you havent requires you have at least version {{python_min}} installed locally. If you havent
installed Python before, the recommended way to install it is to use installed Python before, the recommended way to install it is to use
[Miniforge](https://github.com/conda-forge/miniforge#download). [Miniforge](https://github.com/conda-forge/miniforge#download).
### Install nodejs ### Install NodeJS
[NodeJS {{node_min}}+](https://nodejs.org/en/) is required for building some JavaScript components. Some JavaScript components require you have at least version {{node_min}} of [NodeJS](https://nodejs.org/en/) installed locally.
`configurable-http-proxy`, the default proxy implementation for JupyterHub, is written in Javascript. `configurable-http-proxy`, the default proxy implementation for JupyterHub, is written in JavaScript.
If you have not installed NodeJS before, we recommend installing it in the `miniconda` environment you set up for Python. If you have not installed NodeJS before, we recommend installing it in the `miniconda` environment you set up for Python.
You can do so with `conda install nodejs`. You can do so with `conda install nodejs`.
Many in the Jupyter community use [`nvm`](https://github.com/nvm-sh/nvm) to Many in the Jupyter community use [`nvm`](https://github.com/nvm-sh/nvm) to
managing node dependencies. managing node dependencies.
### Install git ### Install Git
JupyterHub uses [Git](https://git-scm.com) & [GitHub](https://github.com) JupyterHub uses [Git](https://git-scm.com) and [GitHub](https://github.com)
for development & collaboration. You need to [install git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) to work on for development and collaboration. You need to [install Git](https://git-scm.com/book/en/v2/Getting-Started-Installing-Git) to work on
JupyterHub. We also recommend getting a free account on GitHub.com. JupyterHub. We also recommend getting a free account on GitHub.
## Setting up a development install ## Install JupyterHub for development
When developing JupyterHub, you would need to make changes and be able to instantly view the results of the changes. To achieve that, a developer install is required. When developing JupyterHub, you would need to make changes and be able to instantly view the results of the changes. To achieve that, a developer install is required.
@@ -44,7 +44,7 @@ be achieved in many ways, for example, `tox`, `conda`, `docker`, etc. See this
a more detailed discussion. a more detailed discussion.
::: :::
1. Clone the [JupyterHub git repository](https://github.com/jupyterhub/jupyterhub) 1. Clone the [JupyterHub Git repository](https://github.com/jupyterhub/jupyterhub)
to your computer. to your computer.
```bash ```bash
@@ -65,7 +65,7 @@ a more detailed discussion.
npm -v npm -v
``` ```
This should return a version number greater than or equal to 5.0. This should return a version number greater than or equal to {{node_min}}.
3. Install `configurable-http-proxy` (required to run and test the default JupyterHub configuration): 3. Install `configurable-http-proxy` (required to run and test the default JupyterHub configuration):
@@ -92,7 +92,7 @@ a more detailed discussion.
4. Install an editable version of JupyterHub and its requirements for 4. Install an editable version of JupyterHub and its requirements for
development and testing. This lets you edit JupyterHub code in a text editor development and testing. This lets you edit JupyterHub code in a text editor
& restart the JupyterHub process to see your code changes immediately. and restart the JupyterHub process to see your code changes immediately.
```bash ```bash
python3 -m pip install --editable ".[test]" python3 -m pip install --editable ".[test]"
@@ -109,7 +109,7 @@ a more detailed discussion.
Happy developing! Happy developing!
## Using DummyAuthenticator & SimpleLocalProcessSpawner ## Using DummyAuthenticator and SimpleLocalProcessSpawner
To simplify testing of JupyterHub, it is helpful to use To simplify testing of JupyterHub, it is helpful to use
{class}`~jupyterhub.auth.DummyAuthenticator` instead of the default JupyterHub {class}`~jupyterhub.auth.DummyAuthenticator` instead of the default JupyterHub
@@ -132,17 +132,17 @@ The test configuration enables a few things to make testing easier:
- disable caching of static files - disable caching of static files
The default JupyterHub [authenticator](PAMAuthenticator) The default JupyterHub [authenticator](PAMAuthenticator)
& [spawner](LocalProcessSpawner) and [spawner](LocalProcessSpawner)
require your system to have user accounts for each user you want to log in to require your system to have user accounts for each user you want to log in to
JupyterHub as. JupyterHub as.
DummyAuthenticator allows you to log in with any username & password, DummyAuthenticator allows you to log in with any username and password,
while SimpleLocalProcessSpawner allows you to start servers without having to while SimpleLocalProcessSpawner allows you to start servers without having to
create a Unix user for each JupyterHub user. Together, these make it create a Unix user for each JupyterHub user. Together, these make it
much easier to test JupyterHub. much easier to test JupyterHub.
Tip: If you are working on parts of JupyterHub that are common to all Tip: If you are working on parts of JupyterHub that are common to all
authenticators & spawners, we recommend using both DummyAuthenticator & authenticators and spawners, we recommend using both DummyAuthenticator and
SimpleLocalProcessSpawner. If you are working on just authenticator-related SimpleLocalProcessSpawner. If you are working on just authenticator-related
parts, use only SimpleLocalProcessSpawner. Similarly, if you are working on parts, use only SimpleLocalProcessSpawner. Similarly, if you are working on
just spawner-related parts, use only DummyAuthenticator. just spawner-related parts, use only DummyAuthenticator.

View File

@@ -98,7 +98,7 @@ the OAuth callback request.
to retrieve information about the owner of the token (the user). to retrieve information about the owner of the token (the user).
This is the step where behavior diverges for different OAuth providers. This is the step where behavior diverges for different OAuth providers.
Up to this point, all OAuth providers are the same, following the OAuth specification. Up to this point, all OAuth providers are the same, following the OAuth specification.
However, OAuth does not define a standard for issuing tokens in exchange for information about their owner or permissions ([OpenID Connect](https://openid.net/connect/) does that), However, OAuth does not define a standard for issuing tokens in exchange for information about their owner or permissions ([OpenID Connect](https://openid.net/developers/how-connect-works/) does that),
so this step may be different for each OAuth provider. so this step may be different for each OAuth provider.
- Finally, the OAuth client stores its own record that the user is authorized in a cookie. - Finally, the OAuth client stores its own record that the user is authorized in a cookie.
This could be the token itself, or any other appropriate representation of successful authentication. This could be the token itself, or any other appropriate representation of successful authentication.

View File

@@ -101,7 +101,7 @@ matching `*.jupyter.example.org`.
Unfortunately, for many institutional domains, wildcard DNS and SSL may not be available. Unfortunately, for many institutional domains, wildcard DNS and SSL may not be available.
We also **strongly encourage** serving JupyterHub and user content on a domain that is _not_ a subdomain of any sensitive content. We also **strongly encourage** serving JupyterHub and user content on a domain that is _not_ a subdomain of any sensitive content.
For reasoning, see [GitHub's discussion of moving user content to github.io from \*.github.com](https://github.blog/2013-04-09-yummy-cookies-across-domains/). For reasoning, see [GitHub's discussion of moving user content to github.io from \*.github.com](https://github.blog/engineering/yummy-cookies-across-domains/).
**If you do plan to serve untrusted users, enabling subdomains is highly encouraged**, **If you do plan to serve untrusted users, enabling subdomains is highly encouraged**,
as it resolves many security issues, which are difficult to unavoidable when JupyterHub is on a single-domain. as it resolves many security issues, which are difficult to unavoidable when JupyterHub is on a single-domain.
@@ -186,7 +186,6 @@ For example:
- `Content-Security-Policy` header must prohibit popups and iframes from the same origin. - `Content-Security-Policy` header must prohibit popups and iframes from the same origin.
The following Content-Security-Policy rules are _insecure_ and readily enable users to access each others' servers: The following Content-Security-Policy rules are _insecure_ and readily enable users to access each others' servers:
- `frame-ancestors: 'self'` - `frame-ancestors: 'self'`
- `frame-ancestors: '*'` - `frame-ancestors: '*'`
- `sandbox allow-popups` - `sandbox allow-popups`

View File

@@ -142,7 +142,7 @@ in a variety of deployment setups. This often entails connecting your JupyterHub
in these cases, and the security of your JupyterHub deployment will often depend on these decisions. in these cases, and the security of your JupyterHub deployment will often depend on these decisions.
If you are worried about security, don't hesitate to reach out to the JupyterHub community in the If you are worried about security, don't hesitate to reach out to the JupyterHub community in the
[Jupyter Community Forum](https://discourse.jupyter.org/c/jupyterhub). This community of practice has many [Jupyter Community Forum](https://discourse.jupyter.org/c/jupyterhub/10). This community of practice has many
individuals with experience running secure JupyterHub deployments and will be very glad to help you out. individuals with experience running secure JupyterHub deployments and will be very glad to help you out.
### Does JupyterHub provide computing or data infrastructure? ### Does JupyterHub provide computing or data infrastructure?

View File

@@ -35,7 +35,7 @@ This user shouldn't have a login shell or password (possible with -r).
## Set up sudospawner ## Set up sudospawner
Next, you will need [sudospawner](https://github.com/jupyter/sudospawner) Next, you will need [sudospawner](https://github.com/jupyterhub/sudospawner)
to enable monitoring the single-user servers with sudo: to enable monitoring the single-user servers with sudo:
```bash ```bash
@@ -72,7 +72,7 @@ rhea ALL=(JUPYTER_USERS) NOPASSWD:JUPYTER_CMD
``` ```
It might be useful to modify `secure_path` to add commands in path. (Search for It might be useful to modify `secure_path` to add commands in path. (Search for
`secure_path` in the [sudo docs](https://www.sudo.ws/man/1.8.14/sudoers.man.html) `secure_path` in the [sudo docs](https://www.sudo.ws)
As an alternative to adding every user to the `/etc/sudoers` file, you can As an alternative to adding every user to the `/etc/sudoers` file, you can
use a group in the last line above, instead of `JUPYTER_USERS`: use a group in the last line above, instead of `JUPYTER_USERS`:

View File

@@ -71,4 +71,4 @@ aligned, rather than as an indicator of an existing problem.
Upgrade the version of the `jupyterhub` package in your user environment or image Upgrade the version of the `jupyterhub` package in your user environment or image
so that it matches the version of JupyterHub running your JupyterHub server! If you so that it matches the version of JupyterHub running your JupyterHub server! If you
are using the [zero-to-jupyterhub](https://z2jh.jupyter.org) helm chart, you can find the appropriate are using the [zero-to-jupyterhub](https://z2jh.jupyter.org) helm chart, you can find the appropriate
version of the `jupyterhub` package to install in your user image [here](https://jupyterhub.github.io/helm-chart/) version of the `jupyterhub` package to install in your user image [here](https://hub.jupyter.org/helm-chart/)

View File

@@ -232,4 +232,4 @@ A list of the proxies that are currently available for JupyterHub (that we know
1. [`jupyterhub/configurable-http-proxy`](https://github.com/jupyterhub/configurable-http-proxy) The default proxy which uses node-http-proxy 1. [`jupyterhub/configurable-http-proxy`](https://github.com/jupyterhub/configurable-http-proxy) The default proxy which uses node-http-proxy
2. [`jupyterhub/traefik-proxy`](https://github.com/jupyterhub/traefik-proxy) The proxy which configures traefik proxy server for jupyterhub 2. [`jupyterhub/traefik-proxy`](https://github.com/jupyterhub/traefik-proxy) The proxy which configures traefik proxy server for jupyterhub
3. [`AbdealiJK/configurable-http-proxy`](https://github.com/AbdealiJK/configurable-http-proxy) A pure python implementation of the configurable-http-proxy 3. [`AbdealiJK/configurable-http-proxy`](https://github.com/corridor/configurable-http-proxy) A pure python implementation of the configurable-http-proxy

View File

@@ -201,7 +201,7 @@ Authorization header.
### Use requests ### Use requests
Using the popular Python [requests](https://docs.python-requests.org) Using the popular Python [requests](https://requests.readthedocs.io)
library, an API GET request is made to [/users](rest-api-get-users), and the request sends an API token for library, an API GET request is made to [/users](rest-api-get-users), and the request sends an API token for
authorization. The response contains information about the users, here's example code to make an API request for the users of a JupyterHub deployment authorization. The response contains information about the users, here's example code to make an API request for the users of a JupyterHub deployment

View File

@@ -27,7 +27,7 @@ For specific version migrations:
The [changelog](changelog) contains information on what has The [changelog](changelog) contains information on what has
changed with the new JupyterHub release and any deprecation warnings. changed with the new JupyterHub release and any deprecation warnings.
Read these notes to familiarize yourself with the coming changes. There Read these notes to familiarize yourself with the coming changes. There
might be new releases of the authenticators & spawners you use, so might be new releases of the authenticators and spawners you use, so
read the changelogs for those too! read the changelogs for those too!
## Notify your users ## Notify your users
@@ -41,7 +41,7 @@ If you use a different proxy or run `configurable-http-proxy`
independent of JupyterHub, your users will be able to continue using notebook independent of JupyterHub, your users will be able to continue using notebook
servers they had already launched, but will not be able to launch new servers or sign in. servers they had already launched, but will not be able to launch new servers or sign in.
## Backup database & config ## Backup database and config
Before doing an upgrade, it is critical to back up: Before doing an upgrade, it is critical to back up:
@@ -90,7 +90,7 @@ with:
conda install -c conda-forge jupyterhub==<version> conda install -c conda-forge jupyterhub==<version>
``` ```
You should also check for new releases of the authenticator & spawner you You should also check for new releases of the authenticator and spawner you
are using. You might wish to upgrade those packages, too, along with JupyterHub are using. You might wish to upgrade those packages, too, along with JupyterHub
or upgrade them separately. or upgrade them separately.

View File

@@ -17,7 +17,7 @@ It has two main distributions which are developed to serve the needs of each of
1. [The Littlest JupyterHub](https://github.com/jupyterhub/the-littlest-jupyterhub) distribution is suitable if you need a small number of users (1-100) and a single server with a simple environment. 1. [The Littlest JupyterHub](https://github.com/jupyterhub/the-littlest-jupyterhub) distribution is suitable if you need a small number of users (1-100) and a single server with a simple environment.
2. [Zero to JupyterHub with Kubernetes](https://github.com/jupyterhub/zero-to-jupyterhub-k8s) allows you to deploy dynamic servers on the cloud if you need even more users. 2. [Zero to JupyterHub with Kubernetes](https://github.com/jupyterhub/zero-to-jupyterhub-k8s) allows you to deploy dynamic servers on the cloud if you need even more users.
This distribution runs JupyterHub on top of [Kubernetes](https://k8s.io). This distribution runs JupyterHub on top of [Kubernetes](https://kubernetes.io/).
```{note} ```{note}
It is important to evaluate these distributions before you can continue with the It is important to evaluate these distributions before you can continue with the

View File

@@ -84,7 +84,6 @@ The passed scopes are compared to the scopes required to access the API as follo
- if the API scopes are present within the set of passed scopes, the access is granted and the API returns its "full" response - if the API scopes are present within the set of passed scopes, the access is granted and the API returns its "full" response
- if that is not the case, another check is utilized to determine if subscopes of the required API scopes can be found in the passed scope set: - if that is not the case, another check is utilized to determine if subscopes of the required API scopes can be found in the passed scope set:
- if found, the RBAC framework employs the {ref}`filtering <vertical-filtering-target>` procedures to refine the API response to access only resource attributes corresponding to the passed scopes. For example, providing a scope `read:users:activity!group=class-C` for the `GET /users` API will return a list of user models from group `class-C` containing only the `last_activity` attribute for each user model - if found, the RBAC framework employs the {ref}`filtering <vertical-filtering-target>` procedures to refine the API response to access only resource attributes corresponding to the passed scopes. For example, providing a scope `read:users:activity!group=class-C` for the `GET /users` API will return a list of user models from group `class-C` containing only the `last_activity` attribute for each user model
- if not found, the access to API is denied - if not found, the access to API is denied

View File

@@ -1831,7 +1831,7 @@ Highlights:
- More configuration of page templates and service display - More configuration of page templates and service display
- Pagination of the admin page improving performance with large numbers of users - Pagination of the admin page improving performance with large numbers of users
- Improved control of user redirect - Improved control of user redirect
- Support for [jupyter-server](https://jupyter-server.readthedocs.io/en/latest/)-based single-user servers, such as [Voilà](https://voila-gallery.org) and latest JupyterLab. - Support for [jupyter-server](https://jupyter-server.readthedocs.io/en/latest/)-based single-user servers, such as [Voilà](https://voila.readthedocs.io) and latest JupyterLab.
- Lots more improvements to documentation, HTML pages, and customizations - Lots more improvements to documentation, HTML pages, and customizations
([full changelog](https://github.com/jupyterhub/jupyterhub/compare/1.1.0...1.2.0)) ([full changelog](https://github.com/jupyterhub/jupyterhub/compare/1.1.0...1.2.0))

View File

@@ -16,17 +16,13 @@ Please submit pull requests to update information or to add new institutions or
- [BIDS - Berkeley Institute for Data Science](https://bids.berkeley.edu/) - [BIDS - Berkeley Institute for Data Science](https://bids.berkeley.edu/)
- [Data 8](http://data8.org/) - [Data 8](https://www.data8.org/)
- [GitHub organization](https://github.com/data-8) - [GitHub organization](https://github.com/data-8)
- [NERSC](https://www.nersc.gov/) - [NERSC](https://www.nersc.gov/)
- [Press release on Jupyter and Cori](https://www.nersc.gov/news-publications/nersc-news/nersc-center-news/2016/jupyter-notebooks-will-open-up-new-possibilities-on-nerscs-cori-supercomputer/)
- [Moving and sharing data](https://www.nersc.gov/assets/Uploads/03-MovingAndSharingData-Cholia.pdf)
- [Research IT](https://research-it.berkeley.edu) - [Research IT](https://research-it.berkeley.edu)
- [JupyterHub server supports campus research computation](https://research-it.berkeley.edu/blog/17/01/24/free-fully-loaded-jupyterhub-server-supports-campus-research-computation) - [JupyterHub server supports campus research computation](https://research-it.berkeley.edu/news/free-fully-loaded-jupyterhub-server-supports-campus-research-computation)
### University of California Davis ### University of California Davis
@@ -86,7 +82,7 @@ Within CERN, there are two noteworthy JupyterHub deployments in operation:
[ETH Zurich](https://ethz.ch/en.html), (Federal Institute of Technology Zurich), is a public research university in Zürich, Switzerland, with focus on science, technology, engineering, and mathematics, although its 16 departments span a variety of disciplines and subjects. [ETH Zurich](https://ethz.ch/en.html), (Federal Institute of Technology Zurich), is a public research university in Zürich, Switzerland, with focus on science, technology, engineering, and mathematics, although its 16 departments span a variety of disciplines and subjects.
The [Educational Development and Technology](https://ethz.ch/en/the-eth-zurich/organisation/departments/educational-development-and-technology.html) unit provides JupyterHub exclusively for teaching and learning, integrated in the learning management system [Moodle](https://ethz.ch/staffnet/en/teaching/academic-support/it-services-teaching/teaching-applications/moodle-service.html). Each course gets its individually configured JupyterHub environment deployed on a on-premise Kubernetes cluster. The [Educational Development and Technology](https://ethz.ch/en/the-eth-zurich/organisation/departments/teaching-and-learning.html) unit provides JupyterHub exclusively for teaching and learning, integrated in the learning management system [Moodle](https://ethz.ch/staffnet/en/teaching/academic-support/it-services-teaching/teaching-applications/moodle-service.html). Each course gets its individually configured JupyterHub environment deployed on a on-premise Kubernetes cluster.
- [ETH JupyterHub](https://ethz.ch/staffnet/en/teaching/academic-support/it-services-teaching/teaching-applications/jupyterhub.html) for teaching and learning - [ETH JupyterHub](https://ethz.ch/staffnet/en/teaching/academic-support/it-services-teaching/teaching-applications/jupyterhub.html) for teaching and learning
@@ -125,16 +121,15 @@ The [Educational Development and Technology](https://ethz.ch/en/the-eth-zurich/o
### Paderborn University ### Paderborn University
- [Data Science (DICE) group](https://dice-research.org) - [Data Science (DICE) group](https://dice-research.org)
- [nbgraderutils](https://github.com/dice-group/nbgraderutils): Use JupyterHub + nbgrader + iJava kernel for online Java exercises. Used in lecture Statistical Natural Language Processing. - [JavaOnlineExercises](https://github.com/dice-group/JavaOnlineExercises): Use JupyterHub + nbgrader + iJava kernel for online Java exercises. Used in lecture Statistical Natural Language Processing.
### Penn State University ### Penn State University
- [Press release](https://news.psu.edu/story/523093/2018/05/24/new-open-source-web-apps-available-students-and-faculty): "New open-source web apps available for students and faculty" - [Press release](https://www.psu.edu/news/academics/story/new-open-source-web-apps-available-students-and-faculty): "New open-source web apps available for students and faculty"
### University of California San Diego ### University of California San Diego
- San Diego Supercomputer Center - Andrea Zonca - San Diego Supercomputer Center - Andrea Zonca
- [Deploy JupyterHub on a Supercomputer with SSH](https://zonca.github.io/2017/05/jupyterhub-hpc-batchspawner-ssh.html) - [Deploy JupyterHub on a Supercomputer with SSH](https://zonca.github.io/2017/05/jupyterhub-hpc-batchspawner-ssh.html)
- [Run Jupyterhub on a Supercomputer](https://zonca.github.io/2015/04/jupyterhub-hpc.html) - [Run Jupyterhub on a Supercomputer](https://zonca.github.io/2015/04/jupyterhub-hpc.html)
- [Deploy JupyterHub on a VM for a Workshop](https://zonca.github.io/2016/04/jupyterhub-sdsc-cloud.html) - [Deploy JupyterHub on a VM for a Workshop](https://zonca.github.io/2016/04/jupyterhub-sdsc-cloud.html)
@@ -154,7 +149,7 @@ The [Educational Development and Technology](https://ethz.ch/en/the-eth-zurich/o
### Elucidata ### Elucidata
- What's new in Jupyter Notebooks @[Elucidata](https://elucidata.io/): - What's new in Jupyter Notebooks @[Elucidata](https://www.elucidata.io/):
- [Using Jupyter Notebooks with Jupyterhub on GCP, managed by GKE](https://medium.com/elucidata/why-you-should-be-using-a-jupyter-notebook-8385a4ccd93d) - [Using Jupyter Notebooks with Jupyterhub on GCP, managed by GKE](https://medium.com/elucidata/why-you-should-be-using-a-jupyter-notebook-8385a4ccd93d)
## Service Providers ## Service Providers
@@ -174,7 +169,7 @@ The [Educational Development and Technology](https://ethz.ch/en/the-eth-zurich/o
### Microsoft Azure ### Microsoft Azure
- [Azure Data Science Virtual Machine release notes](https://docs.microsoft.com/en-us/azure/machine-learning/machine-learning-data-science-linux-dsvm-intro) - [Azure Data Science Virtual Machine release notes](https://learn.microsoft.com/en-us/azure/machine-learning/machine-learning-data-science-linux-dsvm-intro)
### Rackspace Carina ### Rackspace Carina
@@ -202,5 +197,5 @@ The [Educational Development and Technology](https://ethz.ch/en/the-eth-zurich/o
- https://www.walkingrandomly.com/?p=5734 - https://www.walkingrandomly.com/?p=5734
- https://wrdrd.com/docs/consulting/education-technology - https://wrdrd.com/docs/consulting/education-technology
- https://bitbucket.org/jackhale/fenics-jupyter - https://bitbucket.org/jackhale/fenics-jupyter
- [LinuxCluster blog](https://linuxcluster.wordpress.com/category/application/jupyterhub/) - [LinuxCluster blog](https://thelinuxcluster.com/category/application/jupyterhub/)
- [Spark Cluster on OpenStack with Multi-User Jupyter Notebook](https://arnesund.com/2015/09/21/spark-cluster-on-openstack-with-multi-user-jupyter-notebook/) - [Spark Cluster on OpenStack with Multi-User Jupyter Notebook](https://arnesund.com/2015/09/21/spark-cluster-on-openstack-with-multi-user-jupyter-notebook/)

View File

@@ -563,7 +563,7 @@ and an example of its configuration is found [here](https://github.com/jupyter/n
nbviewer can also be run as a Hub-Managed Service as described [nbviewer README][nbviewer example] nbviewer can also be run as a Hub-Managed Service as described [nbviewer README][nbviewer example]
section on securing the notebook viewer. section on securing the notebook viewer.
[requests]: https://docs.python-requests.org/en/master/ [requests]: https://requests.readthedocs.io
[services_auth]: ../api/services.auth.html [services_auth]: ../api/services.auth.html
[nbviewer example]: https://github.com/jupyter/nbviewer#securing-the-notebook-viewer [nbviewer example]: https://github.com/jupyter/nbviewer#securing-the-notebook-viewer
[fastapi example]: https://github.com/jupyterhub/jupyterhub/tree/HEAD/examples/service-fastapi [fastapi example]: https://github.com/jupyterhub/jupyterhub/tree/HEAD/examples/service-fastapi

View File

@@ -467,7 +467,7 @@ spawner, does not support limits and guarantees. One of the spawners
that supports limits and guarantees is the that supports limits and guarantees is the
[`systemdspawner`](https://github.com/jupyterhub/systemdspawner). [`systemdspawner`](https://github.com/jupyterhub/systemdspawner).
### Memory Limits & Guarantees ### Memory Limits and Guarantees
`c.Spawner.mem_limit`: A **limit** specifies the _maximum amount of memory_ `c.Spawner.mem_limit`: A **limit** specifies the _maximum amount of memory_
that may be allocated, though there is no promise that the maximum amount will that may be allocated, though there is no promise that the maximum amount will
@@ -487,7 +487,7 @@ available for the single-user notebook server to use. The environment variable
limits and providing these guarantees.** If these values are set to `None`, no limits and providing these guarantees.** If these values are set to `None`, no
limits or guarantees are provided, and no environment values are set. limits or guarantees are provided, and no environment values are set.
### CPU Limits & Guarantees ### CPU Limits and Guarantees
`c.Spawner.cpu_limit`: In supported spawners, you can set `c.Spawner.cpu_limit`: In supported spawners, you can set
`c.Spawner.cpu_limit` to limit the total number of cpu-cores that a `c.Spawner.cpu_limit` to limit the total number of cpu-cores that a

View File

@@ -46,7 +46,7 @@ If you want to run docker on a computer that has a public IP then you should
(as in MUST) **secure it with ssl** by adding ssl options to your docker (as in MUST) **secure it with ssl** by adding ssl options to your docker
configuration or using an ssl enabled proxy. configuration or using an ssl enabled proxy.
[Mounting volumes](https://docs.docker.com/engine/admin/volumes/volumes/) [Mounting volumes](https://docs.docker.com/engine/storage/volumes/)
enables you to persist and store the data generated by the docker container, even when you stop the container. enables you to persist and store the data generated by the docker container, even when you stop the container.
The persistent data can be stored on the host system, outside the container. The persistent data can be stored on the host system, outside the container.

View File

@@ -11,7 +11,6 @@ Before installing JupyterHub, you will need:
installing Python packages is helpful. installing Python packages is helpful.
- [Node.js {{node_min}}](https://www.npmjs.com/) or greater, along with npm. [Install Node.js/npm](https://docs.npmjs.com/getting-started/installing-node), - [Node.js {{node_min}}](https://www.npmjs.com/) or greater, along with npm. [Install Node.js/npm](https://docs.npmjs.com/getting-started/installing-node),
using your operating system's package manager. using your operating system's package manager.
- If you are using **`conda`**, the nodejs and npm dependencies will be installed for - If you are using **`conda`**, the nodejs and npm dependencies will be installed for
you by conda. you by conda.

6508
jsx/package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -35,45 +35,45 @@
"testEnvironment": "jsdom" "testEnvironment": "jsdom"
}, },
"dependencies": { "dependencies": {
"bootstrap": "^5.3.6", "bootstrap": "^5.3.7",
"history": "^5.3.0", "history": "^5.3.0",
"lodash": "^4.17.21", "lodash": "^4.17.21",
"prop-types": "^15.8.1", "prop-types": "^15.8.1",
"react": "^19.1.0", "react": "^19.1.1",
"react-bootstrap": "^2.10.10", "react-bootstrap": "^2.10.10",
"react-dom": "^19.1.0", "react-dom": "^19.1.1",
"react-icons": "^5.5.0", "react-icons": "^5.5.0",
"react-redux": "^9.2.0", "react-redux": "^9.2.0",
"react-router": "^7.6.1", "react-router": "^7.7.1",
"redux": "^5.0.1", "redux": "^5.0.1",
"regenerator-runtime": "^0.14.1" "regenerator-runtime": "^0.14.1"
}, },
"devDependencies": { "devDependencies": {
"@babel/core": "^7.27.4", "@babel/core": "^7.28.0",
"@babel/preset-env": "^7.27.2", "@babel/preset-env": "^7.28.0",
"@babel/preset-react": "^7.27.1", "@babel/preset-react": "^7.27.1",
"@eslint/eslintrc": "^3.3.1", "@eslint/eslintrc": "^3.3.1",
"@eslint/js": "^9.28.0", "@eslint/js": "^9.32.0",
"@testing-library/jest-dom": "^6.6.3", "@testing-library/jest-dom": "^6.6.4",
"@testing-library/react": "^16.3.0", "@testing-library/react": "^16.3.0",
"@testing-library/user-event": "^14.6.1", "@testing-library/user-event": "^14.6.1",
"@webpack-cli/serve": "^3.0.1", "@webpack-cli/serve": "^3.0.1",
"babel-jest": "^29.7.0", "babel-jest": "^30.0.5",
"babel-loader": "^10.0.0", "babel-loader": "^10.0.0",
"css-loader": "^7.1.2", "css-loader": "^7.1.2",
"eslint": "^9.28.0", "eslint": "^9.32.0",
"eslint-plugin-prettier": "^5.4.1", "eslint-plugin-prettier": "^5.5.3",
"eslint-plugin-react": "^7.37.5", "eslint-plugin-react": "^7.37.5",
"eslint-plugin-unused-imports": "^4.1.4", "eslint-plugin-unused-imports": "^4.1.4",
"file-loader": "^6.2.0", "file-loader": "^6.2.0",
"globals": "^16.2.0", "globals": "^16.3.0",
"identity-obj-proxy": "^3.0.0", "identity-obj-proxy": "^3.0.0",
"jest": "^29.7.0", "jest": "^30.0.5",
"jest-environment-jsdom": "^29.7.0", "jest-environment-jsdom": "^30.0.5",
"prettier": "^3.5.3", "prettier": "^3.6.2",
"style-loader": "^4.0.0", "style-loader": "^4.0.0",
"webpack": "^5.99.9", "webpack": "^5.101.0",
"webpack-cli": "^6.0.1", "webpack-cli": "^6.0.1",
"webpack-dev-server": "^5.2.1" "webpack-dev-server": "^5.2.2"
} }
} }

View File

@@ -139,7 +139,7 @@ test("Interacting with PaginationFooter causes page refresh", async () => {
render(groupsJsx(updateGroupsSpy)); render(groupsJsx(updateGroupsSpy));
}); });
expect(updateGroupsSpy).toBeCalledWith(0, 2); expect(updateGroupsSpy).toHaveBeenCalledWith(0, 2);
var lastState = var lastState =
mockReducers.mock.results[mockReducers.mock.results.length - 1].value; mockReducers.mock.results[mockReducers.mock.results.length - 1].value;
@@ -153,5 +153,5 @@ test("Interacting with PaginationFooter causes page refresh", async () => {
}); });
expect(searchParams.get("offset")).toEqual("2"); expect(searchParams.get("offset")).toEqual("2");
// FIXME: useSelector mocks prevent updateGroups from being called // FIXME: useSelector mocks prevent updateGroups from being called
// expect(updateGroupsSpy).toBeCalledWith(2, 2); // expect(updateGroupsSpy).toHaveBeenCalledWith(2, 2);
}); });

View File

@@ -591,14 +591,14 @@ test("Search for user calls updateUsers with name filter", async () => {
expect(searchParams.get("offset")).toEqual(null); expect(searchParams.get("offset")).toEqual(null);
// FIXME: useSelector mocks prevent updateUsers from being called // FIXME: useSelector mocks prevent updateUsers from being called
// expect(mockUpdateUsers.mock.calls).toHaveLength(2); // expect(mockUpdateUsers.mock.calls).toHaveLength(2);
// expect(mockUpdateUsers).toBeCalledWith(0, 100, "a"); // expect(mockUpdateUsers).toHaveBeenCalledWith(0, 100, "a");
await user.type(search, "b"); await user.type(search, "b");
expect(search.value).toEqual("ab"); expect(search.value).toEqual("ab");
await act(async () => { await act(async () => {
jest.runAllTimers(); jest.runAllTimers();
}); });
expect(searchParams.get("name_filter")).toEqual("ab"); expect(searchParams.get("name_filter")).toEqual("ab");
// expect(mockUpdateUsers).toBeCalledWith(0, 100, "ab"); // expect(mockUpdateUsers).toHaveBeenCalledWith(0, 100, "ab");
}); });
test("Interacting with PaginationFooter requests page update", async () => { test("Interacting with PaginationFooter requests page update", async () => {
@@ -606,7 +606,7 @@ test("Interacting with PaginationFooter requests page update", async () => {
render(serverDashboardJsx()); render(serverDashboardJsx());
}); });
expect(mockUpdateUsers).toBeCalledWith(defaultUpdateUsersParams); expect(mockUpdateUsers).toHaveBeenCalledWith(defaultUpdateUsersParams);
var n = 3; var n = 3;
expect(searchParams.get("offset")).toEqual(null); expect(searchParams.get("offset")).toEqual(null);
@@ -619,7 +619,7 @@ test("Interacting with PaginationFooter requests page update", async () => {
}); });
expect(searchParams.get("offset")).toEqual("2"); expect(searchParams.get("offset")).toEqual("2");
// FIXME: useSelector mocks prevent updateUsers from being called // FIXME: useSelector mocks prevent updateUsers from being called
// expect(mockUpdateUsers).toBeCalledWith({ // expect(mockUpdateUsers).toHaveBeenCalledWith({
// ...defaultUpdateUsersParams, // ...defaultUpdateUsersParams,
// offset: 2, // offset: 2,
// }); // });

View File

@@ -870,10 +870,10 @@ class SpawnProgressAPIHandler(APIHandler):
html_message = getattr(exc, "jupyterhub_html_message", "") html_message = getattr(exc, "jupyterhub_html_message", "")
if html_message: if html_message:
failed_event['html_message'] = html_message failed_event['html_message'] = html_message
await self.send_event(failed_event)
return
else: else:
raise web.HTTPError(400, "%s is not starting...", spawner._log_name) raise web.HTTPError(400, "%s is not starting...", spawner._log_name)
await self.send_event(failed_event)
return
# retrieve progress events from the Spawner # retrieve progress events from the Spawner
async with aclosing( async with aclosing(
@@ -1034,7 +1034,7 @@ class ActivityAPIHandler(APIHandler):
user.name, user.name,
server_name, server_name,
isoformat(last_activity), isoformat(last_activity),
isoformat(user.last_activity), isoformat(spawner.last_activity),
) )
self.db.commit() self.db.commit()

View File

@@ -32,7 +32,7 @@ from dateutil.parser import parse as parse_date
from jinja2 import ChoiceLoader, Environment, FileSystemLoader, PrefixLoader from jinja2 import ChoiceLoader, Environment, FileSystemLoader, PrefixLoader
from jupyter_events.logger import EventLogger from jupyter_events.logger import EventLogger
from sqlalchemy.exc import OperationalError, SQLAlchemyError from sqlalchemy.exc import OperationalError, SQLAlchemyError
from sqlalchemy.orm import joinedload from sqlalchemy.orm import contains_eager, selectinload
from tornado import gen, web from tornado import gen, web
from tornado.httpclient import AsyncHTTPClient from tornado.httpclient import AsyncHTTPClient
from tornado.ioloop import IOLoop, PeriodicCallback from tornado.ioloop import IOLoop, PeriodicCallback
@@ -3097,9 +3097,10 @@ class JupyterHub(Application):
.filter(orm.Spawner.server != None) .filter(orm.Spawner.server != None)
# pre-load relationships to avoid O(N active servers) queries # pre-load relationships to avoid O(N active servers) queries
.options( .options(
joinedload(orm.User._orm_spawners), contains_eager(orm.User._orm_spawners),
joinedload(orm.Spawner.server), selectinload(orm.Spawner.server),
) )
.populate_existing()
): ):
# instantiate Spawner wrapper and check if it's still alive # instantiate Spawner wrapper and check if it's still alive
# spawner should be running # spawner should be running

View File

@@ -62,7 +62,7 @@ async def test_submit_login_form(app, browser, user_special_chars):
await browser.goto(login_url) await browser.goto(login_url)
await login(browser, user.name, password=user.name) await login(browser, user.name, password=user.name)
expected_url = public_url(app, user) expected_url = public_url(app, user)
await expect(browser).to_have_url(expected_url) await browser.wait_for_url(expected_url)
@pytest.mark.parametrize( @pytest.mark.parametrize(
@@ -143,7 +143,7 @@ async def test_open_url_login(
await expect(browser).to_have_url(re.compile(pattern)) await expect(browser).to_have_url(re.compile(pattern))
await expect(browser).not_to_have_url(re.compile(".*/user/.*")) await expect(browser).not_to_have_url(re.compile(".*/user/.*"))
else: else:
await expect(browser).to_have_url( await browser.wait_for_url(
re.compile(".*/user/" + f"{user_special_chars.urlname}/") re.compile(".*/user/" + f"{user_special_chars.urlname}/")
) )
@@ -883,17 +883,15 @@ async def test_menu_bar(app, browser, page, logged_in, user_special_chars):
expected_url = f"hub/login?next={url_escape(app.base_url)}" expected_url = f"hub/login?next={url_escape(app.base_url)}"
assert expected_url in browser.url assert expected_url in browser.url
else: else:
await expect(browser).to_have_url( await browser.wait_for_url(
re.compile(f".*/user/{user_special_chars.urlname}/") re.compile(f".*/user/{user_special_chars.urlname}/")
) )
await browser.go_back() await browser.go_back()
await expect(browser).to_have_url(re.compile(".*" + page)) await browser.wait_for_url(re.compile(".*" + page))
elif index == 3: elif index == 3:
await expect(browser).to_have_url(re.compile(".*/login")) await browser.wait_for_url(re.compile(".*/login"))
else: else:
await expect(browser).to_have_url( await browser.wait_for_url(re.compile(".*" + expected_link_bar_url[index]))
re.compile(".*" + expected_link_bar_url[index])
)
# LOGOUT # LOGOUT
@@ -924,8 +922,8 @@ async def test_user_logout(app, browser, url, user_special_chars):
# verify that user can login after logout # verify that user can login after logout
await login(browser, user.name, password=user.name) await login(browser, user.name, password=user.name)
await expect(browser).to_have_url( await browser.wait_for_url(
re.compile(".*/user/" + f"{user_special_chars.urlname}/") re.compile(".*/user/" + f"{user_special_chars.urlname}/"),
) )
@@ -1016,7 +1014,7 @@ async def test_oauth_page(
await expect(scopes_element).not_to_be_visible() await expect(scopes_element).not_to_be_visible()
for scopes_element in scopes_elements for scopes_element in scopes_elements
] ]
# checking that all scopes granded to user are presented in POST form (scope_list) # checking that all scopes granted to user are presented in POST form (scope_list)
scope_list_oauth_page = [ scope_list_oauth_page = [
await scopes_element.get_attribute("value") await scopes_element.get_attribute("value")
for scopes_element in scopes_elements for scopes_element in scopes_elements
@@ -1288,8 +1286,8 @@ async def test_start_stop_server_on_admin_page(
spawn_btn_xpath = f'//a[contains(@href, "spawn/{username}")]/button[contains(@class, "btn-light")]' spawn_btn_xpath = f'//a[contains(@href, "spawn/{username}")]/button[contains(@class, "btn-light")]'
spawn_btn = browser.locator(spawn_btn_xpath) spawn_btn = browser.locator(spawn_btn_xpath)
await expect(spawn_btn).to_be_enabled() await expect(spawn_btn).to_be_enabled()
async with browser.expect_navigation(url=f"**/user/{username}/"): await spawn_btn.click()
await spawn_btn.click() await browser.wait_for_url(url=f"**/user/{username}/")
async def click_access_server(browser, username): async def click_access_server(browser, username):
"""access to the server for users via the Access Server button""" """access to the server for users via the Access Server button"""
@@ -1337,7 +1335,7 @@ async def test_start_stop_server_on_admin_page(
# click on Spawn page button # click on Spawn page button
await click_spawn_page(browser, user2.name) await click_spawn_page(browser, user2.name)
await expect(browser).to_have_url(re.compile(".*" + f"/user/{user2.name}/")) await browser.wait_for_url(re.compile(".*" + f"/user/{user2.name}/"))
# open/return to the Admin page # open/return to the Admin page
admin_page = url_path_join(public_host(app), app.hub.base_url, "admin") admin_page = url_path_join(public_host(app), app.hub.base_url, "admin")
@@ -1491,18 +1489,18 @@ async def test_singleuser_xsrf(
await browser.goto(login_url) await browser.goto(login_url)
await login(browser, browser_user.name, browser_user.name) await login(browser, browser_user.name, browser_user.name)
# end up at single-user # end up at single-user
await expect(browser).to_have_url(re.compile(rf".*/user/{browser_user.name}/.*")) await browser.wait_for_url(re.compile(rf".*/user/{browser_user.name}/.*"))
# wait for target user to start, too # wait for target user to start, too
await target_start await target_start
await app.proxy.add_user(target_user) await app.proxy.add_user(target_user)
# visit target user, sets credentials for second server # visit target user, sets credentials for second server
await browser.goto(public_url(app, target_user)) await browser.goto(public_url(app, target_user))
await expect(browser).to_have_url(re.compile(r".*/oauth2/authorize")) await browser.wait_for_url(re.compile(r".*/oauth2/authorize"))
auth_button = browser.locator('//button[@type="submit"]') auth_button = browser.locator('//button[@type="submit"]')
await expect(auth_button).to_be_enabled() await expect(auth_button).to_be_enabled()
await auth_button.click() await auth_button.click()
await expect(browser).to_have_url(re.compile(rf".*/user/{target_user.name}/.*")) await browser.wait_for_url(re.compile(rf".*/user/{target_user.name}/.*"))
# at this point, we are on a page served by target_user, # at this point, we are on a page served by target_user,
# logged in as browser_user # logged in as browser_user
@@ -1644,8 +1642,8 @@ async def test_singleuser_xsrf(
url_path_join(app.base_url, f"hub/spawn/{browser_user.name}/{server_name}"), url_path_join(app.base_url, f"hub/spawn/{browser_user.name}/{server_name}"),
) )
await browser.goto(url) await browser.goto(url)
await expect(browser).to_have_url( await browser.wait_for_url(
re.compile(rf".*/user/{browser_user.name}/{server_name}/.*") re.compile(rf".*/user/{browser_user.name}/{server_name}/.*"),
) )
# from named server URL, make sure we can talk to a kernel # from named server URL, make sure we can talk to a kernel
token = browser_user.new_api_token(scopes=["access:servers!user"]) token = browser_user.new_api_token(scopes=["access:servers!user"])

View File

@@ -65,7 +65,9 @@ async def test_proxy_service(app, mockservice_url):
service = mockservice_url service = mockservice_url
name = service.name name = service.name
await app.proxy.get_all_routes() await app.proxy.get_all_routes()
url = public_url(app, service) + '/foo' url = public_url(app, service)
assert url.endswith("/")
url += "foo"
r = await async_requests.get(url, allow_redirects=False) r = await async_requests.get(url, allow_redirects=False)
path = f'/services/{name}/foo' path = f'/services/{name}/foo'
r.raise_for_status() r.raise_for_status()

195
package-lock.json generated
View File

@@ -24,15 +24,18 @@
"version": "6.7.2", "version": "6.7.2",
"resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.7.2.tgz", "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-6.7.2.tgz",
"integrity": "sha512-JUOtgFW6k9u4Y+xeIaEiLr3+cjoUPiAuLXoyKOJSia6Duzb7pq+A76P9ZdPDoAoxHdHzq6gE9/jKBGXlZT8FbA==", "integrity": "sha512-JUOtgFW6k9u4Y+xeIaEiLr3+cjoUPiAuLXoyKOJSia6Duzb7pq+A76P9ZdPDoAoxHdHzq6gE9/jKBGXlZT8FbA==",
"license": "(CC-BY-4.0 AND OFL-1.1 AND MIT)",
"engines": { "engines": {
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/@parcel/watcher": { "node_modules/@parcel/watcher": {
"version": "2.4.1", "version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.4.1.tgz", "resolved": "https://registry.npmjs.org/@parcel/watcher/-/watcher-2.5.1.tgz",
"integrity": "sha512-HNjmfLQEVRZmHRET336f20H/8kOozUGwk7yajvsonjNxbj2wBTK1WsQuHkD5yYh9RxFGL2EyDHryOihOwUoKDA==", "integrity": "sha512-dfUnCxiN9H4ap84DvD2ubjw+3vUNpstxa0TneY/Paat8a3R4uQZDLSvWjmznAY/DoahqTHl9V46HF/Zs3F29pg==",
"dev": true, "dev": true,
"hasInstallScript": true,
"license": "MIT",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
"detect-libc": "^1.0.3", "detect-libc": "^1.0.3",
@@ -48,28 +51,30 @@
"url": "https://opencollective.com/parcel" "url": "https://opencollective.com/parcel"
}, },
"optionalDependencies": { "optionalDependencies": {
"@parcel/watcher-android-arm64": "2.4.1", "@parcel/watcher-android-arm64": "2.5.1",
"@parcel/watcher-darwin-arm64": "2.4.1", "@parcel/watcher-darwin-arm64": "2.5.1",
"@parcel/watcher-darwin-x64": "2.4.1", "@parcel/watcher-darwin-x64": "2.5.1",
"@parcel/watcher-freebsd-x64": "2.4.1", "@parcel/watcher-freebsd-x64": "2.5.1",
"@parcel/watcher-linux-arm-glibc": "2.4.1", "@parcel/watcher-linux-arm-glibc": "2.5.1",
"@parcel/watcher-linux-arm64-glibc": "2.4.1", "@parcel/watcher-linux-arm-musl": "2.5.1",
"@parcel/watcher-linux-arm64-musl": "2.4.1", "@parcel/watcher-linux-arm64-glibc": "2.5.1",
"@parcel/watcher-linux-x64-glibc": "2.4.1", "@parcel/watcher-linux-arm64-musl": "2.5.1",
"@parcel/watcher-linux-x64-musl": "2.4.1", "@parcel/watcher-linux-x64-glibc": "2.5.1",
"@parcel/watcher-win32-arm64": "2.4.1", "@parcel/watcher-linux-x64-musl": "2.5.1",
"@parcel/watcher-win32-ia32": "2.4.1", "@parcel/watcher-win32-arm64": "2.5.1",
"@parcel/watcher-win32-x64": "2.4.1" "@parcel/watcher-win32-ia32": "2.5.1",
"@parcel/watcher-win32-x64": "2.5.1"
} }
}, },
"node_modules/@parcel/watcher-android-arm64": { "node_modules/@parcel/watcher-android-arm64": {
"version": "2.4.1", "version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.4.1.tgz", "resolved": "https://registry.npmjs.org/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.5.1.tgz",
"integrity": "sha512-LOi/WTbbh3aTn2RYddrO8pnapixAziFl6SMxHM69r3tvdSm94JtCenaKgk1GRg5FJ5wpMCpHeW+7yqPlvZv7kg==", "integrity": "sha512-KF8+j9nNbUN8vzOFDpRMsaKBHZ/mcjEjMToVMJOhTozkDonQFFrRcfdLWn6yWKCmJKmdVxSgHiYvTCef4/qcBA==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true, "dev": true,
"license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
"android" "android"
@@ -83,13 +88,14 @@
} }
}, },
"node_modules/@parcel/watcher-darwin-arm64": { "node_modules/@parcel/watcher-darwin-arm64": {
"version": "2.4.1", "version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.4.1.tgz", "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.5.1.tgz",
"integrity": "sha512-ln41eihm5YXIY043vBrrHfn94SIBlqOWmoROhsMVTSXGh0QahKGy77tfEywQ7v3NywyxBBkGIfrWRHm0hsKtzA==", "integrity": "sha512-eAzPv5osDmZyBhou8PoF4i6RQXAfeKL9tjb3QzYuccXFMQU0ruIc/POh30ePnaOyD1UXdlKguHBmsTs53tVoPw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true, "dev": true,
"license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
"darwin" "darwin"
@@ -103,13 +109,14 @@
} }
}, },
"node_modules/@parcel/watcher-darwin-x64": { "node_modules/@parcel/watcher-darwin-x64": {
"version": "2.4.1", "version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.4.1.tgz", "resolved": "https://registry.npmjs.org/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.5.1.tgz",
"integrity": "sha512-yrw81BRLjjtHyDu7J61oPuSoeYWR3lDElcPGJyOvIXmor6DEo7/G2u1o7I38cwlcoBHQFULqF6nesIX3tsEXMg==", "integrity": "sha512-1ZXDthrnNmwv10A0/3AJNZ9JGlzrF82i3gNQcWOzd7nJ8aj+ILyW1MTxVk35Db0u91oD5Nlk9MBiujMlwmeXZg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true, "dev": true,
"license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
"darwin" "darwin"
@@ -123,13 +130,14 @@
} }
}, },
"node_modules/@parcel/watcher-freebsd-x64": { "node_modules/@parcel/watcher-freebsd-x64": {
"version": "2.4.1", "version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.4.1.tgz", "resolved": "https://registry.npmjs.org/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.5.1.tgz",
"integrity": "sha512-TJa3Pex/gX3CWIx/Co8k+ykNdDCLx+TuZj3f3h7eOjgpdKM+Mnix37RYsYU4LHhiYJz3DK5nFCCra81p6g050w==", "integrity": "sha512-SI4eljM7Flp9yPuKi8W0ird8TI/JK6CSxju3NojVI6BjHsTyK7zxA9urjVjEKJ5MBYC+bLmMcbAWlZ+rFkLpJQ==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true, "dev": true,
"license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
"freebsd" "freebsd"
@@ -143,13 +151,35 @@
} }
}, },
"node_modules/@parcel/watcher-linux-arm-glibc": { "node_modules/@parcel/watcher-linux-arm-glibc": {
"version": "2.4.1", "version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.4.1.tgz", "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.5.1.tgz",
"integrity": "sha512-4rVYDlsMEYfa537BRXxJ5UF4ddNwnr2/1O4MHM5PjI9cvV2qymvhwZSFgXqbS8YoTk5i/JR0L0JDs69BUn45YA==", "integrity": "sha512-RCdZlEyTs8geyBkkcnPWvtXLY44BCeZKmGYRtSgtwwnHR4dxfHRG3gR99XdMEdQ7KeiDdasJwwvNSF5jKtDwdA==",
"cpu": [ "cpu": [
"arm" "arm"
], ],
"dev": true, "dev": true,
"license": "MIT",
"optional": true,
"os": [
"linux"
],
"engines": {
"node": ">= 10.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/parcel"
}
},
"node_modules/@parcel/watcher-linux-arm-musl": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.5.1.tgz",
"integrity": "sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==",
"cpu": [
"arm"
],
"dev": true,
"license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@@ -163,13 +193,14 @@
} }
}, },
"node_modules/@parcel/watcher-linux-arm64-glibc": { "node_modules/@parcel/watcher-linux-arm64-glibc": {
"version": "2.4.1", "version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.4.1.tgz", "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.5.1.tgz",
"integrity": "sha512-BJ7mH985OADVLpbrzCLgrJ3TOpiZggE9FMblfO65PlOCdG++xJpKUJ0Aol74ZUIYfb8WsRlUdgrZxKkz3zXWYA==", "integrity": "sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true, "dev": true,
"license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@@ -183,13 +214,14 @@
} }
}, },
"node_modules/@parcel/watcher-linux-arm64-musl": { "node_modules/@parcel/watcher-linux-arm64-musl": {
"version": "2.4.1", "version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.4.1.tgz", "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.5.1.tgz",
"integrity": "sha512-p4Xb7JGq3MLgAfYhslU2SjoV9G0kI0Xry0kuxeG/41UfpjHGOhv7UoUDAz/jb1u2elbhazy4rRBL8PegPJFBhA==", "integrity": "sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true, "dev": true,
"license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@@ -203,13 +235,14 @@
} }
}, },
"node_modules/@parcel/watcher-linux-x64-glibc": { "node_modules/@parcel/watcher-linux-x64-glibc": {
"version": "2.4.1", "version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.4.1.tgz", "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.5.1.tgz",
"integrity": "sha512-s9O3fByZ/2pyYDPoLM6zt92yu6P4E39a03zvO0qCHOTjxmt3GHRMLuRZEWhWLASTMSrrnVNWdVI/+pUElJBBBg==", "integrity": "sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true, "dev": true,
"license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@@ -223,13 +256,14 @@
} }
}, },
"node_modules/@parcel/watcher-linux-x64-musl": { "node_modules/@parcel/watcher-linux-x64-musl": {
"version": "2.4.1", "version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.4.1.tgz", "resolved": "https://registry.npmjs.org/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.5.1.tgz",
"integrity": "sha512-L2nZTYR1myLNST0O632g0Dx9LyMNHrn6TOt76sYxWLdff3cB22/GZX2UPtJnaqQPdCRoszoY5rcOj4oMTtp5fQ==", "integrity": "sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true, "dev": true,
"license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
"linux" "linux"
@@ -243,13 +277,14 @@
} }
}, },
"node_modules/@parcel/watcher-win32-arm64": { "node_modules/@parcel/watcher-win32-arm64": {
"version": "2.4.1", "version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.4.1.tgz", "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.5.1.tgz",
"integrity": "sha512-Uq2BPp5GWhrq/lcuItCHoqxjULU1QYEcyjSO5jqqOK8RNFDBQnenMMx4gAl3v8GiWa59E9+uDM7yZ6LxwUIfRg==", "integrity": "sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==",
"cpu": [ "cpu": [
"arm64" "arm64"
], ],
"dev": true, "dev": true,
"license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
"win32" "win32"
@@ -263,13 +298,14 @@
} }
}, },
"node_modules/@parcel/watcher-win32-ia32": { "node_modules/@parcel/watcher-win32-ia32": {
"version": "2.4.1", "version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.4.1.tgz", "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-ia32/-/watcher-win32-ia32-2.5.1.tgz",
"integrity": "sha512-maNRit5QQV2kgHFSYwftmPBxiuK5u4DXjbXx7q6eKjq5dsLXZ4FJiVvlcw35QXzk0KrUecJmuVFbj4uV9oYrcw==", "integrity": "sha512-c2KkcVN+NJmuA7CGlaGD1qJh1cLfDnQsHjE89E60vUEMlqduHGCdCLJCID5geFVM0dOtA3ZiIO8BoEQmzQVfpQ==",
"cpu": [ "cpu": [
"ia32" "ia32"
], ],
"dev": true, "dev": true,
"license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
"win32" "win32"
@@ -283,13 +319,14 @@
} }
}, },
"node_modules/@parcel/watcher-win32-x64": { "node_modules/@parcel/watcher-win32-x64": {
"version": "2.4.1", "version": "2.5.1",
"resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.4.1.tgz", "resolved": "https://registry.npmjs.org/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.5.1.tgz",
"integrity": "sha512-+DvS92F9ezicfswqrvIRM2njcYJbd5mb9CUgtrHCHmvn7pPPa+nMDRu1o1bYYz/l5IB2NVGNJWiH7h1E58IF2A==", "integrity": "sha512-9lHBdJITeNR++EvSQVUcaZoWupyHfXe1jZvGZ06O/5MflPcuPLtEphScIBL+AiCWBO46tDSHzWyD0uDmmZqsgA==",
"cpu": [ "cpu": [
"x64" "x64"
], ],
"dev": true, "dev": true,
"license": "MIT",
"optional": true, "optional": true,
"os": [ "os": [
"win32" "win32"
@@ -306,6 +343,7 @@
"version": "2.11.8", "version": "2.11.8",
"resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz", "resolved": "https://registry.npmjs.org/@popperjs/core/-/core-2.11.8.tgz",
"integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==", "integrity": "sha512-P1st0aksCrn9sGZhp8GMYwBnQsbvAWsZAX44oXNNvLHGqAOcoVxmjZiohstwQ7SqKnbR47akdNi+uleWD8+g6A==",
"license": "MIT",
"peer": true, "peer": true,
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
@@ -313,9 +351,9 @@
} }
}, },
"node_modules/bootstrap": { "node_modules/bootstrap": {
"version": "5.3.6", "version": "5.3.7",
"resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.6.tgz", "resolved": "https://registry.npmjs.org/bootstrap/-/bootstrap-5.3.7.tgz",
"integrity": "sha512-jX0GAcRzvdwISuvArXn3m7KZscWWFAf1MKBcnzaN02qWMb3jpMoUX4/qgeiGzqyIb4ojulRzs89UCUmGcFSzTA==", "integrity": "sha512-7KgiD8UHjfcPBHEpDNg+zGz8L3LqR3GVwqZiBRFX04a1BCArZOz1r2kjly2HQ0WokqTO0v1nF+QAt8dsW4lKlw==",
"funding": [ "funding": [
{ {
"type": "github", "type": "github",
@@ -336,6 +374,7 @@
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
"dev": true, "dev": true,
"license": "MIT",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
"fill-range": "^7.1.1" "fill-range": "^7.1.1"
@@ -345,10 +384,11 @@
} }
}, },
"node_modules/chokidar": { "node_modules/chokidar": {
"version": "4.0.1", "version": "4.0.3",
"resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.1.tgz", "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz",
"integrity": "sha512-n8enUVCED/KVRQlab1hr3MVpcVMvxtZjmEa956u+4YijlmQED223XMSYj2tLuKvr4jcCTzNNMpQDUer72MMmzA==", "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"readdirp": "^4.0.1" "readdirp": "^4.0.1"
}, },
@@ -364,6 +404,7 @@
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz",
"integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==", "integrity": "sha512-pGjwhsmsp4kL2RTz08wcOlGN83otlqHeD/Z5T8GXZB+/YcpQ/dgo+lbU8ZsGxV0HIvqqxo9l7mqYwyYMD9bKDg==",
"dev": true, "dev": true,
"license": "Apache-2.0",
"optional": true, "optional": true,
"bin": { "bin": {
"detect-libc": "bin/detect-libc.js" "detect-libc": "bin/detect-libc.js"
@@ -377,6 +418,7 @@
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
"dev": true, "dev": true,
"license": "MIT",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
"to-regex-range": "^5.0.1" "to-regex-range": "^5.0.1"
@@ -386,9 +428,9 @@
} }
}, },
"node_modules/immutable": { "node_modules/immutable": {
"version": "5.0.3", "version": "5.1.3",
"resolved": "https://registry.npmjs.org/immutable/-/immutable-5.0.3.tgz", "resolved": "https://registry.npmjs.org/immutable/-/immutable-5.1.3.tgz",
"integrity": "sha512-P8IdPQHq3lA1xVeBRi5VPqUm5HDgKnx0Ru51wZz5mjxHr5n3RWhjIpOFU7ybkUxfB+5IToy+OLaHYDBIWsv+uw==", "integrity": "sha512-+chQdDfvscSF1SJqv2gn4SRO2ZyS3xL3r7IW/wWEEzrzLisnOlKiQu5ytC/BVNcS15C39WT2Hg/bjKjDMcu+zg==",
"dev": true, "dev": true,
"license": "MIT" "license": "MIT"
}, },
@@ -397,6 +439,7 @@
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
"dev": true, "dev": true,
"license": "MIT",
"optional": true, "optional": true,
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
@@ -407,6 +450,7 @@
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
"dev": true, "dev": true,
"license": "MIT",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
"is-extglob": "^2.1.1" "is-extglob": "^2.1.1"
@@ -420,6 +464,7 @@
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
"dev": true, "dev": true,
"license": "MIT",
"optional": true, "optional": true,
"engines": { "engines": {
"node": ">=0.12.0" "node": ">=0.12.0"
@@ -428,13 +473,15 @@
"node_modules/jquery": { "node_modules/jquery": {
"version": "3.7.1", "version": "3.7.1",
"resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz", "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.7.1.tgz",
"integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==" "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==",
"license": "MIT"
}, },
"node_modules/micromatch": { "node_modules/micromatch": {
"version": "4.0.8", "version": "4.0.8",
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
"dev": true, "dev": true,
"license": "MIT",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
"braces": "^3.0.3", "braces": "^3.0.3",
@@ -448,6 +495,7 @@
"version": "2.30.1", "version": "2.30.1",
"resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz", "resolved": "https://registry.npmjs.org/moment/-/moment-2.30.1.tgz",
"integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==", "integrity": "sha512-uEmtNhbDOrWPFS+hdjFCBfy9f2YoyzRpwcl+DqpC6taX21FzsTLQVbMV/W7PzNSX6x/bhC1zA3c2UQ5NzH6how==",
"license": "MIT",
"engines": { "engines": {
"node": "*" "node": "*"
} }
@@ -457,6 +505,7 @@
"resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz",
"integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==",
"dev": true, "dev": true,
"license": "MIT",
"optional": true "optional": true
}, },
"node_modules/picomatch": { "node_modules/picomatch": {
@@ -464,6 +513,7 @@
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
"dev": true, "dev": true,
"license": "MIT",
"optional": true, "optional": true,
"engines": { "engines": {
"node": ">=8.6" "node": ">=8.6"
@@ -473,12 +523,13 @@
} }
}, },
"node_modules/readdirp": { "node_modules/readdirp": {
"version": "4.0.1", "version": "4.1.2",
"resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.0.1.tgz", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz",
"integrity": "sha512-GkMg9uOTpIWWKbSsgwb5fA4EavTR+SG/PMPoAY8hkhHfEEY0/vqljY+XHqtDf2cr2IJtoNRDbrrEpZUiZCkYRw==", "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==",
"dev": true, "dev": true,
"license": "MIT",
"engines": { "engines": {
"node": ">= 14.16.0" "node": ">= 14.18.0"
}, },
"funding": { "funding": {
"type": "individual", "type": "individual",
@@ -499,9 +550,9 @@
} }
}, },
"node_modules/sass": { "node_modules/sass": {
"version": "1.89.1", "version": "1.90.0",
"resolved": "https://registry.npmjs.org/sass/-/sass-1.89.1.tgz", "resolved": "https://registry.npmjs.org/sass/-/sass-1.90.0.tgz",
"integrity": "sha512-eMLLkl+qz7tx/0cJ9wI+w09GQ2zodTkcE/aVfywwdlRcI3EO19xGnbmJwg/JMIm+5MxVJ6outddLZ4Von4E++Q==", "integrity": "sha512-9GUyuksjw70uNpb1MTYWsH9MQHOHY6kwfnkafC24+7aOMZn9+rVMBxRbLvw756mrBFbIsFg6Xw9IkR2Fnn3k+Q==",
"dev": true, "dev": true,
"license": "MIT", "license": "MIT",
"dependencies": { "dependencies": {
@@ -520,10 +571,11 @@
} }
}, },
"node_modules/source-map-js": { "node_modules/source-map-js": {
"version": "1.2.0", "version": "1.2.1",
"resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.0.tgz", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz",
"integrity": "sha512-itJW8lvSA0TXEphiRoawsCksnlf8SyvmFzIhltqAHluXd88pkCd+cXJVHTDwdCr0IzwptSm035IHQktUu1QUMg==", "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==",
"dev": true, "dev": true,
"license": "BSD-3-Clause",
"engines": { "engines": {
"node": ">=0.10.0" "node": ">=0.10.0"
} }
@@ -533,6 +585,7 @@
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
"dev": true, "dev": true,
"license": "MIT",
"optional": true, "optional": true,
"dependencies": { "dependencies": {
"is-number": "^7.0.0" "is-number": "^7.0.0"

View File

@@ -51,7 +51,7 @@ test = [
# the test test_nbclassic_control_panel. # the test test_nbclassic_control_panel.
"nbclassic", "nbclassic",
"pytest>=3.3", "pytest>=3.3",
"pytest-asyncio>=0.17,!=0.23.*", "pytest-asyncio>=0.17,!=0.23.*,<1.0.0",
"pytest-cov", "pytest-cov",
"pytest-rerunfailures", "pytest-rerunfailures",
"requests-mock", "requests-mock",