diff --git a/.gitignore b/.gitignore index 04ef5c46fa..5cab6cc5c7 100644 --- a/.gitignore +++ b/.gitignore @@ -9,6 +9,7 @@ docs/gh-pages jupyter_server/i18n/*/LC_MESSAGES/*.mo jupyter_server/i18n/*/LC_MESSAGES/nbjs.json jupyter_server/static/style/*.min.css* +jupyter_server/static/vendor/ node_modules *.py[co] __pycache__ diff --git a/jupyter_server/services/api/handlers.py b/jupyter_server/services/api/handlers.py index 22904fdb07..3d2703ea88 100644 --- a/jupyter_server/services/api/handlers.py +++ b/jupyter_server/services/api/handlers.py @@ -43,6 +43,18 @@ def get_content_type(self): return "text/x-yaml" +class APIDocsHandler(JupyterHandler): + """A handler for the documentation of the REST API.""" + + auth_resource = AUTH_RESOURCE + + @web.authenticated + @authorized + async def get(self): + """Get the REST API documentation.""" + return self.finish(self.render_template("apidocs.html")) + + class APIStatusHandler(APIHandler): """An API status handler.""" @@ -115,6 +127,7 @@ async def get(self): default_handlers = [ + (r"/api/apidocs", APIDocsHandler), (r"/api/spec.yaml", APISpecHandler), (r"/api/status", APIStatusHandler), (r"/api/me", IdentityHandler), diff --git a/jupyter_server/templates/apidocs.html b/jupyter_server/templates/apidocs.html new file mode 100644 index 0000000000..9d4e761935 --- /dev/null +++ b/jupyter_server/templates/apidocs.html @@ -0,0 +1,27 @@ +{% extends "page.html" %} + +{% block stylesheet %} + {{ super() }} + + +{% endblock stylesheet %} + +{% block site %} +
+{% endblock site %} + +{% block script %} + {{ super() }} + + + +{% endblock script %} diff --git a/package-lock.json b/package-lock.json index 546528db85..d34b816d1f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,7 +10,8 @@ "license": "BSD", "dependencies": { "bootstrap": "^3.4.0", - "copyfiles": "^2.4.1" + "copyfiles": "^2.4.1", + "swagger-ui-dist": "^5.17.2" } }, "node_modules/ansi-regex": { @@ -288,6 +289,11 @@ "node": ">=8" } }, + "node_modules/swagger-ui-dist": { + "version": "5.17.2", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.17.2.tgz", + "integrity": "sha512-V/NqUw6QoTrjSpctp2oLQvxrl3vW29UsUtZyq7B1CF0v870KOFbYGDQw8rpKaKm0JxTwHpWnW1SN9YuKZdiCyw==" + }, "node_modules/through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", @@ -621,6 +627,11 @@ "ansi-regex": "^5.0.0" } }, + "swagger-ui-dist": { + "version": "5.17.2", + "resolved": "https://registry.npmjs.org/swagger-ui-dist/-/swagger-ui-dist-5.17.2.tgz", + "integrity": "sha512-V/NqUw6QoTrjSpctp2oLQvxrl3vW29UsUtZyq7B1CF0v870KOFbYGDQw8rpKaKm0JxTwHpWnW1SN9YuKZdiCyw==" + }, "through2": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/through2/-/through2-2.0.5.tgz", diff --git a/package.json b/package.json index 78c0dce49a..87d7309e14 100644 --- a/package.json +++ b/package.json @@ -4,11 +4,14 @@ "version": "1.0.0", "license": "BSD", "scripts": { - "build": "copyfiles -f node_modules/bootstrap/dist/css/*.min.* jupyter_server/static/style" + "build": "npm run build:bootstrap && npm run build:swagger-ui", + "build:bootstrap": "copyfiles -f node_modules/bootstrap/dist/css/*.min.* jupyter_server/static/style", + "build:swagger-ui": "copyfiles -f \"node_modules/swagger-ui-dist/{NOTICE,LICENSE,swagger-ui.css,swagger-ui-bundle.js}\" jupyter_server/static/vendor/swagger-ui-dist" }, "dependencies": { "bootstrap": "^3.4.0", - "copyfiles": "^2.4.1" + "copyfiles": "^2.4.1", + "swagger-ui-dist": "^5.17.2" }, "eslintIgnore": [ "*.min.js", diff --git a/pyproject.toml b/pyproject.toml index 42350ed474..93d8448d1a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -126,16 +126,26 @@ path = "jupyter_server/_version.py" validate-bump = false [tool.hatch.build] -artifacts = ["jupyter_server/static/style"] +artifacts = [ + "jupyter_server/static/style", + "jupyter_server/static/vendor", +] [tool.hatch.build.hooks.jupyter-builder] dependencies = ["hatch-jupyter-builder>=0.8.1"] build-function = "hatch_jupyter_builder.npm_builder" ensured-targets = [ "jupyter_server/static/style/bootstrap.min.css", - "jupyter_server/static/style/bootstrap-theme.min.css" + "jupyter_server/static/style/bootstrap-theme.min.css", + "jupyter_server/static/vendor/swagger-ui-dist/LICENSE", + "jupyter_server/static/vendor/swagger-ui-dist/NOTICE", + "jupyter_server/static/vendor/swagger-ui-dist/swagger-ui-bundle.js", + "jupyter_server/static/vendor/swagger-ui-dist/swagger-ui.css", +] +skip-if-exists = [ + "jupyter_server/static/style/bootstrap.min.css", + "jupyter_server/static/vendor/swagger-ui-dist/swagger-ui.css", ] -skip-if-exists = ["jupyter_server/static/style/bootstrap.min.css"] install-pre-commit-hook = true optional-editable-build = true diff --git a/tests/services/api/test_api.py b/tests/services/api/test_api.py index f013dcfcd8..1a1115e18f 100644 --- a/tests/services/api/test_api.py +++ b/tests/services/api/test_api.py @@ -13,6 +13,11 @@ async def test_get_spec(jp_fetch): assert response.code == 200 +async def test_get_api_docs(jp_fetch): + response = await jp_fetch("api", "apidocs", method="GET") + assert response.code == 200 + + async def test_get_status(jp_fetch): response = await jp_fetch("api", "status", method="GET") assert response.code == 200