diff --git a/conda-store-server/conda_store_server/server/templates/base.html b/conda-store-server/conda_store_server/server/templates/base.html index acbeaab9a..cb128dc53 100644 --- a/conda-store-server/conda_store_server/server/templates/base.html +++ b/conda-store-server/conda_store_server/server/templates/base.html @@ -13,6 +13,8 @@ {% include 'navigation.html' %}
+
+ {% block content %}{% endblock %}
diff --git a/conda-store-server/conda_store_server/server/templates/create.html b/conda-store-server/conda_store_server/server/templates/create.html index b08b478df..48928cc7b 100644 --- a/conda-store-server/conda_store_server/server/templates/create.html +++ b/conda-store-server/conda_store_server/server/templates/create.html @@ -7,12 +7,12 @@ {% endif %} -
+
@@ -52,5 +52,44 @@ }); reader.readAsText(event.target.files[0]); }); + + function bannerMessage(message) { + let banner = document.querySelector('#message'); + banner.innerHTML = message; + } + + async function createEnvironmentHandler(event) { + event.preventDefault(); + + let url = "{{ url_for('api.api_post_specification') }}"; + let namespaceInput = document.querySelector('#namespace'); + let specificationInput = document.querySelector('#specification'); + + let response = await fetch(url, { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ + 'namespace': namespaceInput.value, + 'specification': specificationInput.value, + }) + }); + + if (response.ok) { + window.location = "{{ url_for('ui.ui_list_environments') }}"; + } else { + let data = await response.json(); + let message = data.message; + if (message.search('[\n\r]') !== -1) { + bannerMessage(`
${data.message}
`); + } else { + bannerMessage(`
${data.message}
`); + } + } + } + + let form = document.querySelector('#create-environment'); + form.addEventListener('submit', (event) => createEnvironmentHandler(event)); {% endblock %} diff --git a/conda-store-server/conda_store_server/server/templates/environment.html b/conda-store-server/conda_store_server/server/templates/environment.html index ea08e0f06..07829c0d3 100644 --- a/conda-store-server/conda_store_server/server/templates/environment.html +++ b/conda-store-server/conda_store_server/server/templates/environment.html @@ -19,7 +19,7 @@
{{ environment.namespace.name }}/{{ environment.name }}
Edit -
@@ -31,17 +31,17 @@

Builds

  • Build {{ build.id }} {{ build.status.value }} -
    +
    {% if build.id != environment.current_build_id and build.status.value == 'COMPLETED' and build.deleted_on is none %} - {% endif %} - {% if build.status.value in ["COMPLETED", "FAILED"] and build.deleted_on is none %} - {% endif %} @@ -58,26 +58,90 @@

    Builds

    editor.setFontSize("20px"); editor.setReadOnly(true); - function updateEnvironmentBuild(buildId) { - fetch(`{{ url_for('api.api_get_environment', namespace=environment.namespace.name, name=environment.name) }}`, { + function bannerMessage(message) { + let banner = document.querySelector('#message'); + banner.innerHTML = message; + } + + async function setCurrentEnvironmentBuild(event) { + event.preventDefault(); + let buildId = event.currentTarget.getAttribute('data-id'); + + let url = `{{ url_for('api.api_get_environment', namespace=environment.namespace.name, name=environment.name) }}`; + let response = await fetch(url, { method: 'PUT', headers: { - 'Content-Type': 'application/json', + 'Content-Type': 'application/json' }, - body: JSON.stringify({"build_id": buildId}) - }).then(response => window.location.reload(true)); + body: JSON.stringify({ build_id: buildId }) + }) + + if (response.ok) { + window.location.reload(true); + } else { + let data = await response.json(); + bannerMessage(`
    ${data.message}
    `); + } } - function deleteEnvironment() { - fetch(`{{ url_for('api.api_get_environment', namespace=environment.namespace.name, name=environment.name) }}`, { - method: 'DELETE', - }).then(response => window.location = '/'); + async function refreshBuild(event) { + event.preventDefault(); + let buildId = event.currentTarget.getAttribute('data-id'); + + let url = `{{ url_for('api.api_list_builds') }}${ buildId }/`; + let response = await fetch(url, { method: 'PUT' }); + + if (response.ok) { + window.location.reload(true); + } else { + let data = await response.json(); + bannerMessage(`
    ${data.message}
    `); + } } - function buildAction(method, buildId) { - fetch(`{{ url_for('api.api_list_builds') }}${buildId}/`, { - method: method, - }).then(response => window.location.reload(true)); + async function deleteBuild(event) { + event.preventDefault(); + let buildId = event.currentTarget.getAttribute('data-id'); + + let url = `{{ url_for('api.api_list_builds') }}${ buildId }/`; + let response = await fetch(url, { method: 'DELETE' }); + + if (response.ok) { + window.location.reload(true); + } else { + let data = await response.json(); + bannerMessage(`
    ${data.message}
    `); + } } + + document.querySelectorAll('button[data-action="set-current"]').forEach(item => { + item.addEventListener('click', (event) => setCurrentEnvironmentBuild(event)); + }) + + document.querySelectorAll('button[data-action="refresh"]').forEach(item => { + item.addEventListener('click', (event) => refreshBuild(event)); + }) + + document.querySelectorAll('button[data-action="delete"]').forEach(item => { + item.addEventListener('click', (event) => deleteBuild(event)); + }) + + async function deleteEnvironment(event) { + event.preventDefault(); + + let url = "{{ url_for('api.api_get_environment', namespace=environment.namespace.name, name=environment.name) }}"; + let response = await fetch(url, { method: 'DELETE' }) + + if (response.ok) { + window.location = "{{ url_for('ui.ui_list_environments') }}"; + } else { + let data = await response.json(); + bannerMessage(`
    ${data.message}
    `); + } + } + + let deleteEnvironmentButton = document.querySelector('#delete-environment'); + deleteEnvironmentButton.addEventListener('click', (event) => deleteEnvironment(event)); + {% endblock %} diff --git a/conda-store-server/conda_store_server/server/views/api.py b/conda-store-server/conda_store_server/server/views/api.py index 0c16c56e7..37f359045 100644 --- a/conda-store-server/conda_store_server/server/views/api.py +++ b/conda-store-server/conda_store_server/server/views/api.py @@ -128,7 +128,7 @@ def api_get_namespace(namespace): namespace = api.get_namespace(conda_store.db, namespace) if namespace is None: - return jsonify({"status": "error", "error": "namespace does not exist"}), 404 + return jsonify({"status": "error", "message": "namespace does not exist"}), 404 return jsonify( { @@ -147,7 +147,7 @@ def api_create_namespace(namespace): namespace_orm = api.get_namespace(conda_store.db, namespace) if namespace_orm: - return jsonify({"status": "error", "error": "namespace already exists"}), 409 + return jsonify({"status": "error", "message": "namespace already exists"}), 409 try: api.create_namespace(conda_store.db, namespace) @@ -166,7 +166,7 @@ def api_delete_namespace(namespace): namespace_orm = api.get_namespace(conda_store.db, namespace) if namespace_orm is None: - return jsonify({"status": "error", "error": "namespace does not exist"}), 404 + return jsonify({"status": "error", "message": "namespace does not exist"}), 404 conda_store.delete_namespace(namespace) return jsonify({"status": "ok"}) @@ -205,7 +205,10 @@ def api_get_environment(namespace, name): environment = api.get_environment(conda_store.db, namespace=namespace, name=name) if environment is None: - return jsonify({"status": "error", "error": "environment does not exist"}), 404 + return ( + jsonify({"status": "error", "message": "environment does not exist"}), + 404, + ) return jsonify( { @@ -268,10 +271,13 @@ def api_post_specification(): specification = request.json.get("specification") specification = yaml.safe_load(specification) specification = schema.CondaSpecification.parse_obj(specification) - except yaml.error.YAMLError as e: - return jsonify({"status": "error", "error": str(e)}), 400 + except yaml.error.YAMLError: + return ( + jsonify({"status": "error", "message": "Unable to parse. Invalid YAML"}), + 400, + ) except pydantic.ValidationError as e: - return jsonify({"status": "error", "error": e.errors()}), 400 + return jsonify({"status": "error", "message": str(e)}), 400 auth.authorize_request( f"{namespace_name}/{specification.name}", @@ -282,7 +288,7 @@ def api_post_specification(): try: build_id = api.post_specification(conda_store, specification, namespace_name) except ValueError as e: - return jsonify({"status": "error", "error": str(e.args[0])}), 400 + return jsonify({"status": "error", "message": str(e.args[0])}), 400 return jsonify({"status": "ok", "data": {"build_id": build_id}}) @@ -313,7 +319,7 @@ def api_get_build(build_id): build = api.get_build(conda_store.db, build_id) if build is None: - return jsonify({"status": "error", "error": "build id does not exist"}), 404 + return jsonify({"status": "error", "message": "build id does not exist"}), 404 auth.authorize_request( f"{build.environment.namespace.name}/{build.environment.name}", @@ -336,11 +342,11 @@ def api_put_build(build_id): build = api.get_build(conda_store.db, build_id) if build is None: - return jsonify({"status": "error", "error": "build id does not exist"}), 404 + return jsonify({"status": "error", "message": "build id does not exist"}), 404 auth.authorize_request( f"{build.environment.namespace.name}/{build.environment.name}", - {Permissions.ENVIRONMENT_READ}, + {Permissions.ENVIRONMENT_UPDATE}, require=True, ) @@ -355,7 +361,7 @@ def api_delete_build(build_id): build = api.get_build(conda_store.db, build_id) if build is None: - return jsonify({"status": "error", "error": "build id does not exist"}), 404 + return jsonify({"status": "error", "message": "build id does not exist"}), 404 auth.authorize_request( f"{build.environment.namespace.name}/{build.environment.name}", @@ -374,7 +380,7 @@ def api_get_build_packages(build_id): build = api.get_build(conda_store.db, build_id) if build is None: - return jsonify({"status": "error", "error": "build id does not exist"}), 404 + return jsonify({"status": "error", "message": "build id does not exist"}), 404 auth.authorize_request( f"{build.environment.namespace.name}/{build.environment.name}", @@ -406,7 +412,7 @@ def api_get_build_logs(build_id): build = api.get_build(conda_store.db, build_id) if build is None: - return jsonify({"status": "error", "error": "build id does not exist"}), 404 + return jsonify({"status": "error", "message": "build id does not exist"}), 404 auth.authorize_request( f"{build.environment.namespace.name}/{build.environment.name}", @@ -468,7 +474,7 @@ def api_get_build_yaml(build_id): build = api.get_build(conda_store.db, build_id) if build is None: - return jsonify({"status": "error", "error": "build id does not exist"}), 404 + return jsonify({"status": "error", "message": "build id does not exist"}), 404 auth.authorize_request( f"{build.environment.namespace.name}/{build.environment.name}", diff --git a/conda-store-server/conda_store_server/server/views/ui.py b/conda-store-server/conda_store_server/server/views/ui.py index 724cfc426..815048db5 100644 --- a/conda-store-server/conda_store_server/server/views/ui.py +++ b/conda-store-server/conda_store_server/server/views/ui.py @@ -1,15 +1,13 @@ from flask import ( Blueprint, render_template, - request, redirect, Response, url_for, ) -import pydantic import yaml -from conda_store_server import api, schema +from conda_store_server import api from conda_store_server.server.utils import get_conda_store, get_auth, get_server from conda_store_server.server.auth import Permissions from conda_store_server.conda import conda_platform @@ -34,63 +32,6 @@ def ui_create_get_environment(): return render_template("create.html", **context) -@app_ui.route("/create/", methods=["POST"]) -def ui_create_post_environment(): - conda_store = get_conda_store() - auth = get_auth() - - orm_namespaces = auth.filter_namespaces( - api.list_namespaces(conda_store.db, show_soft_deleted=False) - ) - - context = { - "namespaces": orm_namespaces.all(), - "entity": auth.authenticate_request(), - } - - permissions = {Permissions.ENVIRONMENT_CREATE} - namespace_id = int(request.form.get("namespace", conda_store.default_namespace)) - namespace = api.get_namespace(conda_store.db, id=namespace_id) - if namespace is None: - permissions.add(Permissions.NAMESPACE_CREATE) - - try: - specification_text = request.form.get("specification") - specification = yaml.safe_load(specification_text) - specification = schema.CondaSpecification.parse_obj(specification) - except yaml.error.YAMLError: - return render_template( - "create.html", - specification=specification_text, - message="Unable to parse. Invalid YAML", - **context, - ) - except pydantic.ValidationError as e: - return render_template( - "create.html", - specification=specification_text, - message=str(e), - **context, - ) - - auth.authorize_request( - f"{namespace.name}/{specification.name}", - permissions, - require=True, - ) - - try: - api.post_specification(conda_store, specification.dict(), namespace.name) - return redirect(url_for("ui.ui_list_environments")) - except ValueError as e: - return render_template( - "create.html", - specification=specification_text, - message=str(e), - **context, - ) - - @app_ui.route("/", methods=["GET"]) def ui_list_environments(): conda_store = get_conda_store()