adjusted migration
Some checks failed
API CI / api-tests (pull_request) Failing after 21s
CI / test (pull_request) Failing after 22s

Initial bayer app

Show Pack Classification

Adjusted docker compose to bayer specifics

Adjusted Dockerfile for Bayer

Adding secret flags to group, add secret pools to packages

Adjusted View for Package creation

Prep configs, added Package Create Modal

wip

More on PES

wip

wip
This commit is contained in:
Tim Lorsbach
2026-03-06 15:15:08 +01:00
parent 9d70db2ca2
commit 54056c654d
63 changed files with 2657 additions and 2871 deletions

View File

@ -0,0 +1,9 @@
{% if meta.can_edit %}
<button
type="button"
class="btn btn-primary btn-sm"
onclick="document.getElementById('new_pes_modal').showModal(); return false;"
>
New PES
</button>
{% endif %}

View File

@ -0,0 +1,175 @@
{% load static %}
<dialog
id="new_package_modal"
class="modal"
x-data="{
isSubmitting: false,
packageClassification: null,
reset() {
this.isSubmitting = false;
this.packageClassification = null;
},
setFormData(data) {
this.formData = data;
},
get isSecret() {
return this.packageClassification === '20';
},
submit(formId) {
const form = document.getElementById(formId);
// Remove previously injected inputs
form.querySelectorAll('.dynamic-param').forEach(el => el.remove());
// Add values from dynamic form into the html form
if (this.formData) {
Object.entries(this.formData).forEach(([key, value]) => {
const input = document.createElement('input');
input.type = 'hidden';
input.name = key;
input.value = value;
input.classList.add('dynamic-param');
form.appendChild(input);
});
}
if (form && form.checkValidity()) {
this.isSubmitting = true;
form.submit();
} else if (form) {
form.reportValidity();
}
}
}"
@close="reset()"
>
<div class="modal-box max-w-3xl">
<!-- Header -->
<h3 class="text-lg font-bold">New Package</h3>
<!-- Close button (X) -->
<form method="dialog">
<button
class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
:disabled="isSubmitting"
>
</button>
</form>
<!-- Body -->
<div class="py-4">
<form
id="new_package_form"
accept-charset="UTF-8"
action=""
method="post"
>
{% csrf_token %}
<!-- Name -->
<div class="form-control mb-3">
<label class="label" for="package-name">
<span class="label-text">Name</span>
</label>
<input
id="package-name"
class="input input-bordered w-full"
name="package-name"
placeholder="Name"
required
/>
</div>
<!-- Description -->
<div class="form-control mb-3">
<label class="label" for="package-description">
<span class="label-text">Description</span>
</label>
<input
id="package-description"
type="text"
class="input input-bordered w-full"
placeholder="Description..."
name="package-description"
/>
</div>
<!-- Classification Level -->
<div class="form-control mb-3">
<label class="label" for="package-classification">
<span class="label-text">Package Classification</span>
</label>
<select
id="package-classification"
name="package-classification"
class="select select-bordered w-full"
x-model="packageClassification"
required
>
<option value="null" disabled selected>Select Classification</option>
<option value="0">Internal</option>
<option value="10">Restricted</option>
<option value="20">Secret</option>
</select>
</div>
<!-- Secret Groups -->
<div class="form-control mb-3" x-show="isSecret" x-cloak>
<label class="label" for="package-data-pool">
<span class="label-text">Data Pool for SECRET Package</span>
</label>
<p>Only users with this role can be granted access to this package</p>
<select
id="package-data-pool"
name="package-data-pool"
class="select select-bordered w-full"
>
<option value="" disabled selected>Select Data Pool</option>
{% for obj in meta.secret_groups %}
<option value="{{ obj.url }}">{{ obj.name|safe }}</option>
{% endfor %}
</select>
</div>
</form>
</div>
<!-- Footer -->
<div class="modal-action">
<button
type="button"
class="btn"
onclick="this.closest('dialog').close()"
:disabled="isSubmitting"
>
Cancel
</button>
<button
type="button"
class="btn btn-primary"
@click="submit('new_package_form')"
:disabled="isSubmitting || !selectedType || loadingSchemas"
>
<span x-show="!isSubmitting">Submit</span>
<span
x-show="isSubmitting"
class="loading loading-spinner loading-sm"
></span>
<span x-show="isSubmitting">Creating...</span>
</button>
</div>
</div>
<!-- Backdrop -->
<form method="dialog" class="modal-backdrop">
<button :disabled="isSubmitting">close</button>
</form>
</dialog>

View File

@ -0,0 +1,174 @@
{% load static %}
<dialog
id="new_pes_modal"
class="modal"
x-data="{
isSubmitting: false,
pesLink: null,
pesVizHtml: '',
reset() {
this.isSubmitting = false;
},
get isPESSet() {
console.log(this.pesLink);
return this.pesLink !== null;
},
updatePesViz() {
if (!this.isPESSet) {
this.pesVizHtml = '';
return;
}
const img = new Image();
img.src = '{% url 'depict_pes' %}?pesLink=' + encodeURIComponent(this.pesLink);
img.style.width = '100%';
img.style.height = '100%';
img.style.objectFit = 'cover';
img.onload = () => {
this.pesVizHtml = img.outerHTML;
};
img.onerror = () => {
this.pesVizHtml = `
<div class='alert alert-error' role='alert'>
<h4 class='alert-heading'>Could not render PES!</h4>
<p>Could not render PES - Do you have access?</p>
</div>`;
};
},
submit(formId) {
const form = document.getElementById(formId);
// Remove previously injected inputs
form.querySelectorAll('.dynamic-param').forEach(el => el.remove());
// Add values from dynamic form into the html form
if (this.formData) {
Object.entries(this.formData).forEach(([key, value]) => {
const input = document.createElement('input');
input.type = 'hidden';
input.name = key;
input.value = value;
input.classList.add('dynamic-param');
form.appendChild(input);
});
}
if (form && form.checkValidity()) {
this.isSubmitting = true;
form.submit();
} else if (form) {
form.reportValidity();
}
}
}"
@close="reset()"
>
<div class="modal-box max-w-3xl">
<!-- Header -->
<h3 class="text-lg font-bold">New PES</h3>
<!-- Close button (X) -->
<form method="dialog">
<button
class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
:disabled="isSubmitting"
>
</button>
</form>
<!-- Body -->
<div class="py-4">
<form
id="new-pes-modal-form"
accept-charset="UTF-8"
action="{% url 'create pes' meta.current_package.uuid %}"
method="post"
>
{% csrf_token %}
<div class="form-control mb-3">
<label class="label" for="compound-name">
<span class="label-text">Name</span>
</label>
<input
id="compound-name"
class="input input-bordered w-full"
name="compound-name"
placeholder="Name"
required
/>
</div>
<div class="form-control mb-3">
<label class="label" for="compound-description">
<span class="label-text">Description</span>
</label>
<input
id="compound-description"
class="input input-bordered w-full"
name="compound-description"
placeholder="Description"
/>
</div>
<div class="form-control mb-3">
<label class="label" for="pes-link">
<span class="label-text">Link to PES</span>
</label>
<input
id="pes-link"
name="pes-link"
type="text"
class="input input-bordered w-full"
placeholder="Link to PES e.g. https://pesregapp-test.cropkey-np.ag/entities/PES-000126"
x-model="pesLink"
@input="updatePesViz()"
required
/>
</div>
<div id="pes-viz" class="mb-3" x-html="pesVizHtml"></div>
</form>
</div>
<!-- Footer -->
<div class="modal-action">
<button
type="button"
class="btn"
onclick="this.closest('dialog').close()"
:disabled="isSubmitting"
>
Close
</button>
<button
type="button"
class="btn btn-primary"
@click="submit('new-pes-modal-form')"
:disabled="isSubmitting"
>
<span x-show="!isSubmitting">Submit</span>
<span
x-show="isSubmitting"
class="loading loading-spinner loading-sm"
></span>
<span x-show="isSubmitting">Creating...</span>
</button>
</div>
</div>
<!-- Backdrop -->
<form method="dialog" class="modal-backdrop">
<button :disabled="isSubmitting">close</button>
</form>
</dialog>

View File

@ -0,0 +1,174 @@
{% load static %}
<dialog
id="add_pathway_pes_node_modal"
class="modal"
x-data="{
isSubmitting: false,
pesLink: null,
pesVizHtml: '',
reset() {
this.isSubmitting = false;
},
get isPESSet() {
console.log(this.pesLink);
return this.pesLink !== null;
},
updatePesViz() {
if (!this.isPESSet) {
this.pesVizHtml = '';
return;
}
const img = new Image();
img.src = '{% url 'depict_pes' %}?pesLink=' + encodeURIComponent(this.pesLink);
img.style.width = '100%';
img.style.height = '100%';
img.style.objectFit = 'cover';
img.onload = () => {
this.pesVizHtml = img.outerHTML;
};
img.onerror = () => {
this.pesVizHtml = `
<div class='alert alert-error' role='alert'>
<h4 class='alert-heading'>Could not render PES!</h4>
<p>Could not render PES - Do you have access?</p>
</div>`;
};
},
submit(formId) {
const form = document.getElementById(formId);
// Remove previously injected inputs
form.querySelectorAll('.dynamic-param').forEach(el => el.remove());
// Add values from dynamic form into the html form
if (this.formData) {
Object.entries(this.formData).forEach(([key, value]) => {
const input = document.createElement('input');
input.type = 'hidden';
input.name = key;
input.value = value;
input.classList.add('dynamic-param');
form.appendChild(input);
});
}
if (form && form.checkValidity()) {
this.isSubmitting = true;
form.submit();
} else if (form) {
form.reportValidity();
}
}
}"
@close="reset()"
>
<div class="modal-box max-w-3xl">
<!-- Header -->
<h3 class="text-lg font-bold">New PES</h3>
<!-- Close button (X) -->
<form method="dialog">
<button
class="btn btn-sm btn-circle btn-ghost absolute top-2 right-2"
:disabled="isSubmitting"
>
</button>
</form>
<!-- Body -->
<div class="py-4">
<form
id="new-pes-node-modal-form"
accept-charset="UTF-8"
action="{% url 'create pes node' current_object.package.uuid current_object.uuid %}"
method="post"
>
{% csrf_token %}
<div class="form-control mb-3">
<label class="label" for="compound-name">
<span class="label-text">Name</span>
</label>
<input
id="compound-name"
class="input input-bordered w-full"
name="compound-name"
placeholder="Name"
required
/>
</div>
<div class="form-control mb-3">
<label class="label" for="compound-description">
<span class="label-text">Description</span>
</label>
<input
id="compound-description"
class="input input-bordered w-full"
name="compound-description"
placeholder="Description"
/>
</div>
<div class="form-control mb-3">
<label class="label" for="pes-link">
<span class="label-text">Link to PES</span>
</label>
<input
id="pes-link"
name="pes-link"
type="text"
class="input input-bordered w-full"
placeholder="Link to PES e.g. https://pesregapp-test.cropkey-np.ag/entities/PES-000126"
x-model="pesLink"
@input="updatePesViz()"
required
/>
</div>
<div id="pes-viz" class="mb-3" x-html="pesVizHtml"></div>
</form>
</div>
<!-- Footer -->
<div class="modal-action">
<button
type="button"
class="btn"
onclick="this.closest('dialog').close()"
:disabled="isSubmitting"
>
Close
</button>
<button
type="button"
class="btn btn-primary"
@click="submit('new-pes-node-modal-form')"
:disabled="isSubmitting"
>
<span x-show="!isSubmitting">Submit</span>
<span
x-show="isSubmitting"
class="loading loading-spinner loading-sm"
></span>
<span x-show="isSubmitting">Creating...</span>
</button>
</div>
</div>
<!-- Backdrop -->
<form method="dialog" class="modal-backdrop">
<button :disabled="isSubmitting">close</button>
</form>
</dialog>

View File

@ -0,0 +1,19 @@
{% if compound_structure.pes_link %}
<!-- PES -->
<div class="collapse-arrow bg-base-200 collapse">
<input type="checkbox" checked />
<div class="collapse-title text-xl font-medium">Link to PES</div>
<div class="collapse-content">{{ compound_structure.pes_link }}</div>
</div>
<!-- Image Representation -->
<div class="collapse-arrow bg-base-200 collapse">
<input type="checkbox" checked />
<div class="collapse-title text-xl font-medium">PES Image Representation</div>
<div class="collapse-content">
<div class="flex justify-center">
<img src='{% url 'depict_pes' %}?pesLink={{ compound_structure.pes_link|urlencode }}'/>
</div>
</div>
</div>
{% endif %}

View File

@ -0,0 +1,19 @@
{% if compound.default_structure.pes_link %}
<!-- PES -->
<div class="collapse-arrow bg-base-200 collapse">
<input type="checkbox" checked />
<div class="collapse-title text-xl font-medium">Link to PES</div>
<div class="collapse-content">{{ compound.default_structure.pes_link }}</div>
</div>
<!-- Image Representation -->
<div class="collapse-arrow bg-base-200 collapse">
<input type="checkbox" checked />
<div class="collapse-title text-xl font-medium">PES Image Representation</div>
<div class="collapse-content">
<div class="flex justify-center">
<img src='{% url 'depict_pes' %}?pesLink={{ compound.default_structure.pes_link|urlencode }}'/>
</div>
</div>
</div>
{% endif %}

View File

@ -0,0 +1,19 @@
{% if node.default_node_label.pes_link %}
<!-- PES -->
<div class="collapse-arrow bg-base-200 collapse">
<input type="checkbox" checked />
<div class="collapse-title text-xl font-medium">Link to PES</div>
<div class="collapse-content">{{ node.default_node_label.pes_link }}</div>
</div>
<!-- Image Representation -->
<div class="collapse-arrow bg-base-200 collapse">
<input type="checkbox" checked />
<div class="collapse-title text-xl font-medium">PES Image Representation</div>
<div class="collapse-content">
<div class="flex justify-center">
<img src='{% url 'depict_pes' %}?pesLink={{ node.default_node_label.pes_link|urlencode }}'/>
</div>
</div>
</div>
{% endif %}

View File

@ -0,0 +1,97 @@
{% extends "framework_modern.html" %}
{% load static %}
{% block content %}
{% block action_modals %}
{% include "modals/objects/edit_package_modal.html" %}
{% include "modals/objects/edit_package_permissions_modal.html" %}
{% include "modals/objects/publish_package_modal.html" %}
{% include "modals/objects/set_license_modal.html" %}
{% include "modals/objects/export_package_modal.html" %}
{% include "modals/objects/generic_delete_modal.html" %}
{% endblock action_modals %}
<div class="space-y-2 p-4">
<!-- Header Section -->
<div class="card bg-base-100">
<div class="card-body">
<div class="flex items-center justify-between">
<h2 class="card-title text-2xl">{{ package.name }} {% if meta.url_contains_package and meta.current_package.get_classification_level_display == "Restricted" %}<img src="{% static 'images/restricted_mid.png' %}" width="100">{% elif meta.url_contains_package and meta.current_package.get_classification_level_display == "Secret" %}<img src="{% static 'images/secret_mid.png' %}" width="60">{% endif %}</h2>
<div id="actionsButton" class="dropdown dropdown-e nd hidden">
<div tabindex="0" role="button" class="btn btn-ghost btn-sm">
<svg
xmlns="http://www.w3.org/2000/svg"
width="16"
height="16"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
class="lucide lucide-wrench"
>
<path
d="M14.7 6.3a1 1 0 0 0 0 1.4l1.6 1.6a1 1 0 0 0 1.4 0l3.77-3.77a6 6 0 0 1-7.94 7.94l-6.91 6.91a2.12 2.12 0 0 1-3-3l6.91-6.91a6 6 0 0 1 7.94-7.94l-3.76 3.76z"
/>
</svg>
Actions
</div>
<ul
tabindex="-1"
class="dropdown-content menu bg-base-100 rounded-box z-50 w-52 p-2"
>
{% block actions %}
{% include "actions/objects/package.html" %}
{% endblock %}
</ul>
</div>
</div>
<p class="mt-2">{{ package.description|safe }}</p>
<ul class="menu bg-base-200 rounded-box mt-4 w-full">
<li>
<a href="{{ package.url }}/pathway" class="hover:bg-base-300"
>Pathways ({{ package.pathways.count }})</a
>
</li>
<li>
<a href="{{ package.url }}/rule" class="hover:bg-base-300"
>Rules ({{ package.rules.count }})</a
>
</li>
<li>
<a href="{{ package.url }}/compound" class="hover:bg-base-300"
>Compounds ({{ package.compounds.count }})</a
>
</li>
<li>
<a href="{{ package.url }}/reaction" class="hover:bg-base-300"
>Reactions ({{ package.reactions.count }})</a
>
</li>
<li>
<a href="{{ package.url }}/model" class="hover:bg-base-300"
>Models ({{ package.models.count }})</a
>
</li>
<li>
<a href="{{ package.url }}/scenario" class="hover:bg-base-300"
>Scenarios ({{ package.scenarios.count }})</a
>
</li>
</ul>
</div>
</div>
</div>
<script>
// Show actions button if there are actions
document.addEventListener("DOMContentLoaded", function () {
const actionsButton = document.getElementById("actionsButton");
const actionsList = actionsButton?.querySelector("ul");
if (actionsList && actionsList.children.length > 0) {
actionsButton?.classList.remove("hidden");
}
});
</script>
{% endblock content %}

View File

@ -0,0 +1,154 @@
{% extends "static/login_base.html" %}
{% load static %}
{% block title %}enviPath - Sign In{% endblock %}
{% block extra_styles %}
<style>
/* Tab styling */
.tab-content {
display: none;
}
.tab-content.active {
display: block;
}
input[type="radio"].tab-radio {
display: none;
}
.tab-label {
cursor: pointer;
padding: 0.75rem 1.5rem;
border-bottom: 2px solid transparent;
transition: all 0.3s ease;
}
.tab-label:hover {
background-color: rgba(0, 0, 0, 0.05);
}
input[type="radio"].tab-radio:checked + .tab-label {
border-bottom-color: #3b82f6;
font-weight: 600;
}
</style>
{% endblock %}
{% block content %}
<div>
<img src="{% static 'images/bayer-logo.svg' %}">
</div>
<div class="flex flex-col space-y-4 ...">
<div><p></p></div>
<div><p></p></div>
</div>
<!-- Tab Navigation -->
<div class="border-base-300 mb-6 border-b" hidden>
<div class="flex justify-start">
<input
type="radio"
name="auth-tab"
id="tab-sso"
class="tab-radio"
checked
/>
<label for="tab-sso" class="tab-label">SSO</label>
<input
type="radio"
name="auth-tab"
id="tab-signin"
class="tab-radio"
/>
<label for="tab-signin" class="tab-label">Local User</label>
</div>
</div>
<!-- SSO Tab -->
<div id="content-sso" class="tab-content active">
<button role="link" onclick="window.location.href='/entra/login'" name="sso" class="btn btn-primary w-full">
Login with Microsoft
</button>
</div>
<!-- Sign In Tab -->
<div id="content-signin" class="tab-content">
<form method="post" action="{% url 'login' %}" class="space-y-4">
{% csrf_token %}
<input type="hidden" name="login" value="true" />
<div class="form-control">
<label class="label" for="username">
<span class="label-text">Account</span>
</label>
<input
type="text"
id="username"
name="username"
placeholder="Username or Email"
class="input input-bordered w-full"
required
autocomplete="username"
/>
</div>
<div class="form-control">
<label class="label" for="passwordinput">
<span class="label-text">Password</span>
</label>
<input
type="password"
id="passwordinput"
name="password"
placeholder="••••••••"
class="input input-bordered w-full"
required
autocomplete="current-password"
/>
</div>
<div class="text-right">
<a href="{% url 'password_reset' %}" class="link link-primary text-sm"
>Forgot password?</a
>
</div>
<input type="hidden" name="next" value="{{ next }}" />
<button type="submit" name="signin" class="btn btn-primary w-full">
Sign In
</button>
</form>
</div>
{% endblock %}
{% block extra_scripts %}
<script>
// Tab switching functionality
document.querySelectorAll('input[name="auth-tab"]').forEach((radio) => {
radio.addEventListener("change", function () {
// Hide all content
document.querySelectorAll(".tab-content").forEach((content) => {
content.classList.remove("active");
});
// Show selected content
const contentId = "content-" + this.id.replace("tab-", "");
document.getElementById(contentId).classList.add("active");
});
});
// Check for hash in URL to auto-select tab
window.addEventListener("DOMContentLoaded", function () {
const hash = window.location.hash.substring(1); // Remove the # symbol
if (hash === "signup" || hash === "signin") {
const tabRadio = document.getElementById("tab-" + hash);
if (tabRadio) {
tabRadio.checked = true;
// Trigger change event to show correct content
tabRadio.dispatchEvent(new Event("change"));
}
}
});
</script>
{% endblock %}