diff --git a/.idea/runConfigurations/Start_sophon_backend.xml b/.idea/runConfigurations/Start_sophon_backend.xml
index 92070dc..886edf4 100644
--- a/.idea/runConfigurations/Start_sophon_backend.xml
+++ b/.idea/runConfigurations/Start_sophon_backend.xml
@@ -6,14 +6,14 @@
-
+
-
-
-
+
+
+
-
+
diff --git a/backend/.dockerignore b/backend/.dockerignore
new file mode 120000
index 0000000..3e4e48b
--- /dev/null
+++ b/backend/.dockerignore
@@ -0,0 +1 @@
+.gitignore
\ No newline at end of file
diff --git a/backend/Dockerfile b/backend/Dockerfile
new file mode 100644
index 0000000..48315d8
--- /dev/null
+++ b/backend/Dockerfile
@@ -0,0 +1,36 @@
+FROM python:3.9.7-bullseye
+LABEL maintainer="Stefano Pigozzi "
+
+# Set the base workdir as seen in https://hub.docker.com/_/python/
+WORKDIR /usr/src/app
+
+# Install Poetry
+RUN pip install 'poetry==1.1.11'
+
+# Copy the environment requirements into the docker image
+COPY pyproject.toml ./pyproject.toml
+COPY poetry.lock ./poetry.lock
+
+# Install the dependencies using Poetry
+RUN poetry install --no-root
+
+# Copy the rest of the project into the container
+COPY . .
+
+# Install the project using Poetry
+RUN poetry install
+
+# Disable buffering as it may cause problems in logs
+ENV PYTHONUNBUFFERED=1
+
+# Tell Django where the settings module is
+ENV DJANGO_SETTINGS_MODULE="sophon.settings"
+
+# Store the DBM file in a nice place
+ENV DJANGO_PROXY_FILE="/run/sophon/proxy/proxy.dbm"
+
+# Set the static files directory
+ENV DJANGO_STATIC_ROOT="/run/sophon/static"
+
+# Start the uvicorn server
+ENTRYPOINT ["bash", "./docker_start.sh"]
diff --git a/backend/docker_start.sh b/backend/docker_start.sh
new file mode 100644
index 0000000..6226bb2
--- /dev/null
+++ b/backend/docker_start.sh
@@ -0,0 +1,6 @@
+#!/bin/bash
+
+poetry run python -O ./manage.py migrate --no-input
+poetry run python -O ./manage.py collectstatic --no-input
+poetry run python -O ./manage.py initsuperuser
+poetry run python -O -m gunicorn sophon.asgi:application -k uvicorn.workers.UvicornWorker -b 0.0.0.0:8000
diff --git a/backend/poetry.lock b/backend/poetry.lock
index d3ba6b8..f80c4de 100644
--- a/backend/poetry.lock
+++ b/backend/poetry.lock
@@ -47,6 +47,20 @@ category = "main"
optional = false
python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+[[package]]
+name = "coloredlogs"
+version = "15.0.1"
+description = "Colored terminal output for Python's logging module"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[package.dependencies]
+humanfriendly = ">=9.1"
+
+[package.extras]
+cron = ["capturer (>=2.4)"]
+
[[package]]
name = "deprecation"
version = "2.1.0"
@@ -97,18 +111,6 @@ python-versions = ">=3.5"
[package.dependencies]
Django = ">=2.2"
-[[package]]
-name = "django-pam"
-version = "2.0.1"
-description = "Django PAM authentication backend implementation."
-category = "main"
-optional = false
-python-versions = "*"
-
-[package.dependencies]
-python-pam = "*"
-six = "*"
-
[[package]]
name = "djangorestframework"
version = "3.12.4"
@@ -159,6 +161,17 @@ category = "main"
optional = false
python-versions = ">=3.6"
+[[package]]
+name = "humanfriendly"
+version = "10.0"
+description = "Human friendly output for text interfaces using Python"
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*"
+
+[package.dependencies]
+pyreadline3 = { version = "*", markers = "sys_platform == \"win32\" and python_version >= \"3.8\"" }
+
[[package]]
name = "idna"
version = "3.2"
@@ -167,6 +180,14 @@ category = "main"
optional = false
python-versions = ">=3.5"
+[[package]]
+name = "lazy-object-proxy"
+version = "1.6.0"
+description = "A fast and thorough lazy object proxy."
+category = "main"
+optional = false
+python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, !=3.5.*"
+
[[package]]
name = "markdown"
version = "3.3.4"
@@ -219,9 +240,9 @@ optional = false
python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*"
[[package]]
-name = "python-pam"
-version = "1.8.4"
-description = "Python PAM module using ctypes, py3/py2"
+name = "pyreadline3"
+version = "3.3"
+description = "A python implementation of GNU readline."
category = "main"
optional = false
python-versions = "*"
@@ -260,14 +281,6 @@ urllib3 = ">=1.21.1,<1.27"
socks = ["PySocks (>=1.5.6,!=1.5.7)", "win-inet-pton"]
use_chardet_on_py3 = ["chardet (>=3.0.2,<5)"]
-[[package]]
-name = "six"
-version = "1.16.0"
-description = "Python 2 and 3 compatibility utilities"
-category = "main"
-optional = false
-python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*"
-
[[package]]
name = "sqlparse"
version = "0.4.2"
@@ -320,7 +333,7 @@ test = ["websockets"]
[metadata]
lock-version = "1.1"
python-versions = "^3.9"
-content-hash = "13e8ed6ddfe56e2e389508a531dea9997cd5bbf2da8170a685770c148472a9e0"
+content-hash = "aed43668a7f9bc2d3703264168f93ce5f2a4a3836c8619e2fb385906765f74b4"
[metadata.files]
asgiref = [
@@ -343,6 +356,10 @@ colorama = [
{ file = "colorama-0.4.4-py2.py3-none-any.whl", hash = "sha256:9f47eda37229f68eee03b24b9748937c7dc3868f906e8ba69fbcbdd3bc5dc3e2" },
{ file = "colorama-0.4.4.tar.gz", hash = "sha256:5941b2b48a20143d2267e95b1c2a7603ce057ee39fd88e7329b0c292aa16869b" },
]
+coloredlogs = [
+ { file = "coloredlogs-15.0.1-py2.py3-none-any.whl", hash = "sha256:612ee75c546f53e92e70049c9dbfcc18c935a2b9a53b66085ce9ef6a6e5c0934" },
+ { file = "coloredlogs-15.0.1.tar.gz", hash = "sha256:7c991aa71a4577af2f82600d8f8f3a89f936baeaf9b50a9c197da014e5bf16b0" },
+]
deprecation = [
{ file = "deprecation-2.1.0-py2.py3-none-any.whl", hash = "sha256:a10811591210e1fb0e768a8c25517cabeabcba6f0bf96564f8ff45189f90b14a" },
{ file = "deprecation-2.1.0.tar.gz", hash = "sha256:72b3bde64e5d778694b0cf68178aed03d15e15477116add3fb773e581f9518ff" },
@@ -359,10 +376,6 @@ django-filter = [
{file = "django-filter-2.4.0.tar.gz", hash = "sha256:84e9d5bb93f237e451db814ed422a3a625751cbc9968b484ecc74964a8696b06"},
{file = "django_filter-2.4.0-py3-none-any.whl", hash = "sha256:e00d32cebdb3d54273c48f4f878f898dced8d5dfaad009438fe61ebdf535ace1"},
]
-django-pam = [
- { file = "django-pam-2.0.1.tar.gz", hash = "sha256:1c0e670589188e80b800ac5e93ac6599fc1bf5d850bb0f752dab6619efe7875c" },
- { file = "django_pam-2.0.1-py2.py3-none-any.whl", hash = "sha256:c34d2f42e9e92791ca313a1a9a3c2c5e1cd1370ceb706aae34de03f093e17414" },
-]
djangorestframework = [
{ file = "djangorestframework-3.12.4-py3-none-any.whl", hash = "sha256:6d1d59f623a5ad0509fe0d6bfe93cbdfe17b8116ebc8eda86d45f6e16e819aaf" },
{ file = "djangorestframework-3.12.4.tar.gz", hash = "sha256:f747949a8ddac876e879190df194b925c177cdeb725a099db1460872f7c0a7f2" },
@@ -379,10 +392,38 @@ h11 = [
{ file = "h11-0.12.0-py3-none-any.whl", hash = "sha256:36a3cb8c0a032f56e2da7084577878a035d3b61d104230d4bd49c0c6b555a9c6" },
{ file = "h11-0.12.0.tar.gz", hash = "sha256:47222cb6067e4a307d535814917cd98fd0a57b6788ce715755fa2b6c28b56042" },
]
+humanfriendly = [
+ { file = "humanfriendly-10.0-py2.py3-none-any.whl", hash = "sha256:1697e1a8a8f550fd43c2865cd84542fc175a61dcb779b6fee18cf6b6ccba1477" },
+ { file = "humanfriendly-10.0.tar.gz", hash = "sha256:6b0b831ce8f15f7300721aa49829fc4e83921a9a301cc7f606be6686a2288ddc" },
+]
idna = [
{ file = "idna-3.2-py3-none-any.whl", hash = "sha256:14475042e284991034cb48e06f6851428fb14c4dc953acd9be9a5e95c7b6dd7a" },
{ file = "idna-3.2.tar.gz", hash = "sha256:467fbad99067910785144ce333826c71fb0e63a425657295239737f7ecd125f3" },
]
+lazy-object-proxy = [
+ { file = "lazy-object-proxy-1.6.0.tar.gz", hash = "sha256:489000d368377571c6f982fba6497f2aa13c6d1facc40660963da62f5c379726" },
+ { file = "lazy_object_proxy-1.6.0-cp27-cp27m-macosx_10_14_x86_64.whl", hash = "sha256:c6938967f8528b3668622a9ed3b31d145fab161a32f5891ea7b84f6b790be05b" },
+ { file = "lazy_object_proxy-1.6.0-cp27-cp27m-win32.whl", hash = "sha256:ebfd274dcd5133e0afae738e6d9da4323c3eb021b3e13052d8cbd0e457b1256e" },
+ { file = "lazy_object_proxy-1.6.0-cp27-cp27m-win_amd64.whl", hash = "sha256:ed361bb83436f117f9917d282a456f9e5009ea12fd6de8742d1a4752c3017e93" },
+ { file = "lazy_object_proxy-1.6.0-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:d900d949b707778696fdf01036f58c9876a0d8bfe116e8d220cfd4b15f14e741" },
+ { file = "lazy_object_proxy-1.6.0-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:5743a5ab42ae40caa8421b320ebf3a998f89c85cdc8376d6b2e00bd12bd1b587" },
+ { file = "lazy_object_proxy-1.6.0-cp36-cp36m-manylinux2014_aarch64.whl", hash = "sha256:bf34e368e8dd976423396555078def5cfc3039ebc6fc06d1ae2c5a65eebbcde4" },
+ { file = "lazy_object_proxy-1.6.0-cp36-cp36m-win32.whl", hash = "sha256:b579f8acbf2bdd9ea200b1d5dea36abd93cabf56cf626ab9c744a432e15c815f" },
+ { file = "lazy_object_proxy-1.6.0-cp36-cp36m-win_amd64.whl", hash = "sha256:4f60460e9f1eb632584c9685bccea152f4ac2130e299784dbaf9fae9f49891b3" },
+ { file = "lazy_object_proxy-1.6.0-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d7124f52f3bd259f510651450e18e0fd081ed82f3c08541dffc7b94b883aa981" },
+ { file = "lazy_object_proxy-1.6.0-cp37-cp37m-manylinux2014_aarch64.whl", hash = "sha256:22ddd618cefe54305df49e4c069fa65715be4ad0e78e8d252a33debf00f6ede2" },
+ { file = "lazy_object_proxy-1.6.0-cp37-cp37m-win32.whl", hash = "sha256:9d397bf41caad3f489e10774667310d73cb9c4258e9aed94b9ec734b34b495fd" },
+ { file = "lazy_object_proxy-1.6.0-cp37-cp37m-win_amd64.whl", hash = "sha256:24a5045889cc2729033b3e604d496c2b6f588c754f7a62027ad4437a7ecc4837" },
+ { file = "lazy_object_proxy-1.6.0-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:17e0967ba374fc24141738c69736da90e94419338fd4c7c7bef01ee26b339653" },
+ { file = "lazy_object_proxy-1.6.0-cp38-cp38-manylinux2014_aarch64.whl", hash = "sha256:410283732af311b51b837894fa2f24f2c0039aa7f220135192b38fcc42bd43d3" },
+ { file = "lazy_object_proxy-1.6.0-cp38-cp38-win32.whl", hash = "sha256:85fb7608121fd5621cc4377a8961d0b32ccf84a7285b4f1d21988b2eae2868e8" },
+ { file = "lazy_object_proxy-1.6.0-cp38-cp38-win_amd64.whl", hash = "sha256:d1c2676e3d840852a2de7c7d5d76407c772927addff8d742b9808fe0afccebdf" },
+ { file = "lazy_object_proxy-1.6.0-cp39-cp39-macosx_10_14_x86_64.whl", hash = "sha256:b865b01a2e7f96db0c5d12cfea590f98d8c5ba64ad222300d93ce6ff9138bcad" },
+ { file = "lazy_object_proxy-1.6.0-cp39-cp39-manylinux1_x86_64.whl", hash = "sha256:4732c765372bd78a2d6b2150a6e99d00a78ec963375f236979c0626b97ed8e43" },
+ { file = "lazy_object_proxy-1.6.0-cp39-cp39-manylinux2014_aarch64.whl", hash = "sha256:9698110e36e2df951c7c36b6729e96429c9c32b3331989ef19976592c5f3c77a" },
+ { file = "lazy_object_proxy-1.6.0-cp39-cp39-win32.whl", hash = "sha256:1fee665d2638491f4d6e55bd483e15ef21f6c8c2095f235fef72601021e64f61" },
+ { file = "lazy_object_proxy-1.6.0-cp39-cp39-win_amd64.whl", hash = "sha256:f5144c75445ae3ca2057faac03fda5a902eff196702b0a24daf1d6ce0650514b" },
+]
markdown = [
{ file = "Markdown-3.3.4-py3-none-any.whl", hash = "sha256:96c3ba1261de2f7547b46a00ea8463832c921d3f9d6aba3f255a6f71386db20c" },
{ file = "Markdown-3.3.4.tar.gz", hash = "sha256:31b5b491868dcc87d6c24b7e3d19a0d730d59d3e46f4eea6430a321bed387a49" },
@@ -427,16 +468,16 @@ pydantic = [
{file = "pydantic-1.7.4.tar.gz", hash = "sha256:0a1abcbd525fbb52da58c813d54c2ec706c31a91afdb75411a73dd1dec036595"},
]
pyparsing = [
- {file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b"},
- {file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1"},
+ { file = "pyparsing-2.4.7-py2.py3-none-any.whl", hash = "sha256:ef9d7589ef3c200abe66653d3f1ab1033c3c419ae9b9bdb1240a85b024efc88b" },
+ { file = "pyparsing-2.4.7.tar.gz", hash = "sha256:c203ec8783bf771a155b207279b9bccb8dea02d8f0c9e5f8ead507bc3246ecc1" },
]
-python-pam = [
- {file = "python-pam-1.8.4.tar.gz", hash = "sha256:c856d9c89fedb33951dd8a95727ae57c6887b02d065bbdffd2fd9dbc0183909b"},
- {file = "python_pam-1.8.4-py2.py3-none-any.whl", hash = "sha256:8439b470b564ac558585b5a3ffce0fce48d1eeebdff19add48279c33de7da0e0"},
+pyreadline3 = [
+ { file = "pyreadline3-3.3-py3-none-any.whl", hash = "sha256:0003fd0079d152ecbd8111202c5a7dfa6a5569ffd65b235e45f3c2ecbee337b4" },
+ { file = "pyreadline3-3.3.tar.gz", hash = "sha256:ff3b5a1ac0010d0967869f723e687d42cabc7dccf33b14934c92aa5168d260b3" },
]
pytz = [
- {file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798"},
- {file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da"},
+ { file = "pytz-2021.1-py2.py3-none-any.whl", hash = "sha256:eb10ce3e7736052ed3623d49975ce333bcd712c7bb19a58b9e2089d4057d0798" },
+ { file = "pytz-2021.1.tar.gz", hash = "sha256:83a4a90894bf38e243cf052c8b58f381bfe9a7a483f6a9cab140bc7f702ac4da" },
]
pywin32 = [
{file = "pywin32-227-cp27-cp27m-win32.whl", hash = "sha256:371fcc39416d736401f0274dd64c2302728c9e034808e37381b5e1b22be4a6b0"},
@@ -456,10 +497,6 @@ requests = [
{file = "requests-2.26.0-py2.py3-none-any.whl", hash = "sha256:6c1246513ecd5ecd4528a0906f910e8f0f9c6b8ec72030dc9fd154dc1a6efd24"},
{file = "requests-2.26.0.tar.gz", hash = "sha256:b8aa58f8cf793ffd8782d3d8cb19e66ef36f7aba4353eec859e74678b01b07a7"},
]
-six = [
- {file = "six-1.16.0-py2.py3-none-any.whl", hash = "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254"},
- {file = "six-1.16.0.tar.gz", hash = "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926"},
-]
sqlparse = [
{file = "sqlparse-0.4.2-py3-none-any.whl", hash = "sha256:48719e356bb8b42991bdbb1e8b83223757b93789c00910a616a071910ca4a64d"},
{file = "sqlparse-0.4.2.tar.gz", hash = "sha256:0c00730c74263a94e5a9919ade150dfc3b19c574389985446148402998287dae"},
diff --git a/backend/pyproject.toml b/backend/pyproject.toml
index 32a747f..f9cb80a 100644
--- a/backend/pyproject.toml
+++ b/backend/pyproject.toml
@@ -12,12 +12,13 @@ djangorestframework = "^3.12.4"
Markdown = "^3.3.4"
django-filter = "^2.4.0"
pydantic = "~1.7.3"
-django-pam = "^2.0.0"
deprecation = "^2.1.0"
docker = "^5.0.2"
django-cors-headers = "^3.8.0"
uvicorn = "^0.15.0"
gunicorn = "^20.1.0"
+coloredlogs = "^15.0.1"
+lazy-object-proxy = "^1.6.0"
[tool.poetry.dev-dependencies]
diff --git a/backend/sophon/core/management/__init__.py b/backend/sophon/core/management/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/backend/sophon/core/management/commands/__init__.py b/backend/sophon/core/management/commands/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/backend/sophon/core/management/commands/initsuperuser.py b/backend/sophon/core/management/commands/initsuperuser.py
new file mode 100644
index 0000000..ada5b00
--- /dev/null
+++ b/backend/sophon/core/management/commands/initsuperuser.py
@@ -0,0 +1,25 @@
+import logging
+from os import environ
+
+from django.contrib.auth import get_user_model
+from django.core.management.base import BaseCommand
+
+log = logging.getLogger(__name__)
+
+
+# Based on https://stackoverflow.com/a/39745576/4334568
+class Command(BaseCommand):
+ help = "Creates the superuser non-interactively if it doesn't exist"
+
+ def handle(self, *args, **options):
+ User = get_user_model()
+ log.debug("Checking if an user already exists...")
+ if not User.objects.exists():
+ log.info("Creating superuser...")
+ User.objects.create_superuser(
+ username=environ["DJANGO_SU_USERNAME"],
+ email=environ["DJANGO_SU_EMAIL"],
+ password=environ["DJANGO_SU_PASSWORD"],
+ )
+ else:
+ log.info("An user already exists, not creating superuser.")
diff --git a/backend/sophon/notebooks/__init__.py b/backend/sophon/notebooks/__init__.py
index 3b4de9e..1a43c78 100644
--- a/backend/sophon/notebooks/__init__.py
+++ b/backend/sophon/notebooks/__init__.py
@@ -8,13 +8,6 @@ It depends on the following :mod:`django` apps:
It can be configured with the following :data:`os.environ` keys:
-- ``DOCKER_HOST``: The URL to the Docker host.
-- ``DOCKER_TLS_VERIFY``: Verify the host against a CA certificate.
-- ``DOCKER_CERT_PATH``: A path to a directory containing TLS certificates to use when connecting to the Docker host.
-- ``APACHE_PROXY_EXPRESS_DBM``: The filename of the ``proxy_express`` DBM file to write to.
-- ``APACHE_PROXY_BASE_DOMAIN``: The base domain for virtualhost reverse proxying.
-- ``APACHE_PROXY_HTTP_PROTOCOL``: The http_protocol that Apache uses to make the containers available to the public.
-- ``APACHE_PROXY_WS_PROTOCOL``: The http_protocol that Apache uses to make the Jupyter websocket available to the public.
- ``SOPHON_CONTAINER_PREFIX``: The prefix added to the names of notebooks' containers will have.
- ``SOPHON_VOLUME_PREFIX``: The prefix added to the names of notebooks' volumes will have.
- ``SOPHON_NETWORK_PREFIX``: The prefix added to the names of notebooks' volumes will have.
diff --git a/backend/sophon/notebooks/admin.py b/backend/sophon/notebooks/admin.py
index 3e4c9c6..3b2632e 100644
--- a/backend/sophon/notebooks/admin.py
+++ b/backend/sophon/notebooks/admin.py
@@ -13,7 +13,6 @@ class NotebookAdmin(SophonAdmin):
"locked_by",
"container_image",
"container_id",
- "port",
)
list_filter = (
@@ -40,7 +39,6 @@ class NotebookAdmin(SophonAdmin):
"fields": (
"container_image",
"container_id",
- "internet_access",
),
},
),
@@ -48,6 +46,7 @@ class NotebookAdmin(SophonAdmin):
"Proxy", {
"fields": (
"port",
+ "internal_url",
),
},
),
diff --git a/backend/sophon/notebooks/apache.conf b/backend/sophon/notebooks/apache.conf
deleted file mode 100644
index beca315..0000000
--- a/backend/sophon/notebooks/apache.conf
+++ /dev/null
@@ -1,20 +0,0 @@
-# Log rewrite at the maximum level
-# DO NOT ENABLE THIS IN PRODUCTION OR YOUR LOGS WILL BE FLOODED!
-# LogLevel "rewrite:trace8"
-
-# Enable rewriting
-RewriteEngine on
-RewriteMap "sophonproxy" "dbm=gdbm:/mnt/tebi/ext4/workspace/sophon/proxy.dbm"
-
-# Preserve host
-ProxyPreserveHost on
-
-# Proxy websockets
-RewriteCond "%{HTTP_HOST}" ".dev.sophon.steffo.eu$" [NC]
-RewriteCond "%{HTTP:Connection}" "Upgrade" [NC]
-RewriteCond "%{HTTP:Upgrade}" "websocket" [NC]
-RewriteRule "/(.*)" "ws://${sophonproxy:%{HTTP_HOST}}/$1" [P,L]
-
-# Proxy regular requests
-RewriteCond "%{HTTP_HOST}" ".dev.sophon.steffo.eu$" [NC]
-RewriteRule "/(.*)" "http://${sophonproxy:%{HTTP_HOST}}/$1" [P,L]
diff --git a/backend/sophon/notebooks/apache.py b/backend/sophon/notebooks/apache.py
index e0f4562..5488728 100644
--- a/backend/sophon/notebooks/apache.py
+++ b/backend/sophon/notebooks/apache.py
@@ -1,21 +1,28 @@
import dbm.gnu
import logging
import os
+import pathlib
import socket
import typing as t
+from django.conf import settings
+
log = logging.getLogger(__name__)
-db_name = os.environ.get("APACHE_PROXY_EXPRESS_DBM", "proxy.dbm")
-base_domain = os.environ["APACHE_PROXY_BASE_DOMAIN"]
-http_protocol = os.environ.get("APACHE_PROXY_HTTP_PROTOCOL", "https")
class ApacheDB:
- def __init__(self, filename: str):
- self.filename: str = filename
- log.debug(f"{self.filename}: Initializing...")
- with dbm.open(self.filename, "c"):
+ def __init__(self, path: t.Union[str, pathlib.Path]):
+ self.path: pathlib.Path
+ if isinstance(path, str):
+ self.path = pathlib.Path(path)
+ else:
+ self.path = path
+ log.debug(f"Initializing directories...")
+ os.makedirs(self.path.parent, exist_ok=True)
+ log.debug(f"Initializing database...")
+ with dbm.open(str(self.path), "c"):
pass
+ log.debug("Done!")
@staticmethod
def convert_to_bytes(item: t.Union[str, bytes]) -> bytes:
@@ -26,27 +33,25 @@ class ApacheDB:
def __getitem__(self, key: t.Union[str, bytes]) -> bytes:
key = self.convert_to_bytes(key)
- log.debug(f"{self.filename}: Getting {key!r}...")
- with dbm.open(self.filename, "r") as adb:
+ log.debug(f"{self.path}: Getting {key!r}...")
+ with dbm.open(str(self.path), "r") as adb:
return adb[key]
def __setitem__(self, key: bytes, value: bytes) -> None:
key = self.convert_to_bytes(key)
value = self.convert_to_bytes(value)
- log.debug(f"{self.filename}: Setting {key!r} → {value!r}...")
- with dbm.open(self.filename, "w") as adb:
+ log.debug(f"{self.path}: Setting {key!r} → {value!r}...")
+ with dbm.open(str(self.path), "w") as adb:
adb[key] = value
def __delitem__(self, key):
key = self.convert_to_bytes(key)
- log.debug(f"{self.filename}: Deleting {key!r}...")
- with dbm.open(self.filename, "w") as adb:
+ log.debug(f"{self.path}: Deleting {key!r}...")
+ with dbm.open(str(self.path), "w") as adb:
del adb[key]
-log.info(f"Creating proxy_express database: {db_name}")
-db = ApacheDB(db_name)
-log.info(f"Created proxy_express database!")
+db: ApacheDB = ApacheDB(settings.PROXY_FILE)
def get_ephemeral_port() -> int:
diff --git a/backend/sophon/notebooks/docker.py b/backend/sophon/notebooks/docker.py
index de1aa63..77be0d7 100644
--- a/backend/sophon/notebooks/docker.py
+++ b/backend/sophon/notebooks/docker.py
@@ -4,17 +4,24 @@ import time
import docker.errors
import docker.models.containers
+import lazy_object_proxy
+from django.conf import settings
log = logging.getLogger(__name__)
-log.info("Connecting to Docker daemon...")
-try:
- client: docker.DockerClient = docker.from_env()
-except docker.errors.DockerException as e:
- log.fatal("Could not connect to the Docker daemon!")
- raise
-else:
- log.info("Connection to Docker daemon successful!")
+
+def get_docker_client() -> docker.DockerClient:
+ log.info("Connecting to Docker daemon...")
+ try:
+ result = docker.from_env(environment=settings.__dict__)
+ except docker.errors.DockerException as e:
+ log.fatal("Could not connect to the Docker daemon!")
+ else:
+ log.info("Connection to Docker daemon successful!")
+ return result
+
+
+client: docker.DockerClient = lazy_object_proxy.Proxy(get_docker_client)
class HealthState(enum.IntEnum):
@@ -49,6 +56,13 @@ def get_health(container: docker.models.containers.Container) -> HealthState:
return state
+def get_proxy_container() -> docker.models.containers.Container:
+ """
+ :return: The container of the proxy, having the name specified in `settings.PROXY_CONTAINER_NAME`.
+ """
+ return client.containers.get(settings.PROXY_CONTAINER_NAME)
+
+
def sleep_until_container_has_started(container: docker.models.containers.Container) -> HealthState:
"""
Sleep until the specified container is not anymore in the ``starting`` state.
diff --git a/backend/sophon/notebooks/migrations/0009_alter_notebook_slug.py b/backend/sophon/notebooks/migrations/0009_alter_notebook_slug.py
new file mode 100644
index 0000000..806f1f2
--- /dev/null
+++ b/backend/sophon/notebooks/migrations/0009_alter_notebook_slug.py
@@ -0,0 +1,22 @@
+# Generated by Django 3.2.7 on 2021-10-18 18:02
+
+from django.db import migrations, models
+
+import sophon.notebooks.validators
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ('notebooks', '0008_alter_notebook_container_image'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='notebook',
+ name='slug',
+ field=models.SlugField(
+ help_text='Unique alphanumeric string which identifies the project. Changing this once the container has been created will break Docker!',
+ max_length=64, primary_key=True, serialize=False,
+ validators=[sophon.notebooks.validators.DisallowedValuesValidator(['api', 'proxy', 'backend', 'frontend', 'src'])], verbose_name='Slug'),
+ ),
+ ]
diff --git a/backend/sophon/notebooks/migrations/0010_auto_20211019_1738.py b/backend/sophon/notebooks/migrations/0010_auto_20211019_1738.py
new file mode 100644
index 0000000..ff04150
--- /dev/null
+++ b/backend/sophon/notebooks/migrations/0010_auto_20211019_1738.py
@@ -0,0 +1,30 @@
+# Generated by Django 3.2.7 on 2021-10-19 17:38
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ('notebooks', '0009_alter_notebook_slug'),
+ ]
+
+ operations = [
+ migrations.RemoveField(
+ model_name='notebook',
+ name='internet_access',
+ ),
+ migrations.AddField(
+ model_name='notebook',
+ name='internal_url',
+ field=models.IntegerField(blank=True,
+ help_text='The URL reachable from the proxy where the container is available. Can be null if the notebook is not running.',
+ null=True, verbose_name='Internal URL'),
+ ),
+ migrations.AlterField(
+ model_name='notebook',
+ name='port',
+ field=models.IntegerField(blank=True,
+ help_text='The port number of the local machine at which the container is available. Can be null if the notebook is not running, or if the proxy itself is running in a Docker container.',
+ null=True, verbose_name='Local port number'),
+ ),
+ ]
diff --git a/backend/sophon/notebooks/migrations/0011_auto_20211019_1745.py b/backend/sophon/notebooks/migrations/0011_auto_20211019_1745.py
new file mode 100644
index 0000000..99c3216
--- /dev/null
+++ b/backend/sophon/notebooks/migrations/0011_auto_20211019_1745.py
@@ -0,0 +1,29 @@
+# Generated by Django 3.2.7 on 2021-10-19 17:45
+
+from django.db import migrations, models
+
+import sophon.notebooks.validators
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ('notebooks', '0010_auto_20211019_1738'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='notebook',
+ name='container_image',
+ field=models.CharField(choices=[('steffo45/jupyterlab-docker-sophon', 'Python (Sophonic)')], default='steffo45/jupyterlab-docker-sophon',
+ help_text='The Docker image to run for this notebook.', max_length=256, verbose_name='Docker image'),
+ ),
+ migrations.AlterField(
+ model_name='notebook',
+ name='slug',
+ field=models.SlugField(
+ help_text='Unique alphanumeric string which identifies the project. Changing this once the container has been created will break Docker!',
+ max_length=64, primary_key=True, serialize=False,
+ validators=[sophon.notebooks.validators.DisallowedValuesValidator(['api', 'static', 'proxy', 'backend', 'frontend', 'src'])],
+ verbose_name='Slug'),
+ ),
+ ]
diff --git a/backend/sophon/notebooks/migrations/0012_alter_notebook_internal_url.py b/backend/sophon/notebooks/migrations/0012_alter_notebook_internal_url.py
new file mode 100644
index 0000000..04239c0
--- /dev/null
+++ b/backend/sophon/notebooks/migrations/0012_alter_notebook_internal_url.py
@@ -0,0 +1,19 @@
+# Generated by Django 3.2.7 on 2021-10-19 19:46
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+ dependencies = [
+ ('notebooks', '0011_auto_20211019_1745'),
+ ]
+
+ operations = [
+ migrations.AlterField(
+ model_name='notebook',
+ name='internal_url',
+ field=models.CharField(blank=True,
+ help_text='The URL reachable from the proxy where the container is available. Can be null if the notebook is not running.',
+ max_length=512, null=True, verbose_name='Internal URL'),
+ ),
+ ]
diff --git a/backend/sophon/notebooks/models.py b/backend/sophon/notebooks/models.py
index 327da3f..61b89be 100644
--- a/backend/sophon/notebooks/models.py
+++ b/backend/sophon/notebooks/models.py
@@ -1,7 +1,6 @@
from __future__ import annotations
import logging
-import os
import typing as t
import docker.errors
@@ -9,15 +8,17 @@ import docker.models.containers
import docker.models.images
import docker.models.networks
import docker.models.volumes
+from django.conf import settings
from django.contrib.auth.models import User
from django.db import models
from sophon.core.models import SophonGroupModel, ResearchGroup
from sophon.notebooks.apache import db as apache_db
-from sophon.notebooks.apache import get_ephemeral_port, base_domain, http_protocol
+from sophon.notebooks.apache import get_ephemeral_port
from sophon.notebooks.docker import client as docker_client
-from sophon.notebooks.docker import sleep_until_container_has_started
+from sophon.notebooks.docker import sleep_until_container_has_started, get_proxy_container
from sophon.notebooks.jupyter import generate_secure_token
+from sophon.notebooks.validators import DisallowedValuesValidator
from sophon.projects.models import ResearchProject
module_name = __name__
@@ -42,6 +43,16 @@ class Notebook(SophonGroupModel):
help_text="Unique alphanumeric string which identifies the project. Changing this once the container has been created will break Docker!",
max_length=64,
primary_key=True,
+ validators=[
+ DisallowedValuesValidator([
+ "api", # reserved for docker deployment
+ "static", # reserved for production deployment
+ "proxy", # reserved for future use
+ "backend", # reserved for future use
+ "frontend", # reserved for future use
+ "src", # reserved for future use
+ ])
+ ]
)
project = models.ForeignKey(
@@ -65,13 +76,13 @@ class Notebook(SophonGroupModel):
# Remember to make a migration when changing this!
IMAGE_CHOICES = (
- ("jupyter/base-notebook", "Base"),
- ("jupyter/minimal-notebook", "Python"),
- ("jupyter/scipy-notebook", "Python (Scientific)"),
- ("jupyter/tensorflow-notebook", "Python (Tensorflow)"),
- ("jupyter/r-notebook", "Python + R"),
- ("jupyter/pyspark-notebook", "Python (Scientific) + Apache Spark"),
- ("jupyter/all-spark-notebook", "Python (Scientific) + Scala + R + Apache Spark"),
+ # ("jupyter/base-notebook", "Base"),
+ # ("jupyter/minimal-notebook", "Python"),
+ # ("jupyter/scipy-notebook", "Python (Scientific)"),
+ # ("jupyter/tensorflow-notebook", "Python (Tensorflow)"),
+ # ("jupyter/r-notebook", "Python + R"),
+ # ("jupyter/pyspark-notebook", "Python (Scientific) + Apache Spark"),
+ # ("jupyter/all-spark-notebook", "Python (Scientific) + Scala + R + Apache Spark"),
("steffo45/jupyterlab-docker-sophon", "Python (Sophonic)"),
)
@@ -83,14 +94,6 @@ class Notebook(SophonGroupModel):
default="steffo45/jupyterlab-docker-sophon",
)
- # TODO: Find a way to prevent Internet access without using the --internal flag, as it doesn't allow to expose ports
- internet_access = models.BooleanField(
- "Allow internet access",
- help_text="If true, the notebook will be able to access the Internet as the host machine. Can only be set by a superuser via the admin interface. "
- "Does not currently do anything.",
- default=True,
- )
-
jupyter_token = models.CharField(
"Jupyter Access Token",
help_text="The token to allow access to the JupyterLab editor.",
@@ -107,10 +110,17 @@ class Notebook(SophonGroupModel):
port = models.IntegerField(
"Local port number",
- help_text="The port number of the local machine at which the container is available. Can be null if the notebook is not running.",
+ help_text="The port number of the local machine at which the container is available. Can be null if the notebook is not running, or if the proxy itself is running in a Docker container.",
blank=True, null=True,
)
+ internal_url = models.CharField(
+ "Internal URL",
+ help_text="The URL reachable from the proxy where the container is available. Can be null if the notebook is not running.",
+ blank=True, null=True,
+ max_length=512,
+ )
+
def get_group(self) -> ResearchGroup:
return self.project.group
@@ -122,7 +132,6 @@ class Notebook(SophonGroupModel):
"name",
"locked_by",
"container_image",
- "internet_access",
"is_running",
"lab_url",
"legacy_notebook_url",
@@ -165,28 +174,28 @@ class Notebook(SophonGroupModel):
"""
:return: The name given to the container associated with this :class:`Notebook`.
"""
- return f"{os.environ.get('SOPHON_CONTAINER_PREFIX', 'sophon-container')}-{self.slug}"
+ return f"{settings.DOCKER_CONTAINER_PREFIX}-{self.slug}"
@property
def volume_name(self) -> str:
"""
:return: The name given to the volume associated with this :class:`Notebook`.
"""
- return f"{os.environ.get('SOPHON_VOLUME_PREFIX', 'sophon-volume')}-{self.slug}"
+ return f"{settings.DOCKER_VOLUME_PREFIX}-{self.slug}"
@property
def network_name(self) -> str:
"""
:return: The name given to the network associated with this :class:`Notebook`.
"""
- return f"{os.environ.get('SOPHON_NETWORK_PREFIX', 'sophon-network')}-{self.slug}"
+ return f"{settings.DOCKER_NETWORK_PREFIX}-{self.slug}"
@property
def external_domain(self) -> str:
"""
:return: The domain name where this :class:`Notebook` will be accessible on the Internet after its container is started.
"""
- return f"{self.slug}.{base_domain}"
+ return f"{self.slug}.{settings.PROXY_BASE_DOMAIN}"
@property
def lab_url(self) -> t.Optional[str]:
@@ -197,7 +206,7 @@ class Notebook(SophonGroupModel):
"""
if not self.is_running:
return None
- return f"{http_protocol}://{self.external_domain}/lab?token={self.jupyter_token}"
+ return f"{settings.PROXY_PROTOCOL}://{self.external_domain}/lab?token={self.jupyter_token}"
@property
def legacy_notebook_url(self) -> t.Optional[str]:
@@ -208,13 +217,13 @@ class Notebook(SophonGroupModel):
"""
if not self.is_running:
return None
- return f"{http_protocol}://{self.external_domain}/tree?token={self.jupyter_token}"
+ return f"{settings.PROXY_PROTOCOL}://{self.external_domain}/tree?token={self.jupyter_token}"
@property
- def internal_domain(self) -> t.Optional[str]:
+ def local_domain(self) -> t.Optional[str]:
"""
:return: The domain name where this :class:`Notebook` is accessible on the local machine, or :data:`None` if no port has been assigned to this
- container yet.
+ container yet or if the proxy itself is running in a container.
"""
if self.port is None:
return None
@@ -296,7 +305,7 @@ class Notebook(SophonGroupModel):
self.log.debug(f"Network does not exist, creating it now...")
network = docker_client.networks.create(
name=self.network_name,
- internal=not self.internet_access,
+ internal=True,
)
self.log.info(f"Created {network!r}")
return network
@@ -305,9 +314,22 @@ class Notebook(SophonGroupModel):
"""
Disable the proxying of this :class:`Notebook` by removing its URLs from the :data:`apache_db` and its port from :attr:`.port`.
"""
+ if settings.PROXY_CONTAINER_NAME:
+ self.log.debug("Getting the notebook network...")
+ network = self.get_network()
- self.log.debug("Unassigning port...")
- self.port = None
+ self.log.debug("Getting proxy container...")
+ proxy = get_proxy_container()
+
+ self.log.debug("Disconnecting proxy container from the network...")
+ try:
+ network.disconnect(proxy)
+ except docker.errors.APIError:
+ self.log.debug("Container was already disconnected.")
+
+ else:
+ self.log.debug("Unassigning port...")
+ self.port = None
self.log.debug("Removing entry from the apache_db...")
try:
@@ -315,7 +337,7 @@ class Notebook(SophonGroupModel):
except KeyError:
pass
- self.log.debug("Clearing port from the SQL database...")
+ self.log.debug("Clearing proxy data from the SQL database...")
self.save()
def enable_proxying(self) -> None:
@@ -323,13 +345,30 @@ class Notebook(SophonGroupModel):
Enable the proxying of this :class:`Notebook` by adding its URLs to the :data:`apache_db` and its port to :attr:`.port`.
"""
- self.log.debug("Getting free port...")
- self.port: int = get_ephemeral_port()
+ if settings.PROXY_CONTAINER_NAME:
+ self.log.debug("Getting the notebook network...")
+ network = self.get_network()
- self.log.debug("Adding entry to the apache_db...")
- apache_db[self.external_domain] = f"{self.internal_domain}"
+ self.log.debug("Getting proxy container...")
+ proxy = get_proxy_container()
- self.log.debug("Saving port to the SQL database...")
+ self.log.debug("Connecting proxy container to the network...")
+ network.connect(proxy)
+
+ self.log.debug("Setting internal_url...")
+ self.internal_url = f"{self.container_name}:8888"
+
+ self.log.debug("Adding entry to the apache_db...")
+ apache_db[bytes(self.external_domain, encoding="ascii")] = bytes(self.internal_url, encoding="ascii")
+
+ else:
+ self.log.debug("Getting free port...")
+ self.port: int = get_ephemeral_port()
+
+ self.log.debug("Adding entry to the apache_db...")
+ apache_db[bytes(self.external_domain, encoding="ascii")] = bytes(f"{self.local_domain}", encoding="ascii")
+
+ self.log.debug("Saving proxy data to the SQL database...")
self.save()
def get_container(self) -> t.Optional[docker.models.containers.Container]:
@@ -449,7 +488,7 @@ class Notebook(SophonGroupModel):
name=self.container_name,
ports={
"8888/tcp": (f"127.0.0.1", f"{self.port}/tcp")
- },
+ } if self.port else {},
environment={
"JUPYTER_ENABLE_LAB": "yes",
"RESTARTABLE": "yes",
@@ -462,6 +501,7 @@ class Notebook(SophonGroupModel):
"mode": "rw",
}
},
+ network=network.name,
)
self.log.debug("Storing container_id in the SQL database...")
diff --git a/backend/sophon/notebooks/validators.py b/backend/sophon/notebooks/validators.py
new file mode 100644
index 0000000..96e125b
--- /dev/null
+++ b/backend/sophon/notebooks/validators.py
@@ -0,0 +1,16 @@
+from django.core.validators import ValidationError
+from django.utils.deconstruct import deconstructible
+from django.utils.translation import gettext_lazy as _
+
+
+@deconstructible
+class DisallowedValuesValidator:
+ def __init__(self, values):
+ self.values = values
+
+ def __call__(self, value):
+ if value in self.values:
+ raise ValidationError(
+ _("%(value)s is a disallowed value."),
+ params={"value": value},
+ )
diff --git a/backend/sophon/settings.py b/backend/sophon/settings.py
index 6273572..340d94c 100644
--- a/backend/sophon/settings.py
+++ b/backend/sophon/settings.py
@@ -10,176 +10,36 @@ For the full list of settings and their values, see
https://docs.djangoproject.com/en/3.1/ref/settings/
"""
+import logging
+import logging.config
import os
-from pathlib import Path
+import pathlib
+import platform
+import secrets
-# Build paths inside the project like this: BASE_DIR / 'subdir'.
+import coloredlogs
import rest_framework.authentication
-BASE_DIR = Path(__file__).resolve().parent.parent
+#
+# This must be the first thing set, or logging won't work properly.
-# Quick-start development settings - unsuitable for production
-# See https://docs.djangoproject.com/en/3.1/howto/deployment/checklist/
-
-# SECURITY WARNING: keep the secret key used in production secret!
-SECRET_KEY = os.environ.get("DJANGO_SECRET_KEY", "change-me-in-production")
-
-# SECURITY WARNING: don't run with debug turned on in production!
-DEBUG = bool(os.environ.get("DJANGO_DEBUG"))
-
-ALLOWED_HOSTS = []
-
-
-# Application definition
-
-INSTALLED_APPS = [
- 'django.contrib.admin',
- 'django.contrib.auth',
- 'django.contrib.contenttypes',
- 'django.contrib.sessions',
- 'django.contrib.messages',
- 'django.contrib.staticfiles',
- 'django.contrib.postgres',
- 'corsheaders',
- 'rest_framework',
- 'rest_framework.authtoken',
- 'sophon.core',
- 'sophon.projects',
- 'sophon.notebooks',
-]
-
-MIDDLEWARE = [
- 'corsheaders.middleware.CorsMiddleware',
- 'django.middleware.security.SecurityMiddleware',
- 'django.contrib.sessions.middleware.SessionMiddleware',
- 'django.middleware.common.CommonMiddleware',
- 'django.middleware.csrf.CsrfViewMiddleware',
- 'django.contrib.auth.middleware.AuthenticationMiddleware',
- 'django.contrib.messages.middleware.MessageMiddleware',
- 'django.middleware.clickjacking.XFrameOptionsMiddleware',
-]
-
-ROOT_URLCONF = 'sophon.urls'
-
-TEMPLATES = [
- {
- 'BACKEND': 'django.template.backends.django.DjangoTemplates',
- 'DIRS': [],
- 'APP_DIRS': True,
- 'OPTIONS': {
- 'context_processors': [
- 'django.template.context_processors.debug',
- 'django.template.context_processors.request',
- 'django.contrib.auth.context_processors.auth',
- 'django.contrib.messages.context_processors.messages',
- ],
- },
- },
-]
-
-WSGI_APPLICATION = 'sophon.wsgi.application'
-
-
-# Database
-# https://docs.djangoproject.com/en/3.1/ref/settings/#databases
-
-DATABASES = {
- 'default': {
- 'ENGINE': 'django.db.backends.postgresql',
- 'HOST': os.environ.get("DJANGO_DATABASE_HOST", ""), # Connect via UNIX socket (does not work on Windows)
- 'NAME': os.environ.get("DJANGO_DATABASE_NAME", "sophon"),
- 'USER': os.environ.get("DJANGO_DATABASE_USER", "sophon"), # Connect using its own user, isolating sophon from the rest of the server
- 'PASSWORD': os.environ.get("DJANGO_DATABASE_PASSWORD", ""),
- }
-}
-
-# Remember to run the following commands on a new installation:
-# sudo -iu postgres
-# createuser sophon
-# createdb --owner=sophon sophon
-#
-# If you need to run tests, also ensure `sophon` is a superuser, or it won't be able to create and drop the testing database!
-
-
-# Password validation
-# https://docs.djangoproject.com/en/3.1/ref/settings/#auth-password-validators
-
-AUTH_PASSWORD_VALIDATORS = [
- {
- 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
- },
- {
- 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
- },
- {
- 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
- },
- {
- 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
- },
-]
-
-AUTHENTICATION_BACKENDS = [
- 'django_pam.auth.backends.PAMBackend',
- 'django.contrib.auth.backends.ModelBackend',
-]
-
-
-# Internationalization
-# https://docs.djangoproject.com/en/3.1/topics/i18n/
-
-LANGUAGE_CODE = os.environ.get("DJANGO_LANGUAGE_CODE", "en-us")
-
-TIME_ZONE = os.environ.get("DJANGO_TIME_ZONE", "UTC")
-
-USE_I18N = True
-
-USE_L10N = True
-
-USE_TZ = True
-
-
-# Static files (CSS, JavaScript, Images)
-# https://docs.djangoproject.com/en/3.1/howto/static-files/
-
-STATIC_URL = '/static/'
-
-
-# Django REST framework
-# https://www.django-rest-framework.org/#example
-
-
-REST_FRAMEWORK = {
- # Use Django's standard `django.contrib.auth` permissions,
- # or allow read-only access for unauthenticated users.
- 'DEFAULT_PERMISSION_CLASSES': [
- 'rest_framework.permissions.AllowAny'
- ],
- 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
- 'DEFAULT_AUTHENTICATION_CLASSES': [
- 'rest_framework.authentication.BasicAuthentication',
- 'rest_framework.authentication.SessionAuthentication',
- 'sophon.auth1.BearerTokenAuthentication',
- ],
- 'PAGE_SIZE': 100,
-}
-
-# Logging
-# https://docs.djangoproject.com/en/3.1/ref/settings/#std:setting-LOGGING
+log = logging.getLogger(__name__)
LOGGING = {
"version": 1,
"formatters": {
"detail": {
- "format": "{asctime}\t| {levelname}\t| {name}\t| {message}",
+ "()": coloredlogs.ColoredFormatter,
+ "format": "{asctime:>19} | {name:<24} | {levelname:>8} | {message}",
"style": "{",
}
},
"handlers": {
"console": {
+ # Log to console
"class": "logging.StreamHandler",
- "level": "DEBUG",
+ "level": os.environ.get("LOGGING_LEVEL", "INFO"),
"stream": "ext://sys.stdout",
"formatter": "detail",
},
@@ -199,8 +59,330 @@ LOGGING = {
"handlers": ["console"],
}
}
+logging.config.dictConfig(LOGGING)
-# CORS
+#
-CORS_ALLOWED_ORIGINS = os.environ["DJANGO_CORS_ALLOWED_ORIGINS"].split("|")
+#
+
+# Set the cookie secret key
+try:
+ SECRET_KEY = os.environ["DJANGO_SECRET_KEY"]
+except KeyError:
+ log.warning("DJANGO_SECRET_KEY wasn't changed from the default value, cookies will be invalidated at every launch")
+ SECRET_KEY = secrets.token_urlsafe(24)
+log.debug(f"SECRET_KEY = {'*' * len(SECRET_KEY)}")
+
+
+# Set debug mode
+DEBUG = __debug__
+if DEBUG:
+ log.warning("DEBUG mode is on, run the Python interpreter with the -O option to disable.")
+log.debug(f"{DEBUG = }")
+
+
+# Set the hosts from which the admin page can be accessed, separated by pipes
+ALLOWED_HOSTS = os.environ.get("DJANGO_ALLOWED_HOSTS", "").split("|") if "DJANGO_ALLOWED_HOSTS" in os.environ else []
+if len(ALLOWED_HOSTS) == 0:
+ log.warning(f"No DJANGO_ALLOWED_HOSTS are set, the admin page may not be accessible.")
+log.debug(f"{ALLOWED_HOSTS = }")
+
+# Set the origins from which the API can be called, separated by pipes
+CORS_ALLOWED_ORIGINS = os.environ.get("DJANGO_ALLOWED_ORIGINS", "").split("|") if "DJANGO_ALLOWED_ORIGINS" in os.environ else []
+if len(CORS_ALLOWED_ORIGINS) == 0:
+ log.warning(f"No DJANGO_ALLOWED_ORIGINS are set, the API may not be usable.")
+log.debug(f"{CORS_ALLOWED_ORIGINS = }")
+
+# Set the database engine to use with Sophon
+try:
+ DATABASE_ENGINE = os.environ["DJANGO_DATABASE_ENGINE"]
+except KeyError:
+ log.warning("DJANGO_DATABASE_ENGINE was not set, defaulting to PostgreSQL")
+ DATABASE_ENGINE = "django.db.backends.postgresql"
+log.debug(f"{DATABASE_ENGINE = }")
+
+# Set the dbms to use with Sophon
+try:
+ DATABASE_HOST = os.environ["DJANGO_DATABASE_HOST"]
+except KeyError:
+ log.warning("DJANGO_DATABASE_HOST was not set, defaulting to the local PostgreSQL UNIX socket")
+ DATABASE_HOST = ""
+log.debug(f"{DATABASE_HOST = }")
+
+# Set the database name to use with Sophon
+try:
+ DATABASE_NAME = os.environ["DJANGO_DATABASE_NAME"]
+except KeyError:
+ log.warning("DJANGO_DATABASE_NAME was not set, defaulting to `sophon`")
+ DATABASE_NAME = "sophon"
+log.debug(f"{DATABASE_NAME = }")
+
+# Set the database user to login to the database as
+try:
+ DATABASE_USER = os.environ["DJANGO_DATABASE_USER"]
+except KeyError:
+ log.warning("DJANGO_DATABASE_USER was not set, defaulting to `sophon`")
+ DATABASE_USER = "sophon"
+log.debug(f"{DATABASE_USER = }")
+
+# Set the database password to login to the database with
+try:
+ DATABASE_PASSWORD = os.environ["DJANGO_DATABASE_PASSWORD"]
+except KeyError:
+ log.warning("DJANGO_DATABASE_PASSWORD was not set, defaulting to none")
+ DATABASE_PASSWORD = ""
+log.debug(f"DATABASE_PASSWORD = {'*' * len(DATABASE_PASSWORD)}")
+
+# Set the authentication backend to use to authenticate users
+try:
+ AUTHENTICATION_BACKEND = os.environ["DJANGO_AUTHENTICATION_BACKEND"]
+except KeyError:
+ log.warning("DJANGO_AUTHENTICATION_BACKEND was not set, defaulting to the standard Django model authentication")
+ AUTHENTICATION_BACKEND = "django.contrib.auth.backends.ModelBackend"
+log.debug(f"{AUTHENTICATION_BACKEND = }")
+
+# Set the language to use in the admin page
+try:
+ LANGUAGE_CODE = os.environ["DJANGO_LANGUAGE_CODE"]
+except KeyError:
+ log.warning("DJANGO_LANGUAGE_CODE was not set, defaulting to American English")
+ LANGUAGE_CODE = "en-us"
+log.debug(f"{LANGUAGE_CODE = }")
+
+# Set the time zone to use
+try:
+ TIME_ZONE = os.environ["DJANGO_TIME_ZONE"]
+except KeyError:
+ log.warning("DJANGO_TIME_ZONE was not set, defaulting to UTC")
+ TIME_ZONE = "UTC"
+log.debug(f"{TIME_ZONE = }")
+
+# Set the directory to create static files at
+try:
+ STATIC_ROOT = os.environ["DJANGO_STATIC_ROOT"]
+except KeyError:
+ if platform.system() == "Windows":
+ log.warning("DJANGO_STATIC_ROOT was not set, defaulting to `.\\static`")
+ # I have no idea of why IDEA is trying to resolve this
+ # noinspection PyUnresolvedReferences
+ STATIC_ROOT = ".\\static"
+ else:
+ log.warning("DJANGO_STATIC_ROOT was not set, defaulting to `/run/sophon/static`")
+ # I have no idea of why IDEA is trying to resolve this
+ # noinspection PyUnresolvedReferences
+ STATIC_ROOT = "/run/sophon/static"
+log.debug(f"{STATIC_ROOT = }")
+
+# Set the URL where static files are served at
+try:
+ STATIC_URL = os.environ["DJANGO_STATIC_URL"]
+except KeyError:
+ log.warning("DJANGO_STATIC_URL was not set, defaulting to `/static/`")
+ STATIC_URL = "/static/"
+log.debug(f"{STATIC_URL = }")
+
+# Set the directory where the apache proxy file will be created
+try:
+ PROXY_FILE = os.environ["DJANGO_PROXY_FILE"]
+except KeyError:
+ log.warning("DJANGO_PROXY_FILE was not set, defaulting to `./proxy.dbm`")
+ PROXY_FILE = "./proxy.dbm"
+log.debug(f"{PROXY_FILE = }")
+
+# Set the protocol that the proxy will use
+try:
+ PROXY_PROTOCOL = os.environ["DJANGO_PROXY_PROTOCOL"]
+except KeyError:
+ log.warning("DJANGO_PROXY_PROTOCOL was not set, defaulting to HTTPS")
+ PROXY_PROTOCOL = "https"
+if PROXY_PROTOCOL not in ["http", "https"]:
+ log.warning("Unrecognized DJANGO_PROXY_PROTOCOL, acceptable values are `http` and `https`.")
+log.debug(f"{PROXY_PROTOCOL = }")
+
+# Set the base domain that the proxy will use
+try:
+ PROXY_BASE_DOMAIN = os.environ["DJANGO_PROXY_BASE_DOMAIN"]
+except KeyError:
+ log.warning("DJANGO_PROXY_BASE_DOMAIN was not set, defaulting to `dev.sophon.steffo.eu`")
+ PROXY_BASE_DOMAIN = "dev.sophon.steffo.eu"
+log.debug(f"{PROXY_BASE_DOMAIN = }")
+
+# Set the name of the proxy docker container
+PROXY_CONTAINER_NAME = os.environ.get("DJANGO_PROXY_CONTAINER_NAME")
+log.debug(f"{PROXY_CONTAINER_NAME =}")
+
+# Set the prefix to add to all instantiated notebook container names
+try:
+ DOCKER_CONTAINER_PREFIX = os.environ["DJANGO_DOCKER_CONTAINER_PREFIX"]
+except KeyError:
+ log.warning("DOCKER_CONTAINER_PREFIX was not set, defaulting to `sophon-container`")
+ DOCKER_CONTAINER_PREFIX = "sophon-container"
+log.debug(f"{DOCKER_CONTAINER_PREFIX = }")
+
+# Set the prefix to add to all instantiated notebook volume names
+try:
+ DOCKER_VOLUME_PREFIX = os.environ["DJANGO_DOCKER_VOLUME_PREFIX"]
+except KeyError:
+ log.warning("DOCKER_VOLUME_PREFIX was not set, defaulting to `sophon-volume`")
+ DOCKER_VOLUME_PREFIX = "sophon-volume"
+log.debug(f"{DOCKER_VOLUME_PREFIX = }")
+
+# Set the prefix to add to all instantiated notebook network names
+try:
+ DOCKER_NETWORK_PREFIX = os.environ["DJANGO_DOCKER_NETWORK_PREFIX"]
+except KeyError:
+ log.warning("DOCKER_VOLUME_PREFIX was not set, defaulting to `sophon-network`")
+ DOCKER_NETWORK_PREFIX = "sophon-network"
+log.debug(f"{DOCKER_NETWORK_PREFIX = }")
+
+try:
+ DOCKER_HOST = os.environ["DJANGO_DOCKER_HOST"]
+except KeyError:
+ log.warning("DJANGO_DOCKER_HOST was not set, defaulting to none (may cause problems)")
+ DOCKER_HOST = None
+log.debug(f"{DOCKER_HOST = }")
+
+try:
+ DOCKER_TLS_VERIFY = os.environ["DJANGO_DOCKER_TLS_VERIFY"]
+except KeyError:
+ log.warning("DJANGO_DOCKER_TLS_VERIFY was not set, defaulting to none (may cause problems)")
+ DOCKER_TLS_VERIFY = None
+log.debug(f"{DOCKER_TLS_VERIFY = }")
+
+try:
+ DOCKER_CERT_PATH = os.environ["DJANGO_DOCKER_CERT_PATH"]
+except KeyError:
+ log.warning("DJANGO_DOCKER_CERT_PATH was not set, defaulting to none (may cause problems)")
+ DOCKER_CERT_PATH = None
+log.debug(f"{DOCKER_CERT_PATH = }")
+
+#
+
+#
+
+# Set the base project directory
+BASE_DIR = pathlib.Path(__file__).resolve().parent.parent
+
+# Define the installed django apps
+INSTALLED_APPS = [
+ 'django.contrib.admin',
+ 'django.contrib.auth',
+ 'django.contrib.contenttypes',
+ 'django.contrib.sessions',
+ 'django.contrib.messages',
+ 'django.contrib.staticfiles',
+ 'django.contrib.postgres',
+ 'corsheaders',
+ 'rest_framework',
+ 'rest_framework.authtoken',
+ 'sophon.core',
+ 'sophon.projects', # Can be removed safely!
+ 'sophon.notebooks', # Can be removed safely!
+]
+
+# Define the installed middleware
+MIDDLEWARE = [
+ 'corsheaders.middleware.CorsMiddleware',
+ 'django.middleware.security.SecurityMiddleware',
+ 'django.contrib.sessions.middleware.SessionMiddleware',
+ 'django.middleware.common.CommonMiddleware',
+ 'django.middleware.csrf.CsrfViewMiddleware',
+ 'django.contrib.auth.middleware.AuthenticationMiddleware',
+ 'django.contrib.messages.middleware.MessageMiddleware',
+ 'django.middleware.clickjacking.XFrameOptionsMiddleware',
+]
+
+# Define the root urlconf of the project
+ROOT_URLCONF = 'sophon.urls'
+
+# Define the installed template engines
+TEMPLATES = [
+ {
+ 'BACKEND': 'django.template.backends.django.DjangoTemplates',
+ 'DIRS': [],
+ 'APP_DIRS': True,
+ 'OPTIONS': {
+ 'context_processors': [
+ 'django.template.context_processors.debug',
+ 'django.template.context_processors.request',
+ 'django.contrib.auth.context_processors.auth',
+ 'django.contrib.messages.context_processors.messages',
+ ],
+ },
+ },
+]
+
+# Define the path to the project wsgi application
+WSGI_APPLICATION = 'sophon.wsgi.application'
+
+# Configure the database
+#
+# Remember to run the following commands on a new installation:
+# sudo -iu postgres
+# createuser sophon
+# createdb --owner=sophon sophon
+#
+# If you need to run tests, also ensure `sophon` is a superuser, or it won't be able to create and drop the testing database!
+DATABASES = {
+ 'default': {
+ 'ENGINE': DATABASE_ENGINE,
+ 'HOST': DATABASE_HOST, # Connect via UNIX socket (does not work on Windows)
+ 'NAME': DATABASE_NAME,
+ 'USER': DATABASE_USER, # Connect using its own user, isolating sophon from the rest of the server
+ 'PASSWORD': DATABASE_PASSWORD,
+ }
+}
+
+# Define the Django model password validators
+AUTH_PASSWORD_VALIDATORS = [
+ {
+ 'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
+ },
+ {
+ 'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
+ },
+]
+
+# Define the Django authentication backends
+AUTHENTICATION_BACKENDS = [
+ AUTHENTICATION_BACKEND,
+]
+
+# Enable translation
+USE_I18N = True
+
+# Enable formatting
+USE_L10N = True
+
+# Enable timezones
+USE_TZ = True
+
+# Configure Django Rest Framework for the Sophon API
+REST_FRAMEWORK = {
+ 'DEFAULT_PERMISSION_CLASSES': [
+ # Allow requests from all kinds of users
+ 'rest_framework.permissions.AllowAny'
+ ],
+ 'DEFAULT_AUTHENTICATION_CLASSES': [
+ # Allow basic authentication
+ 'rest_framework.authentication.BasicAuthentication',
+ # Allow session authentication
+ 'rest_framework.authentication.SessionAuthentication',
+ # Allow bearer authentication
+ 'sophon.auth1.BearerTokenAuthentication',
+ ],
+ 'DEFAULT_PAGINATION_CLASS': 'rest_framework.pagination.PageNumberPagination',
+ # Paginate in groups of 100 resources
+ 'PAGE_SIZE': 100,
+}
+
+# Specify the URLs where the CORS middleware should be applied to
CORS_URLS_REGEX = r"^/api/"
+
+#
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..ea4a3c3
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,98 @@
+version: "3.9"
+
+
+volumes:
+ proxy-data:
+ django-static:
+ react-static:
+ db-data:
+
+
+networks:
+ bone:
+
+
+services:
+ db:
+ image: postgres
+ volumes:
+ - db-data:/var/lib/postgresql/data
+ environment:
+ # Don't change these.
+ - POSTGRES_USER=sophon
+ - POSTGRES_PASSWORD=sophonity
+ - POSTGRES_DB=sophon
+ networks:
+ - bone
+
+ frontend:
+ image: steffo45/sophon-frontend
+ environment:
+ - REACT_APP_DEFAULT_INSTANCE=http://api.dev.sophon.steffo.eu
+ networks:
+ - bone
+
+ backend:
+ image: steffo45/sophon-backend
+ environment:
+ # TODO: Set a random secret key!
+ - DJANGO_SECRET_KEY=change-me!!!
+ # TODO: Configure your allowed hosts!
+ - DJANGO_ALLOWED_HOSTS=api.dev.sophon.steffo.eu
+ # TODO: Configure your allowed origins! (* doesn't work)
+ - DJANGO_ALLOWED_ORIGINS=http://dev.sophon.steffo.eu
+ # TODO: Configure your proxy details!
+ - DJANGO_PROXY_BASE_DOMAIN=dev.sophon.steffo.eu
+ - DJANGO_PROXY_PROTOCOL=http
+ - DJANGO_PROXY_CONTAINER_NAME=sophon2-proxy-1 # The correct name probably is $DIRECTORYNAME-proxy-1
+ # TODO: Configure your static url!
+ - DJANGO_STATIC_URL=http://static.dev.sophon.steffo.eu/django-static/
+ # TODO: Set your language!
+ - DJANGO_LANGUAGE_CODE=en-us
+ # TODO: Set your timezone!
+ - DJANGO_TIME_ZONE=CET
+ # TODO: Set the superuser login details!
+ - DJANGO_SU_USERNAME=root
+ - DJANGO_SU_EMAIL=root@example.org
+ - DJANGO_SU_PASSWORD=square
+ # Don't change these.
+ - DJANGO_DATABASE_ENGINE=django.db.backends.postgresql
+ - DJANGO_DATABASE_HOST=db
+ - DJANGO_DATABASE_USER=sophon
+ - DJANGO_DATABASE_PASSWORD=sophonity
+ - DJANGO_DATABASE_NAME=sophon
+ - DJANGO_AUTHENTICATION_BACKEND=django.contrib.auth.backends.ModelBackend
+ - DJANGO_DOCKER_CONTAINER_PREFIX=sophon-container
+ - DJANGO_DOCKER_VOLUME_PREFIX=sophon-volume
+ - DJANGO_DOCKER_NETWORK_PREFIX=sophon-network
+ - DJANGO_DOCKER_HOST=/var/run/docker.sock
+ - DJANGO_DOCKER_TLS_VERIFY=
+ - DJANGO_DOCKER_CERT_PATH=
+ volumes:
+ - proxy-data:/run/sophon/proxy
+ - django-static:/run/sophon/static
+ - /var/run/docker.sock:/var/run/docker.sock
+ depends_on:
+ - db
+ networks:
+ - bone
+
+ proxy:
+ image: steffo45/sophon-proxy
+ ports:
+ - "80:80"
+ environment:
+ # TODO: Configure your proxy details!
+ - APACHE_PROXY_BASE_DOMAIN=dev.sophon.steffo.eu
+ #`Don't change these.
+ - SOPHON_BACKEND_NAME=backend:8000
+ - SOPHON_FRONTEND_NAME=frontend:5000
+ volumes:
+ - proxy-data:/run/sophon/proxy
+ - django-static:/usr/local/apache2/htdocs/django-static
+ - react-static:/usr/local/apache2/htdocs/react-static
+ depends_on:
+ - backend
+ - frontend
+ networks:
+ - bone
diff --git a/frontend/.dockerignore b/frontend/.dockerignore
new file mode 120000
index 0000000..3e4e48b
--- /dev/null
+++ b/frontend/.dockerignore
@@ -0,0 +1 @@
+.gitignore
\ No newline at end of file
diff --git a/frontend/Dockerfile b/frontend/Dockerfile
new file mode 100644
index 0000000..c0e2373
--- /dev/null
+++ b/frontend/Dockerfile
@@ -0,0 +1,19 @@
+FROM node:16.11.1-bullseye
+LABEL maintainer="Stefano Pigozzi "
+
+# Set the base workdir
+WORKDIR /usr/src/app
+
+# Copy the yarn files
+COPY package.json ./package.json
+COPY yarn.lock ./yarn.lock
+
+# Install the package
+RUN yarn install
+
+# Copy the rest of the project into the container
+COPY . .
+
+# Start the serve server
+ENTRYPOINT ["yarn", "run"]
+CMD ["serve"]
\ No newline at end of file
diff --git a/frontend/Frontend (React).iml b/frontend/Frontend (React).iml
index 7626055..e1c660c 100644
--- a/frontend/Frontend (React).iml
+++ b/frontend/Frontend (React).iml
@@ -5,6 +5,7 @@
+
diff --git a/frontend/package.json b/frontend/package.json
index 0ef0eb3..1afc3af 100644
--- a/frontend/package.json
+++ b/frontend/package.json
@@ -22,6 +22,7 @@
"react-markdown": "^7.0.1",
"react-scripts": "4.0.3",
"react-storage-hooks": "^4.0.1",
+ "serve": "^12.0.1",
"typescript": "^4.1.2",
"web-vitals": "^1.0.1"
},
@@ -29,7 +30,8 @@
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
- "eject": "react-scripts eject"
+ "eject": "react-scripts eject",
+ "serve": "react-scripts build && serve -s build"
},
"eslintConfig": {
"extends": [
diff --git a/frontend/src/components/elements/DescriptionBox.tsx b/frontend/src/components/elements/DescriptionBox.tsx
index 0b49fd6..8bae9ee 100644
--- a/frontend/src/components/elements/DescriptionBox.tsx
+++ b/frontend/src/components/elements/DescriptionBox.tsx
@@ -3,12 +3,13 @@ import {FontAwesomeIcon} from "@fortawesome/react-fontawesome"
import {Box, Heading, Idiomatic} from "@steffo/bluelib-react"
import * as React from "react"
import ReactMarkdown from "react-markdown"
+import {Empty} from "./Empty"
export interface DescriptionBoxProps {
icon: IconDefinition,
name: string,
- description: string,
+ description?: string | null,
}
@@ -19,9 +20,16 @@ export function DescriptionBox({icon, name, description}: DescriptionBoxProps):
About {name}
-
- {description}
-
+ {
+ description ?
+
+ {description}
+
+ :
+
+ This resource has no about text.
+
+ }
),
[icon, name, description],
diff --git a/frontend/src/components/group/GroupCreateBox.tsx b/frontend/src/components/group/GroupCreateBox.tsx
index ea501cc..40a9cf5 100644
--- a/frontend/src/components/group/GroupCreateBox.tsx
+++ b/frontend/src/components/group/GroupCreateBox.tsx
@@ -93,7 +93,7 @@ export function GroupCreateBox({viewSet, resource}: GroupCreateBoxProps): JSX.El
return obj
}).reduce((a, b) => {
return {...a, ...b}
- }),
+ }, {}),
[authorization, cache],
)
diff --git a/frontend/src/components/instance/InstanceDescriptionBox.tsx b/frontend/src/components/instance/InstanceDescriptionBox.tsx
index 51c535e..1eae5fa 100644
--- a/frontend/src/components/instance/InstanceDescriptionBox.tsx
+++ b/frontend/src/components/instance/InstanceDescriptionBox.tsx
@@ -16,10 +16,6 @@ export function InstanceDescriptionBox(): JSX.Element | null {
return null
}
- if(!instance.state.details.description) {
- return null
- }
-
return (
{
return {...a, ...b}
- })
+ }, {})
},
[users],
)
diff --git a/frontend/src/types/SophonTypes.ts b/frontend/src/types/SophonTypes.ts
index 94d82ad..80b3c2d 100644
--- a/frontend/src/types/SophonTypes.ts
+++ b/frontend/src/types/SophonTypes.ts
@@ -1,4 +1,4 @@
-import {DjangoResource, DjangoSlug} from "./DjangoTypes";
+import {DjangoResource, DjangoSlug} from "./DjangoTypes"
/**
@@ -64,9 +64,8 @@ export interface SophonNotebook extends DjangoResource {
locked_by: number,
slug: DjangoSlug,
legacy_notebook_url: string | null,
- jupyter_token: string,
+ jupyter_token?: string,
is_running: boolean,
- internet_access: true,
container_image: string,
project: DjangoSlug,
name: string,
diff --git a/frontend/yarn.lock b/frontend/yarn.lock
index 8ad5615..8e40e42 100644
--- a/frontend/yarn.lock
+++ b/frontend/yarn.lock
@@ -2350,6 +2350,11 @@
resolved "https://registry.yarnpkg.com/@xtuc/long/-/long-4.2.2.tgz#d291c6a4e97989b5c61d9acf396ae4fe133a718d"
integrity sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==
+"@zeit/schemas@2.6.0":
+ version "2.6.0"
+ resolved "https://registry.yarnpkg.com/@zeit/schemas/-/schemas-2.6.0.tgz#004e8e553b4cd53d538bd38eac7bcbf58a867fe3"
+ integrity sha512-uUrgZ8AxS+Lio0fZKAipJjAh415JyrOZowliZAzmnJSsf7piVL5w+G0+gFJ0KSu3QRhvui/7zuvpLz03YjXAhg==
+
abab@^2.0.3, abab@^2.0.5:
version "2.0.5"
resolved "https://registry.yarnpkg.com/abab/-/abab-2.0.5.tgz#c0b678fb32d60fc1219c784d6a826fe385aeb79a"
@@ -2434,7 +2439,7 @@ ajv-keywords@^3.1.0, ajv-keywords@^3.4.1, ajv-keywords@^3.5.2:
resolved "https://registry.yarnpkg.com/ajv-keywords/-/ajv-keywords-3.5.2.tgz#31f29da5ab6e00d1c2d329acf7b5929614d5014d"
integrity sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==
-ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.4, ajv@^6.12.5:
+ajv@6.12.6, ajv@^6.1.0, ajv@^6.10.0, ajv@^6.10.2, ajv@^6.12.4, ajv@^6.12.5:
version "6.12.6"
resolved "https://registry.yarnpkg.com/ajv/-/ajv-6.12.6.tgz#baf5a62e802b07d977034586f8c3baf5adf26df4"
integrity sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==
@@ -2459,6 +2464,13 @@ alphanum-sort@^1.0.0:
resolved "https://registry.yarnpkg.com/alphanum-sort/-/alphanum-sort-1.0.2.tgz#97a1119649b211ad33691d9f9f486a8ec9fbe0a3"
integrity sha1-l6ERlkmyEa0zaR2fn0hqjsn74KM=
+ansi-align@^2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/ansi-align/-/ansi-align-2.0.0.tgz#c36aeccba563b89ceb556f3690f0b1d9e3547f7f"
+ integrity sha1-w2rsy6VjuJzrVW82kPCx2eNUf38=
+ dependencies:
+ string-width "^2.0.0"
+
ansi-colors@^3.0.0:
version "3.2.4"
resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-3.2.4.tgz#e3a3da4bfbae6c86a9c285625de124a234026fbf"
@@ -2486,6 +2498,11 @@ ansi-regex@^2.0.0:
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-2.1.1.tgz#c3b33ab5ee360d86e0e628f0468ae7ef27d654df"
integrity sha1-w7M6te42DYbg5ijwRorn7yfWVN8=
+ansi-regex@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-3.0.0.tgz#ed0317c322064f79466c02966bddb605ab37d998"
+ integrity sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=
+
ansi-regex@^4.1.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-4.1.0.tgz#8b9f8f08cf1acb843756a839ca8c7e3168c51997"
@@ -2536,6 +2553,16 @@ aproba@^1.1.1:
resolved "https://registry.yarnpkg.com/aproba/-/aproba-1.2.0.tgz#6802e6264efd18c790a1b0d517f0f2627bf2c94a"
integrity sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==
+arch@^2.1.1:
+ version "2.2.0"
+ resolved "https://registry.yarnpkg.com/arch/-/arch-2.2.0.tgz#1bc47818f305764f23ab3306b0bfc086c5a29d11"
+ integrity sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==
+
+arg@2.0.0:
+ version "2.0.0"
+ resolved "https://registry.yarnpkg.com/arg/-/arg-2.0.0.tgz#c06e7ff69ab05b3a4a03ebe0407fac4cba657545"
+ integrity sha512-XxNTUzKnz1ctK3ZIcI2XUPlD96wbHP2nGqkPKpvk/HNRlPveYrXIVSTk9m3LcqOgDPg3B1nMvdV/K8wZd7PG4w==
+
argparse@^1.0.7:
version "1.0.10"
resolved "https://registry.yarnpkg.com/argparse/-/argparse-1.0.10.tgz#bcd6791ea5ae09725e17e5ad988134cd40b3d911"
@@ -3037,6 +3064,19 @@ boolbase@^1.0.0, boolbase@~1.0.0:
resolved "https://registry.yarnpkg.com/boolbase/-/boolbase-1.0.0.tgz#68dff5fbe60c51eb37725ea9e3ed310dcc1e776e"
integrity sha1-aN/1++YMUes3cl6p4+0xDcwed24=
+boxen@1.3.0:
+ version "1.3.0"
+ resolved "https://registry.yarnpkg.com/boxen/-/boxen-1.3.0.tgz#55c6c39a8ba58d9c61ad22cd877532deb665a20b"
+ integrity sha512-TNPjfTr432qx7yOjQyaXm3dSR0MH9vXp7eT1BFSl/C51g+EFnOR9hTg1IreahGBmDNCehscshe45f+C1TBZbLw==
+ dependencies:
+ ansi-align "^2.0.0"
+ camelcase "^4.0.0"
+ chalk "^2.0.1"
+ cli-boxes "^1.0.0"
+ string-width "^2.0.0"
+ term-size "^1.2.0"
+ widest-line "^2.0.0"
+
brace-expansion@^1.1.7:
version "1.1.11"
resolved "https://registry.yarnpkg.com/brace-expansion/-/brace-expansion-1.1.11.tgz#3c7fcbf529d87226f3d2f52b966ff5271eb441dd"
@@ -3316,6 +3356,11 @@ camelcase@5.3.1, camelcase@^5.0.0, camelcase@^5.3.1:
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-5.3.1.tgz#e3c9b31569e106811df242f715725a1f4c494320"
integrity sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==
+camelcase@^4.0.0:
+ version "4.1.0"
+ resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-4.1.0.tgz#d545635be1e33c542649c69173e5de6acfae34dd"
+ integrity sha1-1UVjW+HjPFQmScaRc+Xeas+uNN0=
+
camelcase@^6.0.0, camelcase@^6.1.0, camelcase@^6.2.0:
version "6.2.0"
resolved "https://registry.yarnpkg.com/camelcase/-/camelcase-6.2.0.tgz#924af881c9d525ac9d87f40d964e5cea982a1809"
@@ -3348,7 +3393,16 @@ case-sensitive-paths-webpack-plugin@2.3.0:
resolved "https://registry.yarnpkg.com/case-sensitive-paths-webpack-plugin/-/case-sensitive-paths-webpack-plugin-2.3.0.tgz#23ac613cc9a856e4f88ff8bb73bbb5e989825cf7"
integrity sha512-/4YgnZS8y1UXXmC02xD5rRrBEu6T5ub+mQHLNRj0fzTRbgdBYhsNo2V5EqwgqrExjxsjtF/OpAKAMkKsxbD5XQ==
-chalk@2.4.2, chalk@^2.0.0, chalk@^2.4.1, chalk@^2.4.2:
+chalk@2.4.1:
+ version "2.4.1"
+ resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.1.tgz#18c49ab16a037b6eb0152cc83e3471338215b66e"
+ integrity sha512-ObN6h1v2fTJSmUXoS3nMQ92LbDK9be4TV+6G+omQlGJFdcUX5heKi1LZ1YnRMIgwTLEj3E24bT6tYni50rlCfQ==
+ dependencies:
+ ansi-styles "^3.2.1"
+ escape-string-regexp "^1.0.5"
+ supports-color "^5.3.0"
+
+chalk@2.4.2, chalk@^2.0.0, chalk@^2.0.1, chalk@^2.4.1, chalk@^2.4.2:
version "2.4.2"
resolved "https://registry.yarnpkg.com/chalk/-/chalk-2.4.2.tgz#cd42541677a54333cf541a49108c1432b44c9424"
integrity sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==
@@ -3492,6 +3546,20 @@ clean-stack@^2.0.0:
resolved "https://registry.yarnpkg.com/clean-stack/-/clean-stack-2.2.0.tgz#ee8472dbb129e727b31e8a10a427dee9dfe4008b"
integrity sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==
+cli-boxes@^1.0.0:
+ version "1.0.0"
+ resolved "https://registry.yarnpkg.com/cli-boxes/-/cli-boxes-1.0.0.tgz#4fa917c3e59c94a004cd61f8ee509da651687143"
+ integrity sha1-T6kXw+WclKAEzWH47lCdplFocUM=
+
+clipboardy@2.3.0:
+ version "2.3.0"
+ resolved "https://registry.yarnpkg.com/clipboardy/-/clipboardy-2.3.0.tgz#3c2903650c68e46a91b388985bc2774287dba290"
+ integrity sha512-mKhiIL2DrQIsuXMgBgnfEHOZOryC7kY7YO//TN6c63wlEm3NG5tz+YgY5rVi29KCmq/QQjKYvM7a19+MDOTHOQ==
+ dependencies:
+ arch "^2.1.1"
+ execa "^1.0.0"
+ is-wsl "^2.1.1"
+
cliui@^5.0.0:
version "5.0.0"
resolved "https://registry.yarnpkg.com/cliui/-/cliui-5.0.0.tgz#deefcfdb2e800784aa34f46fa08e06851c7bbbc5"
@@ -3628,13 +3696,26 @@ compose-function@3.0.3:
dependencies:
arity-n "^1.0.4"
-compressible@~2.0.16:
+compressible@~2.0.14, compressible@~2.0.16:
version "2.0.18"
resolved "https://registry.yarnpkg.com/compressible/-/compressible-2.0.18.tgz#af53cca6b070d4c3c0750fbd77286a6d7cc46fba"
integrity sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==
dependencies:
mime-db ">= 1.43.0 < 2"
+compression@1.7.3:
+ version "1.7.3"
+ resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.3.tgz#27e0e176aaf260f7f2c2813c3e440adb9f1993db"
+ integrity sha512-HSjyBG5N1Nnz7tF2+O7A9XUhyjru71/fwgNb7oIsEVHR0WShfs2tIS/EySLgiTe98aOK18YDlMXpzjCXY/n9mg==
+ dependencies:
+ accepts "~1.3.5"
+ bytes "3.0.0"
+ compressible "~2.0.14"
+ debug "2.6.9"
+ on-headers "~1.0.1"
+ safe-buffer "5.1.2"
+ vary "~1.1.2"
+
compression@^1.7.4:
version "1.7.4"
resolved "https://registry.yarnpkg.com/compression/-/compression-1.7.4.tgz#95523eff170ca57c29a0ca41e6fe131f41e5bb8f"
@@ -3683,6 +3764,11 @@ constants-browserify@^1.0.0:
resolved "https://registry.yarnpkg.com/constants-browserify/-/constants-browserify-1.0.0.tgz#c20b96d8c617748aaf1c16021760cd27fcb8cb75"
integrity sha1-wguW2MYXdIqvHBYCF2DNJ/y4y3U=
+content-disposition@0.5.2:
+ version "0.5.2"
+ resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.2.tgz#0cf68bb9ddf5f2be7961c3a85178cb85dba78cb4"
+ integrity sha1-DPaLud318r55YcOoUXjLhdunjLQ=
+
content-disposition@0.5.3:
version "0.5.3"
resolved "https://registry.yarnpkg.com/content-disposition/-/content-disposition-0.5.3.tgz#e130caf7e7279087c5616c2007d0485698984fbd"
@@ -3849,6 +3935,15 @@ cross-spawn@7.0.3, cross-spawn@^7.0.0, cross-spawn@^7.0.2:
shebang-command "^2.0.0"
which "^2.0.1"
+cross-spawn@^5.0.1:
+ version "5.1.0"
+ resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
+ integrity sha1-6L0O/uWPz/b4+UUQoKVUu/ojVEk=
+ dependencies:
+ lru-cache "^4.0.1"
+ shebang-command "^1.2.0"
+ which "^1.2.9"
+
cross-spawn@^6.0.0:
version "6.0.5"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-6.0.5.tgz#4a5ec7c64dfae22c3a14124dbacdee846d80cbc4"
@@ -4203,6 +4298,11 @@ deep-equal@^1.0.1:
object-keys "^1.1.1"
regexp.prototype.flags "^1.2.0"
+deep-extend@^0.6.0:
+ version "0.6.0"
+ resolved "https://registry.yarnpkg.com/deep-extend/-/deep-extend-0.6.0.tgz#c4fa7c95404a17a9c3e8ca7e1537312b736330ac"
+ integrity sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==
+
deep-is@^0.1.3, deep-is@~0.1.3:
version "0.1.4"
resolved "https://registry.yarnpkg.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
@@ -4993,6 +5093,19 @@ exec-sh@^0.3.2:
resolved "https://registry.yarnpkg.com/exec-sh/-/exec-sh-0.3.6.tgz#ff264f9e325519a60cb5e273692943483cca63bc"
integrity sha512-nQn+hI3yp+oD0huYhKwvYI32+JFeq+XkNcD1GAo3Y/MjxsfVGmrrzrnzjWiNY6f+pUCP440fThsFh5gZrRAU/w==
+execa@^0.7.0:
+ version "0.7.0"
+ resolved "https://registry.yarnpkg.com/execa/-/execa-0.7.0.tgz#944becd34cc41ee32a63a9faf27ad5a65fc59777"
+ integrity sha1-lEvs00zEHuMqY6n68nrVpl/Fl3c=
+ dependencies:
+ cross-spawn "^5.0.1"
+ get-stream "^3.0.0"
+ is-stream "^1.1.0"
+ npm-run-path "^2.0.0"
+ p-finally "^1.0.0"
+ signal-exit "^3.0.0"
+ strip-eof "^1.0.0"
+
execa@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/execa/-/execa-1.0.0.tgz#c6236a5bb4df6d6f15e88e7f017798216749ddd8"
@@ -5154,6 +5267,13 @@ fast-levenshtein@^2.0.6, fast-levenshtein@~2.0.6:
resolved "https://registry.yarnpkg.com/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz#3d8a5c66883a16a30ca8643e851f19baa7797917"
integrity sha1-PYpcZog6FqMMqGQ+hR8Zuqd5eRc=
+fast-url-parser@1.1.3:
+ version "1.1.3"
+ resolved "https://registry.yarnpkg.com/fast-url-parser/-/fast-url-parser-1.1.3.tgz#f4af3ea9f34d8a271cf58ad2b3759f431f0b318d"
+ integrity sha1-9K8+qfNNiicc9YrSs3WfQx8LMY0=
+ dependencies:
+ punycode "^1.3.2"
+
fastq@^1.6.0:
version "1.13.0"
resolved "https://registry.yarnpkg.com/fastq/-/fastq-1.13.0.tgz#616760f88a7526bdfc596b7cab8c18938c36b98c"
@@ -5460,6 +5580,11 @@ get-package-type@^0.1.0:
resolved "https://registry.yarnpkg.com/get-package-type/-/get-package-type-0.1.0.tgz#8de2d803cff44df3bc6c456e6668b36c3926e11a"
integrity sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==
+get-stream@^3.0.0:
+ version "3.0.0"
+ resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-3.0.0.tgz#8e943d1358dc37555054ecbe2edb05aa174ede14"
+ integrity sha1-jpQ9E1jcN1VQVOy+LtsFqhdO3hQ=
+
get-stream@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/get-stream/-/get-stream-4.1.0.tgz#c1b255575f3dc21d59bfc79cd3d2b46b1c3a54b5"
@@ -6019,7 +6144,7 @@ inherits@2.0.3:
resolved "https://registry.yarnpkg.com/inherits/-/inherits-2.0.3.tgz#633c2c83e3da42a502f52466022480f4208261de"
integrity sha1-Yzwsg+PaQqUC9SRmAiSA9CCCYd4=
-ini@^1.3.5:
+ini@^1.3.5, ini@~1.3.0:
version "1.3.8"
resolved "https://registry.yarnpkg.com/ini/-/ini-1.3.8.tgz#a29da425b48806f34767a4efce397269af28432c"
integrity sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==
@@ -7325,6 +7450,14 @@ lower-case@^2.0.2:
dependencies:
tslib "^2.0.3"
+lru-cache@^4.0.1:
+ version "4.1.5"
+ resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-4.1.5.tgz#8bbe50ea85bed59bc9e33dcab8235ee9bcf443cd"
+ integrity sha512-sWZlbEP2OsHNkXrMl5GYk/jKk70MBng6UU4YI/qGDYbgf6YbP4EvmqISbXCoJiRKs+1bSpFHVgQxvJ17F2li5g==
+ dependencies:
+ pseudomap "^1.0.2"
+ yallist "^2.1.2"
+
lru-cache@^5.1.1:
version "5.1.1"
resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-5.1.1.tgz#1da27e6710271947695daf6848e847f01d84b920"
@@ -7737,6 +7870,18 @@ mime-db@1.50.0, "mime-db@>= 1.43.0 < 2":
resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.50.0.tgz#abd4ac94e98d3c0e185016c67ab45d5fde40c11f"
integrity sha512-9tMZCDlYHqeERXEHO9f/hKfNXhre5dK2eE/krIvUjZbS2KPcqGDfNShIWS1uW9XOTKQKqK6qbeOci18rbfW77A==
+mime-db@~1.33.0:
+ version "1.33.0"
+ resolved "https://registry.yarnpkg.com/mime-db/-/mime-db-1.33.0.tgz#a3492050a5cb9b63450541e39d9788d2272783db"
+ integrity sha512-BHJ/EKruNIqJf/QahvxwQZXKygOQ256myeN/Ew+THcAa5q+PjyTTMMeNQC4DZw5AwfvelsUrA6B67NKMqXDbzQ==
+
+mime-types@2.1.18:
+ version "2.1.18"
+ resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.18.tgz#6f323f60a83d11146f831ff11fd66e2fe5503bb8"
+ integrity sha512-lc/aahn+t4/SWV/qcmumYjymLsWfN3ELhpmVuUFjgsORruuZPVSwAQryq+HHGvO/SI2KVX26bx+En+zhM8g8hQ==
+ dependencies:
+ mime-db "~1.33.0"
+
mime-types@^2.1.12, mime-types@^2.1.27, mime-types@~2.1.17, mime-types@~2.1.24:
version "2.1.33"
resolved "https://registry.yarnpkg.com/mime-types/-/mime-types-2.1.33.tgz#1fa12a904472fafd068e48d9e8401f74d3f70edb"
@@ -8238,7 +8383,7 @@ on-finished@~2.3.0:
dependencies:
ee-first "1.1.1"
-on-headers@~1.0.2:
+on-headers@~1.0.1, on-headers@~1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/on-headers/-/on-headers-1.0.2.tgz#772b0ae6aaa525c399e489adfad90c403eb3c28f"
integrity sha512-pZAE+FJLoyITytdqK0U5s+FIpjN0JP3OzFi/u8Rx+EV5/W+JTWGXG8xFzevE7AjBfDqHv/8vL8qQsIhHnqRkrA==
@@ -8515,7 +8660,7 @@ path-is-absolute@^1.0.0:
resolved "https://registry.yarnpkg.com/path-is-absolute/-/path-is-absolute-1.0.1.tgz#174b9268735534ffbc7ace6bf53a5a9e1b5c5f5f"
integrity sha1-F0uSaHNVNP+8es5r9TpanhtcX18=
-path-is-inside@^1.0.2:
+path-is-inside@1.0.2, path-is-inside@^1.0.2:
version "1.0.2"
resolved "https://registry.yarnpkg.com/path-is-inside/-/path-is-inside-1.0.2.tgz#365417dede44430d1c11af61027facf074bdfc53"
integrity sha1-NlQX3t5EQw0cEa9hAn+s8HS9/FM=
@@ -8540,6 +8685,11 @@ path-to-regexp@0.1.7:
resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c"
integrity sha1-32BBeABfUi8V60SQ5yR6G/qmf4w=
+path-to-regexp@2.2.1:
+ version "2.2.1"
+ resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-2.2.1.tgz#90b617025a16381a879bc82a38d4e8bdeb2bcf45"
+ integrity sha512-gu9bD6Ta5bwGrrU8muHzVOBFFREpp2iRkVfhBJahwJ6p6Xw20SjT0MxLnwkjOibQmGSYhiUnf2FLe7k+jcFmGQ==
+
path-type@^3.0.0:
version "3.0.0"
resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f"
@@ -9452,6 +9602,11 @@ prr@~1.0.1:
resolved "https://registry.yarnpkg.com/prr/-/prr-1.0.1.tgz#d3fc114ba06995a45ec6893f484ceb1d78f5f476"
integrity sha1-0/wRS6BplaRexok/SEzrHXj19HY=
+pseudomap@^1.0.2:
+ version "1.0.2"
+ resolved "https://registry.yarnpkg.com/pseudomap/-/pseudomap-1.0.2.tgz#f052a28da70e618917ef0a8ac34c1ae5a68286b3"
+ integrity sha1-8FKijacOYYkX7wqKw0wa5aaChrM=
+
psl@^1.1.33:
version "1.8.0"
resolved "https://registry.yarnpkg.com/psl/-/psl-1.8.0.tgz#9326f8bcfb013adcc005fdff056acce020e51c24"
@@ -9499,7 +9654,7 @@ punycode@1.3.2:
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.3.2.tgz#9653a036fb7c1ee42342f2325cceefea3926c48d"
integrity sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=
-punycode@^1.2.4:
+punycode@^1.2.4, punycode@^1.3.2:
version "1.4.1"
resolved "https://registry.yarnpkg.com/punycode/-/punycode-1.4.1.tgz#c0d5a63b2718800ad8e1eb0fa5269c84dd41845e"
integrity sha1-wNWmOycYgArY4esPpSachN1BhF4=
@@ -9574,6 +9729,11 @@ randomfill@^1.0.3:
randombytes "^2.0.5"
safe-buffer "^5.1.0"
+range-parser@1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.0.tgz#f49be6b487894ddc40dcc94a322f611092e00d5e"
+ integrity sha1-9JvmtIeJTdxA3MlKMi9hEJLgDV4=
+
range-parser@^1.2.1, range-parser@~1.2.1:
version "1.2.1"
resolved "https://registry.yarnpkg.com/range-parser/-/range-parser-1.2.1.tgz#3cf37023d199e1c24d1a55b84800c2f3e6468031"
@@ -9589,6 +9749,16 @@ raw-body@2.4.0:
iconv-lite "0.4.24"
unpipe "1.0.0"
+rc@^1.0.1, rc@^1.1.6:
+ version "1.2.8"
+ resolved "https://registry.yarnpkg.com/rc/-/rc-1.2.8.tgz#cd924bf5200a075b83c188cd6b9e211b7fc0d3ed"
+ integrity sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==
+ dependencies:
+ deep-extend "^0.6.0"
+ ini "~1.3.0"
+ minimist "^1.2.0"
+ strip-json-comments "~2.0.1"
+
react-app-polyfill@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/react-app-polyfill/-/react-app-polyfill-2.0.0.tgz#a0bea50f078b8a082970a9d853dc34b6dcc6a3cf"
@@ -9919,6 +10089,21 @@ regexpu-core@^4.7.1:
unicode-match-property-ecmascript "^2.0.0"
unicode-match-property-value-ecmascript "^2.0.0"
+registry-auth-token@3.3.2:
+ version "3.3.2"
+ resolved "https://registry.yarnpkg.com/registry-auth-token/-/registry-auth-token-3.3.2.tgz#851fd49038eecb586911115af845260eec983f20"
+ integrity sha512-JL39c60XlzCVgNrO+qq68FoNb56w/m7JYvGR2jT5iR1xBrUA3Mfx5Twk5rqTThPmQKMWydGmq8oFtDlxfrmxnQ==
+ dependencies:
+ rc "^1.1.6"
+ safe-buffer "^5.0.1"
+
+registry-url@3.1.0:
+ version "3.1.0"
+ resolved "https://registry.yarnpkg.com/registry-url/-/registry-url-3.1.0.tgz#3d4ef870f73dde1d77f0cf9a381432444e174942"
+ integrity sha1-PU74cPc93h138M+aOBQyRE4XSUI=
+ dependencies:
+ rc "^1.0.1"
+
regjsgen@^0.5.2:
version "0.5.2"
resolved "https://registry.yarnpkg.com/regjsgen/-/regjsgen-0.5.2.tgz#92ff295fb1deecbf6ecdab2543d207e91aa33733"
@@ -10368,6 +10553,20 @@ serialize-javascript@^5.0.1:
dependencies:
randombytes "^2.1.0"
+serve-handler@6.1.3:
+ version "6.1.3"
+ resolved "https://registry.yarnpkg.com/serve-handler/-/serve-handler-6.1.3.tgz#1bf8c5ae138712af55c758477533b9117f6435e8"
+ integrity sha512-FosMqFBNrLyeiIDvP1zgO6YoTzFYHxLDEIavhlmQ+knB2Z7l1t+kGLHkZIDN7UVWqQAmKI3D20A6F6jo3nDd4w==
+ dependencies:
+ bytes "3.0.0"
+ content-disposition "0.5.2"
+ fast-url-parser "1.1.3"
+ mime-types "2.1.18"
+ minimatch "3.0.4"
+ path-is-inside "1.0.2"
+ path-to-regexp "2.2.1"
+ range-parser "1.2.0"
+
serve-index@^1.9.1:
version "1.9.1"
resolved "https://registry.yarnpkg.com/serve-index/-/serve-index-1.9.1.tgz#d3768d69b1e7d82e5ce050fff5b453bea12a9239"
@@ -10391,6 +10590,21 @@ serve-static@1.14.1:
parseurl "~1.3.3"
send "0.17.1"
+serve@^12.0.1:
+ version "12.0.1"
+ resolved "https://registry.yarnpkg.com/serve/-/serve-12.0.1.tgz#5b0e05849f5ed9b8aab0f30a298c3664bba052bb"
+ integrity sha512-CQ4ikLpxg/wmNM7yivulpS6fhjRiFG6OjmP8ty3/c1SBnSk23fpKmLAV4HboTA2KrZhkUPlDfjDhnRmAjQ5Phw==
+ dependencies:
+ "@zeit/schemas" "2.6.0"
+ ajv "6.12.6"
+ arg "2.0.0"
+ boxen "1.3.0"
+ chalk "2.4.1"
+ clipboardy "2.3.0"
+ compression "1.7.3"
+ serve-handler "6.1.3"
+ update-check "1.5.2"
+
set-blocking@^2.0.0:
version "2.0.0"
resolved "https://registry.yarnpkg.com/set-blocking/-/set-blocking-2.0.0.tgz#045f9782d011ae9a6803ddd382b24392b3d890f7"
@@ -10783,6 +10997,14 @@ string-natural-compare@^3.0.1:
resolved "https://registry.yarnpkg.com/string-natural-compare/-/string-natural-compare-3.0.1.tgz#7a42d58474454963759e8e8b7ae63d71c1e7fdf4"
integrity sha512-n3sPwynL1nwKi3WJ6AIsClwBMa0zTi54fn2oLU6ndfTSIO05xaznjSf15PcBZU6FNWbmN5Q6cxT4V5hGvB4taw==
+string-width@^2.0.0, string-width@^2.1.1:
+ version "2.1.1"
+ resolved "https://registry.yarnpkg.com/string-width/-/string-width-2.1.1.tgz#ab93f27a8dc13d28cac815c462143a6d9012ae9e"
+ integrity sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==
+ dependencies:
+ is-fullwidth-code-point "^2.0.0"
+ strip-ansi "^4.0.0"
+
string-width@^3.0.0, string-width@^3.1.0:
version "3.1.0"
resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961"
@@ -10868,6 +11090,13 @@ strip-ansi@^3.0.1:
dependencies:
ansi-regex "^2.0.0"
+strip-ansi@^4.0.0:
+ version "4.0.0"
+ resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-4.0.0.tgz#a8479022eb1ac368a871389b635262c505ee368f"
+ integrity sha1-qEeQIusaw2iocTibY1JixQXuNo8=
+ dependencies:
+ ansi-regex "^3.0.0"
+
strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0:
version "5.2.0"
resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae"
@@ -10922,6 +11151,11 @@ strip-json-comments@^3.1.0, strip-json-comments@^3.1.1:
resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006"
integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==
+strip-json-comments@~2.0.1:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-2.0.1.tgz#3c531942e908c2697c0ec344858c286c7ca0a60a"
+ integrity sha1-PFMZQukIwml8DsNEhYwobHygpgo=
+
style-loader@1.3.0:
version "1.3.0"
resolved "https://registry.yarnpkg.com/style-loader/-/style-loader-1.3.0.tgz#828b4a3b3b7e7aa5847ce7bae9e874512114249e"
@@ -11047,6 +11281,13 @@ tempy@^0.3.0:
type-fest "^0.3.1"
unique-string "^1.0.0"
+term-size@^1.2.0:
+ version "1.2.0"
+ resolved "https://registry.yarnpkg.com/term-size/-/term-size-1.2.0.tgz#458b83887f288fc56d6fffbfad262e26638efa69"
+ integrity sha1-RYuDiH8oj8Vtb/+/rSYuJmOO+mk=
+ dependencies:
+ execa "^0.7.0"
+
terminal-link@^2.0.0:
version "2.1.1"
resolved "https://registry.yarnpkg.com/terminal-link/-/terminal-link-2.1.1.tgz#14a64a27ab3c0df933ea546fba55f2d078edc994"
@@ -11529,6 +11770,14 @@ upath@^1.1.1, upath@^1.1.2, upath@^1.2.0:
resolved "https://registry.yarnpkg.com/upath/-/upath-1.2.0.tgz#8f66dbcd55a883acdae4408af8b035a5044c1894"
integrity sha512-aZwGpamFO61g3OlfT7OQCHqhGnW43ieH9WZeP7QxN/G/jS4jfqUkZxoryvJgVPEcrl5NL/ggHsSmLMHuH64Lhg==
+update-check@1.5.2:
+ version "1.5.2"
+ resolved "https://registry.yarnpkg.com/update-check/-/update-check-1.5.2.tgz#2fe09f725c543440b3d7dabe8971f2d5caaedc28"
+ integrity sha512-1TrmYLuLj/5ZovwUS7fFd1jMH3NnFDN1y1A8dboedIDt7zs/zJMo6TwwlhYKkSeEwzleeiSBV5/3c9ufAQWDaQ==
+ dependencies:
+ registry-auth-token "3.3.2"
+ registry-url "3.1.0"
+
uri-js@^4.2.2:
version "4.4.1"
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
@@ -11937,6 +12186,13 @@ which@^2.0.1, which@^2.0.2:
dependencies:
isexe "^2.0.0"
+widest-line@^2.0.0:
+ version "2.0.1"
+ resolved "https://registry.yarnpkg.com/widest-line/-/widest-line-2.0.1.tgz#7438764730ec7ef4381ce4df82fb98a53142a3fc"
+ integrity sha512-Ba5m9/Fa4Xt9eb2ELXt77JxVDV8w7qQrH0zS/TWSJdLyAwQjWoOzpzj5lwVftDz6n/EOu3tNACS84v509qwnJA==
+ dependencies:
+ string-width "^2.1.1"
+
word-wrap@^1.2.3, word-wrap@~1.2.3:
version "1.2.3"
resolved "https://registry.yarnpkg.com/word-wrap/-/word-wrap-1.2.3.tgz#610636f6b1f703891bd34771ccb17fb93b47079c"
@@ -12174,6 +12430,11 @@ y18n@^4.0.0:
resolved "https://registry.yarnpkg.com/y18n/-/y18n-4.0.3.tgz#b5f259c82cd6e336921efd7bfd8bf560de9eeedf"
integrity sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==
+yallist@^2.1.2:
+ version "2.1.2"
+ resolved "https://registry.yarnpkg.com/yallist/-/yallist-2.1.2.tgz#1c11f9218f076089a47dd512f93c6699a6a81d52"
+ integrity sha1-HBH5IY8HYImkfdUS+TxmmaaoHVI=
+
yallist@^3.0.2:
version "3.1.1"
resolved "https://registry.yarnpkg.com/yallist/-/yallist-3.1.1.tgz#dbb7daf9bfd8bac9ab45ebf602b8cbad0d5d08fd"
diff --git a/proxy/Dockerfile b/proxy/Dockerfile
new file mode 100644
index 0000000..3a06e31
--- /dev/null
+++ b/proxy/Dockerfile
@@ -0,0 +1,4 @@
+FROM httpd:2.4
+LABEL maintainer="Stefano Pigozzi "
+
+COPY ./httpd.conf /usr/local/apache2/conf/httpd.conf
diff --git a/proxy/httpd.conf b/proxy/httpd.conf
new file mode 100644
index 0000000..3efca55
--- /dev/null
+++ b/proxy/httpd.conf
@@ -0,0 +1,595 @@
+#
+# This is the main Apache HTTP server configuration file. It contains the
+# configuration directives that give the server its instructions.
+# See for detailed information.
+# In particular, see
+#
+# for a discussion of each configuration directive.
+#
+# Do NOT simply read the instructions in here without understanding
+# what they do. They're here only as hints or reminders. If you are unsure
+# consult the online docs. You have been warned.
+#
+# Configuration and logfile names: If the filenames you specify for many
+# of the server's control files begin with "/" (or "drive:/" for Win32), the
+# server will use that explicit path. If the filenames do *not* begin
+# with "/", the value of ServerRoot is prepended -- so "logs/access_log"
+# with ServerRoot set to "/usr/local/apache2" will be interpreted by the
+# server as "/usr/local/apache2/logs/access_log", whereas "/logs/access_log"
+# will be interpreted as '/logs/access_log'.
+
+#
+# ServerRoot: The top of the directory tree under which the server's
+# configuration, error, and log files are kept.
+#
+# Do not add a slash at the end of the directory path. If you point
+# ServerRoot at a non-local disk, be sure to specify a local disk on the
+# Mutex directive, if file-based mutexes are used. If you wish to share the
+# same ServerRoot for multiple httpd daemons, you will need to change at
+# least PidFile.
+#
+ServerRoot "/usr/local/apache2"
+
+#
+# Mutex: Allows you to set the mutex mechanism and mutex file directory
+# for individual mutexes, or change the global defaults
+#
+# Uncomment and change the directory if mutexes are file-based and the default
+# mutex file directory is not on a local disk or is not appropriate for some
+# other reason.
+#
+# Mutex default:logs
+
+#
+# Listen: Allows you to bind Apache to specific IP addresses and/or
+# ports, instead of the default. See also the
+# directive.
+#
+# Change this to Listen on specific IP addresses as shown below to
+# prevent Apache from glomming onto all bound IP addresses.
+#
+#Listen 12.34.56.78:80
+Listen 80
+
+#
+# Dynamic Shared Object (DSO) Support
+#
+# To be able to use the functionality of a module which was built as a DSO you
+# have to place corresponding `LoadModule' lines at this location so the
+# directives contained in it are actually available _before_ they are used.
+# Statically compiled modules (those listed by `httpd -l') do not need
+# to be loaded here.
+#
+# Example:
+# LoadModule foo_module modules/mod_foo.so
+#
+LoadModule mpm_event_module modules/mod_mpm_event.so
+#LoadModule mpm_prefork_module modules/mod_mpm_prefork.so
+#LoadModule mpm_worker_module modules/mod_mpm_worker.so
+LoadModule authn_file_module modules/mod_authn_file.so
+#LoadModule authn_dbm_module modules/mod_authn_dbm.so
+#LoadModule authn_anon_module modules/mod_authn_anon.so
+#LoadModule authn_dbd_module modules/mod_authn_dbd.so
+#LoadModule authn_socache_module modules/mod_authn_socache.so
+LoadModule authn_core_module modules/mod_authn_core.so
+LoadModule authz_host_module modules/mod_authz_host.so
+LoadModule authz_groupfile_module modules/mod_authz_groupfile.so
+LoadModule authz_user_module modules/mod_authz_user.so
+#LoadModule authz_dbm_module modules/mod_authz_dbm.so
+#LoadModule authz_owner_module modules/mod_authz_owner.so
+#LoadModule authz_dbd_module modules/mod_authz_dbd.so
+LoadModule authz_core_module modules/mod_authz_core.so
+#LoadModule authnz_ldap_module modules/mod_authnz_ldap.so
+#LoadModule authnz_fcgi_module modules/mod_authnz_fcgi.so
+LoadModule access_compat_module modules/mod_access_compat.so
+LoadModule auth_basic_module modules/mod_auth_basic.so
+#LoadModule auth_form_module modules/mod_auth_form.so
+#LoadModule auth_digest_module modules/mod_auth_digest.so
+#LoadModule allowmethods_module modules/mod_allowmethods.so
+#LoadModule isapi_module modules/mod_isapi.so
+#LoadModule file_cache_module modules/mod_file_cache.so
+#LoadModule cache_module modules/mod_cache.so
+#LoadModule cache_disk_module modules/mod_cache_disk.so
+#LoadModule cache_socache_module modules/mod_cache_socache.so
+#LoadModule socache_shmcb_module modules/mod_socache_shmcb.so
+#LoadModule socache_dbm_module modules/mod_socache_dbm.so
+#LoadModule socache_memcache_module modules/mod_socache_memcache.so
+#LoadModule socache_redis_module modules/mod_socache_redis.so
+#LoadModule watchdog_module modules/mod_watchdog.so
+#LoadModule macro_module modules/mod_macro.so
+#LoadModule dbd_module modules/mod_dbd.so
+#LoadModule bucketeer_module modules/mod_bucketeer.so
+#LoadModule dumpio_module modules/mod_dumpio.so
+#LoadModule echo_module modules/mod_echo.so
+#LoadModule example_hooks_module modules/mod_example_hooks.so
+#LoadModule case_filter_module modules/mod_case_filter.so
+#LoadModule case_filter_in_module modules/mod_case_filter_in.so
+#LoadModule example_ipc_module modules/mod_example_ipc.so
+#LoadModule buffer_module modules/mod_buffer.so
+#LoadModule data_module modules/mod_data.so
+#LoadModule ratelimit_module modules/mod_ratelimit.so
+LoadModule reqtimeout_module modules/mod_reqtimeout.so
+#LoadModule ext_filter_module modules/mod_ext_filter.so
+#LoadModule request_module modules/mod_request.so
+#LoadModule include_module modules/mod_include.so
+LoadModule filter_module modules/mod_filter.so
+#LoadModule reflector_module modules/mod_reflector.so
+#LoadModule substitute_module modules/mod_substitute.so
+#LoadModule sed_module modules/mod_sed.so
+#LoadModule charset_lite_module modules/mod_charset_lite.so
+#LoadModule deflate_module modules/mod_deflate.so
+#LoadModule xml2enc_module modules/mod_xml2enc.so
+#LoadModule proxy_html_module modules/mod_proxy_html.so
+#LoadModule brotli_module modules/mod_brotli.so
+LoadModule mime_module modules/mod_mime.so
+#LoadModule ldap_module modules/mod_ldap.so
+LoadModule log_config_module modules/mod_log_config.so
+#LoadModule log_debug_module modules/mod_log_debug.so
+#LoadModule log_forensic_module modules/mod_log_forensic.so
+#LoadModule logio_module modules/mod_logio.so
+#LoadModule lua_module modules/mod_lua.so
+LoadModule env_module modules/mod_env.so
+#LoadModule mime_magic_module modules/mod_mime_magic.so
+#LoadModule cern_meta_module modules/mod_cern_meta.so
+#LoadModule expires_module modules/mod_expires.so
+LoadModule headers_module modules/mod_headers.so
+#LoadModule ident_module modules/mod_ident.so
+#LoadModule usertrack_module modules/mod_usertrack.so
+#LoadModule unique_id_module modules/mod_unique_id.so
+LoadModule setenvif_module modules/mod_setenvif.so
+LoadModule version_module modules/mod_version.so
+#LoadModule remoteip_module modules/mod_remoteip.so
+LoadModule proxy_module modules/mod_proxy.so
+#LoadModule proxy_connect_module modules/mod_proxy_connect.so
+#LoadModule proxy_ftp_module modules/mod_proxy_ftp.so
+LoadModule proxy_http_module modules/mod_proxy_http.so
+#LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so
+#LoadModule proxy_scgi_module modules/mod_proxy_scgi.so
+#LoadModule proxy_uwsgi_module modules/mod_proxy_uwsgi.so
+#LoadModule proxy_fdpass_module modules/mod_proxy_fdpass.so
+LoadModule proxy_wstunnel_module modules/mod_proxy_wstunnel.so
+#LoadModule proxy_ajp_module modules/mod_proxy_ajp.so
+#LoadModule proxy_balancer_module modules/mod_proxy_balancer.so
+#LoadModule proxy_express_module modules/mod_proxy_express.so
+#LoadModule proxy_hcheck_module modules/mod_proxy_hcheck.so
+#LoadModule session_module modules/mod_session.so
+#LoadModule session_cookie_module modules/mod_session_cookie.so
+#LoadModule session_crypto_module modules/mod_session_crypto.so
+#LoadModule session_dbd_module modules/mod_session_dbd.so
+#LoadModule slotmem_shm_module modules/mod_slotmem_shm.so
+#LoadModule slotmem_plain_module modules/mod_slotmem_plain.so
+#LoadModule ssl_module modules/mod_ssl.so
+#LoadModule optional_hook_export_module modules/mod_optional_hook_export.so
+#LoadModule optional_hook_import_module modules/mod_optional_hook_import.so
+#LoadModule optional_fn_import_module modules/mod_optional_fn_import.so
+#LoadModule optional_fn_export_module modules/mod_optional_fn_export.so
+#LoadModule dialup_module modules/mod_dialup.so
+#LoadModule http2_module modules/mod_http2.so
+#LoadModule proxy_http2_module modules/mod_proxy_http2.so
+#LoadModule md_module modules/mod_md.so
+#LoadModule lbmethod_byrequests_module modules/mod_lbmethod_byrequests.so
+#LoadModule lbmethod_bytraffic_module modules/mod_lbmethod_bytraffic.so
+#LoadModule lbmethod_bybusyness_module modules/mod_lbmethod_bybusyness.so
+#LoadModule lbmethod_heartbeat_module modules/mod_lbmethod_heartbeat.so
+LoadModule unixd_module modules/mod_unixd.so
+#LoadModule heartbeat_module modules/mod_heartbeat.so
+#LoadModule heartmonitor_module modules/mod_heartmonitor.so
+#LoadModule dav_module modules/mod_dav.so
+LoadModule status_module modules/mod_status.so
+LoadModule autoindex_module modules/mod_autoindex.so
+#LoadModule asis_module modules/mod_asis.so
+#LoadModule info_module modules/mod_info.so
+#LoadModule suexec_module modules/mod_suexec.so
+
+ #LoadModule cgid_module modules/mod_cgid.so
+
+
+ #LoadModule cgi_module modules/mod_cgi.so
+
+#LoadModule dav_fs_module modules/mod_dav_fs.so
+#LoadModule dav_lock_module modules/mod_dav_lock.so
+#LoadModule vhost_alias_module modules/mod_vhost_alias.so
+#LoadModule negotiation_module modules/mod_negotiation.so
+LoadModule dir_module modules/mod_dir.so
+#LoadModule imagemap_module modules/mod_imagemap.so
+#LoadModule actions_module modules/mod_actions.so
+#LoadModule speling_module modules/mod_speling.so
+#LoadModule userdir_module modules/mod_userdir.so
+LoadModule alias_module modules/mod_alias.so
+LoadModule rewrite_module modules/mod_rewrite.so
+
+
+#
+# If you wish httpd to run as a different user or group, you must run
+# httpd as root initially and it will switch.
+#
+# User/Group: The name (or #number) of the user/group to run httpd as.
+# It is usually good practice to create a dedicated user and group for
+# running httpd, as with most system services.
+#
+User daemon
+Group daemon
+
+
+
+# 'Main' server configuration
+#
+# The directives in this section set up the values used by the 'main'
+# server, which responds to any requests that aren't handled by a
+# definition. These values also provide defaults for
+# any containers you may define later in the file.
+#
+# All of these directives may appear inside containers,
+# in which case these default settings will be overridden for the
+# virtual host being defined.
+#
+
+#
+# ServerAdmin: Your address, where problems with the server should be
+# e-mailed. This address appears on some server-generated pages, such
+# as error documents. e.g. admin@your-domain.com
+#
+ServerAdmin nobody@example.org
+
+#
+# ServerName gives the name and port that the server uses to identify itself.
+# This can often be determined automatically, but we recommend you specify
+# it explicitly to prevent problems during startup.
+#
+# If your host doesn't have a registered DNS name, enter its IP address here.
+#
+#ServerName www.example.com:80
+
+#
+# Deny access to the entirety of your server's filesystem. You must
+# explicitly permit access to web content directories in other
+# blocks below.
+#
+
+ AllowOverride none
+ Require all denied
+
+
+#
+# Note that from this point forward you must specifically allow
+# particular features to be enabled - so if something's not working as
+# you might expect, make sure that you have specifically enabled it
+# below.
+#
+
+#
+# DocumentRoot: The directory out of which you will serve your
+# documents. By default, all requests are taken from this directory, but
+# symbolic links and aliases may be used to point to other locations.
+#
+DocumentRoot "/usr/local/apache2/htdocs"
+
+ #
+ # Possible values for the Options directive are "None", "All",
+ # or any combination of:
+ # Indexes Includes FollowSymLinks SymLinksifOwnerMatch ExecCGI MultiViews
+ #
+ # Note that "MultiViews" must be named *explicitly* --- "Options All"
+ # doesn't give it to you.
+ #
+ # The Options directive is both complicated and important. Please see
+ # http://httpd.apache.org/docs/2.4/mod/core.html#options
+ # for more information.
+ #
+ Options Indexes FollowSymLinks
+
+ #
+ # AllowOverride controls what directives may be placed in .htaccess files.
+ # It can be "All", "None", or any combination of the keywords:
+ # AllowOverride FileInfo AuthConfig Limit
+ #
+ AllowOverride None
+
+ #
+ # Controls who can get stuff from this server.
+ #
+ Require all granted
+
+
+#
+# DirectoryIndex: sets the file that Apache will serve if a directory
+# is requested.
+#
+
+ DirectoryIndex index.html
+
+
+#
+# The following lines prevent .htaccess and .htpasswd files from being
+# viewed by Web clients.
+#
+
+ Require all denied
+
+
+#
+# ErrorLog: The location of the error log file.
+# If you do not specify an ErrorLog directive within a
+# container, error messages relating to that virtual host will be
+# logged here. If you *do* define an error logfile for a
+# container, that host's errors will be logged there and not here.
+#
+ErrorLog /proc/self/fd/2
+
+#
+# LogLevel: Control the number of messages logged to the error_log.
+# Possible values include: debug, info, notice, warn, error, crit,
+# alert, emerg.
+#
+LogLevel warn
+
+
+ #
+ # The following directives define some format nicknames for use with
+ # a CustomLog directive (see below).
+ #
+ LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\"" combined
+ LogFormat "%h %l %u %t \"%r\" %>s %b" common
+
+
+ # You need to enable mod_logio.c to use %I and %O
+ LogFormat "%h %l %u %t \"%r\" %>s %b \"%{Referer}i\" \"%{User-Agent}i\" %I %O" combinedio
+
+
+ #
+ # The location and format of the access logfile (Common Logfile Format).
+ # If you do not define any access logfiles within a
+ # container, they will be logged here. Contrariwise, if you *do*
+ # define per- access logfiles, transactions will be
+ # logged therein and *not* in this file.
+ #
+ CustomLog /proc/self/fd/1 common
+
+ #
+ # If you prefer a logfile with access, agent, and referer information
+ # (Combined Logfile Format) you can use the following directive.
+ #
+ #CustomLog "logs/access_log" combined
+
+
+
+ #
+ # Redirect: Allows you to tell clients about documents that used to
+ # exist in your server's namespace, but do not anymore. The client
+ # will make a new request for the document at its new location.
+ # Example:
+ # Redirect permanent /foo http://www.example.com/bar
+
+ #
+ # Alias: Maps web paths into filesystem paths and is used to
+ # access content that does not live under the DocumentRoot.
+ # Example:
+ # Alias /webpath /full/filesystem/path
+ #
+ # If you include a trailing / on /webpath then the server will
+ # require it to be present in the URL. You will also likely
+ # need to provide a section to allow access to
+ # the filesystem path.
+
+ #
+ # ScriptAlias: This controls which directories contain server scripts.
+ # ScriptAliases are essentially the same as Aliases, except that
+ # documents in the target directory are treated as applications and
+ # run by the server when requested rather than as documents sent to the
+ # client. The same rules about trailing "/" apply to ScriptAlias
+ # directives as to Alias.
+ #
+ ScriptAlias /cgi-bin/ "/usr/local/apache2/cgi-bin/"
+
+
+
+
+ #
+ # ScriptSock: On threaded servers, designate the path to the UNIX
+ # socket used to communicate with the CGI daemon of mod_cgid.
+ #
+ #Scriptsock cgisock
+
+
+#
+# "/usr/local/apache2/cgi-bin" should be changed to whatever your ScriptAliased
+# CGI directory exists, if you have that configured.
+#
+
+ AllowOverride None
+ Options None
+ Require all granted
+
+
+
+ #
+ # Avoid passing HTTP_PROXY environment to CGI's on this or any proxied
+ # backend servers which have lingering "httpoxy" defects.
+ # 'Proxy' request header is undefined by the IETF, not listed by IANA
+ #
+ RequestHeader unset Proxy early
+
+
+
+ #
+ # TypesConfig points to the file containing the list of mappings from
+ # filename extension to MIME-type.
+ #
+ TypesConfig conf/mime.types
+
+ #
+ # AddType allows you to add to or override the MIME configuration
+ # file specified in TypesConfig for specific file types.
+ #
+ #AddType application/x-gzip .tgz
+ #
+ # AddEncoding allows you to have certain browsers uncompress
+ # information on the fly. Note: Not all browsers support this.
+ #
+ #AddEncoding x-compress .Z
+ #AddEncoding x-gzip .gz .tgz
+ #
+ # If the AddEncoding directives above are commented-out, then you
+ # probably should define those extensions to indicate media types:
+ #
+ AddType application/x-compress .Z
+ AddType application/x-gzip .gz .tgz
+
+ #
+ # AddHandler allows you to map certain file extensions to "handlers":
+ # actions unrelated to filetype. These can be either built into the server
+ # or added with the Action directive (see below)
+ #
+ # To use CGI scripts outside of ScriptAliased directories:
+ # (You will also need to add "ExecCGI" to the "Options" directive.)
+ #
+ #AddHandler cgi-script .cgi
+
+ # For type maps (negotiated resources):
+ #AddHandler type-map var
+
+ #
+ # Filters allow you to process content before it is sent to the client.
+ #
+ # To parse .shtml files for server-side includes (SSI):
+ # (You will also need to add "Includes" to the "Options" directive.)
+ #
+ #AddType text/html .shtml
+ #AddOutputFilter INCLUDES .shtml
+
+
+#
+# The mod_mime_magic module allows the server to use various hints from the
+# contents of the file itself to determine its type. The MIMEMagicFile
+# directive tells the module where the hint definitions are located.
+#
+#MIMEMagicFile conf/magic
+
+#
+# Customizable error responses come in three flavors:
+# 1) plain text 2) local redirects 3) external redirects
+#
+# Some examples:
+#ErrorDocument 500 "The server made a boo boo."
+#ErrorDocument 404 /missing.html
+#ErrorDocument 404 "/cgi-bin/missing_handler.pl"
+#ErrorDocument 402 http://www.example.com/subscription_info.html
+#
+
+#
+# MaxRanges: Maximum number of Ranges in a request before
+# returning the entire resource, or one of the special
+# values 'default', 'none' or 'unlimited'.
+# Default setting is to accept 200 Ranges.
+#MaxRanges unlimited
+
+#
+# EnableMMAP and EnableSendfile: On systems that support it,
+# memory-mapping or the sendfile syscall may be used to deliver
+# files. This usually improves server performance, but must
+# be turned off when serving from networked-mounted
+# filesystems or if support for these functions is otherwise
+# broken on your system.
+# Defaults: EnableMMAP On, EnableSendfile Off
+#
+#EnableMMAP off
+#EnableSendfile on
+
+# Supplemental configuration
+#
+# The configuration files in the conf/extra/ directory can be
+# included to add extra features or to modify the default configuration of
+# the server, or you may simply copy their contents here and change as
+# necessary.
+
+# Server-pool management (MPM specific)
+#Include conf/extra/httpd-mpm.conf
+
+# Multi-language error messages
+#Include conf/extra/httpd-multilang-errordoc.conf
+
+# Fancy directory listings
+#Include conf/extra/httpd-autoindex.conf
+
+# Language settings
+#Include conf/extra/httpd-languages.conf
+
+# User home directories
+#Include conf/extra/httpd-userdir.conf
+
+# Real-time info on requests and configuration
+#Include conf/extra/httpd-info.conf
+
+# Virtual hosts
+#Include conf/extra/httpd-vhosts.conf
+
+# Local access to the Apache HTTP Server Manual
+#Include conf/extra/httpd-manual.conf
+
+# Distributed authoring and versioning (WebDAV)
+#Include conf/extra/httpd-dav.conf
+
+# Various default settings
+#Include conf/extra/httpd-default.conf
+
+# Configure mod_proxy_html to understand HTML4/XHTML1
+
+Include conf/extra/proxy-html.conf
+
+
+# Secure (SSL/TLS) connections
+#Include conf/extra/httpd-ssl.conf
+#
+# Note: The following must must be present to support
+# starting without SSL on platforms with no /dev/random equivalent
+# but a statically compiled-in mod_ssl.
+#
+
+SSLRandomSeed startup builtin
+SSLRandomSeed connect builtin
+
+
+
+# === Sophon specific settings ===
+# --- DARK SORCERY AHEAD ---
+# Regexes used as string comparisions lie beyond this line.
+
+LogLevel rewrite:trace6
+
+# Enable rewriting (proxying)
+RewriteEngine on
+
+# Preserve host
+ProxyPreserveHost on
+
+
+# Proxy regular requests to the frontend
+# sophon.steffo.eu → frontend
+RewriteCond "%{ENV:matched}" "! -eq 1" [NC] # If the url hasn't been matched by the previous rules
+RewriteCond "%{ENV:APACHE_PROXY_BASE_DOMAIN} %{HTTP_HOST}" "^([^ ]+) \1$" [NC] # If ENV:APACHE_PROXY_BASE_DOMAIN equals HTTP_HOST
+RewriteCond "%{ENV:SOPHON_FRONTEND_NAME}" "^(.+)$" [NC] # Capture ENV:SOPHON_FRONTEND_NAME for substitution in the rewriterule
+RewriteRule "/(.*)" "http://%1/$1" [P,L,E=matched:1] # Rewrite as a SPA and set the matched flag
+
+# Proxy api requests to the backend
+# api.sophon.steffo.eu → backend
+RewriteCond "%{ENV:matched}" "! -eq 1" [NC] # If the url hasn't been matched by the previous rules
+RewriteCond "api.%{ENV:APACHE_PROXY_BASE_DOMAIN} %{HTTP_HOST}" "^([^ ]+) \1$" [NC] # If api. prefixed to ENV:APACHE_PROXY_BASE_DOMAIN equals HTTP_HOST
+RewriteCond "%{ENV:SOPHON_BACKEND_NAME}" "^(.+)$" [NC] # Capture ENV:SOPHON_BACKEND_NAME for substitution in the rewriterule
+RewriteRule "/(.*)" "http://%1/$1" [P,L,E=matched:1] # Rewrite and set the matched flag
+
+# Create a map between the proxy file generated by Sophon and Apache
+RewriteMap "sophonproxy" "dbm=gdbm:/run/sophon/proxy/proxy.dbm"
+
+# Proxy websockets to the notebooks
+# *.sophon.steffo.eu → notebook
+RewriteCond "%{ENV:matched}" "! -eq 1" [NC] # If the url hasn't been matched by the previous rules
+RewriteCond ".%{ENV:APACHE_PROXY_BASE_DOMAIN} %{HTTP_HOST}" "^([^ ]+) [^ ]+\1$" [NC] # If this is any other subdomain of ENV:APACHE_PROXY_BASE_DOMAIN
+RewriteCond "%{HTTP:Connection}" "Upgrade" [NC] # If this is a websocket connection
+RewriteCond "%{HTTP:Upgrade}" "websocket" [NC] # If this is a websocket connection
+RewriteRule "/(.*)" "ws://${sophonproxy:%{HTTP_HOST}}/$1" [P,L,E=matched:1] # Rewrite and set the matched flag
+
+# Proxy regular requests to the notebooks
+# *.sophon.steffo.eu → notebook
+RewriteCond "%{ENV:matched}" "! -eq 1" [NC] # If the url hasn't been matched by the previous rules
+RewriteCond ".%{ENV:APACHE_PROXY_BASE_DOMAIN} %{HTTP_HOST}" "^([^ ]+) [^ ]+\1$" [NC] # If this is any other subdomain of ENV:APACHE_PROXY_BASE_DOMAIN
+RewriteRule "/(.*)" "http://${sophonproxy:%{HTTP_HOST}}/$1" [P,L,E=matched:1]