Files
enviPy-bayer/templates/objects/pathway.html
Tim Lorsbach 54056c654d
Some checks failed
API CI / api-tests (pull_request) Failing after 21s
CI / test (pull_request) Failing after 22s
adjusted migration
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
2026-04-21 22:53:30 +02:00

632 lines
22 KiB
HTML

{% extends "framework_modern.html" %}
{% load static %}
{% load envipytags %}
{% block content %}
<script src="https://d3js.org/d3.v7.min.js"></script>
<style>
#vizdiv {
width: 100%;
height: 600px;
background: white;
position: relative;
}
#pwsvg {
width: 100%;
height: 100%;
color: red;
}
.link {
stroke: #999;
stroke-opacity: 0.6;
/* marker-end: url(#arrow); */
}
.link_no_arrow {
stroke: #999;
stroke-opacity: 0.6;
}
.node image {
cursor: pointer;
}
.node circle {
fill: lightblue;
stroke: steelblue;
stroke-width: 1.5px;
}
.inside_app_domain {
fill: green;
stroke: green;
stroke-width: 1.5px;
}
.outside_app_domain {
fill: red;
stroke: red;
stroke-width: 1.5px;
}
.passes_app_domain {
stroke: green;
stroke-width: 1.5px;
stroke-opacity: 0.6;
}
.fails_app_domain {
stroke: red;
stroke-width: 1.5px;
stroke-opacity: 0.6;
}
.has_timeseries {
stroke: #3b82f6;
stroke-width: 3px;
}
.highlighted {
stroke: red;
stroke-width: 3px;
}
</style>
<script src="{% static 'js/pw.js' %}"></script>
{% block action_modals %}
{% include "modals/objects/add_pathway_node_modal.html" %}
{% include "modals/objects/add_pathway_pes_node_modal.html" %}
{% include "modals/objects/add_pathway_edge_modal.html" %}
{% epdb_slot_templates "epdb.modals.objects.pathway.add" as add_templates %}
{% for tpl in add_templates %}
{% include tpl %}
{% endfor %}
{% include "modals/objects/download_pathway_csv_modal.html" %}
{% include "modals/objects/download_pathway_image_modal.html" %}
{% include "modals/objects/identify_missing_rules_modal.html" %}
{% include "modals/objects/generic_copy_object_modal.html" %}
{% include "modals/objects/edit_pathway_modal.html" %}
{% include "modals/objects/generic_set_aliases_modal.html" %}
{% include "modals/objects/generic_set_scenario_modal.html" %}
{% include "modals/objects/delete_pathway_node_modal.html" %}
{% include "modals/objects/delete_pathway_edge_modal.html" %}
{% include "modals/objects/generic_delete_modal.html" %}
{% include "modals/objects/engineer_pathway_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">{{ pathway.name }}</h2>
</div>
</div>
</div>
<!-- Graphical Representation -->
<div class="collapse-arrow bg-base-200 collapse">
<input type="checkbox" checked />
<div class="collapse-title text-xl font-medium">
Graphical Representation
</div>
<div class="collapse-content">
<div class="bg-base-100 mb-2 rounded-lg p-2">
<div class="navbar bg-base-100 rounded-lg">
<div class="flex-1">
<div class="dropdown">
<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-edit"
>
<path
d="M11 4H4a2 2 0 0 0-2 2v14a2 2 0 0 0 2 2h14a2 2 0 0 0 2-2v-7"
/>
<path
d="M18.5 2.5a2.121 2.121 0 0 1 3 3L12 15l-4 1 1-4 9.5-9.5z"
/>
</svg>
Actions
</div>
<ul
tabindex="0"
class="dropdown-content menu bg-base-100 rounded-box z-50 w-52 p-2"
>
{% include "actions/objects/pathway.html" %}
</ul>
</div>
<div class="dropdown">
<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-eye"
>
<path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z" />
<circle cx="12" cy="12" r="3" />
</svg>
View
</div>
<ul
tabindex="0"
class="dropdown-content menu bg-base-100 rounded-box z-50 w-60 p-2"
>
{% if pathway.setting.model.app_domain %}
<li>
<a id="app-domain-toggle-button" class="cursor-pointer">
<svg
id="app-domain-icon"
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-eye"
>
<path
d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z"
/>
<circle cx="12" cy="12" r="3" />
</svg>
App Domain View
</a>
</li>
{% endif %}
<li>
<a id="timeseries-toggle-button" class="cursor-pointer">
<svg
id="timeseries-icon"
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"
>
<polyline points="22 12 18 12 15 21 9 3 6 12 2 12" />
</svg>
OECD 301F View
</a>
</li>
<li>
<a id="pred-prop-toggle-button" class="cursor-pointer">
<svg
id="pred-prop-icon"
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"
>
<path
stroke-linecap="round"
stroke-linejoin="round"
d="M8.25 6.75h12M8.25 12h12m-12 5.25h12M3.75 6.75h.007v.008H3.75V6.75Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0ZM3.75 12h.007v.008H3.75V12Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm-.375 5.25h.007v.008H3.75v-.008Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z"
/>
</svg>
<svg
class="slash"
viewBox="0 0 100 30"
preserveAspectRatio="none"
aria-hidden="true"
>
<line x1="0" y1="30" x2="100" y2="0" />
</svg>
Show Predicted Properties
</a>
</li>
</ul>
</div>
</div>
<div class="flex-none gap-2">
<button
class="btn btn-ghost btn-sm"
onclick="goFullscreen('vizdiv')"
>
<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-maximize"
>
<path
d="M8 3H5a2 2 0 0 0-2 2v3m18 0V5a2 2 0 0 0-2-2h-3m0 18h3a2 2 0 0 0 2-2v-3M3 16v3a2 2 0 0 0 2 2h3"
/>
</svg>
Fullscreen
</button>
</div>
</div>
</div>
<div
id="vizdiv"
x-data="pathwayViewer({
status: '{{ pathway.status }}',
modified: '{{ pathway.modified|date:"Y-m-d H:i:s" }}',
statusUrl: '{{ pathway.url }}?status=true',
emptyDueToThreshold: '{{ pathway.empty_due_to_threshold }}'
})"
x-init="init()"
>
{% if pathway.predicted %}
<!-- Status Display -->
<div class="tooltip tooltip-left absolute top-4 right-4 z-10">
<div class="tooltip-content" x-text="statusTooltip"></div>
<div id="status" class="flex items-center">
<!-- Completed icon -->
<template x-if="status === 'completed'">
<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-check"
>
<path d="M20 6 9 17l-5-5" />
</svg>
</template>
<!-- Failed icon -->
<template x-if="status === 'failed'">
<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-x"
>
<path d="M18 6 6 18" />
<path d="M6 6l12 12" />
</svg>
</template>
<!-- Loading spinner -->
<div
x-show="status === 'running'"
style="width: 20px; height: 20px;"
>
{% include "components/loading-spinner.html" %}
</div>
</div>
</div>
{% endif %}
<!-- Update Notice -->
<div
x-show="showUpdateNotice"
x-cloak
class="alert alert-info absolute top-4 right-4 left-4 z-10"
>
<span x-html="updateMessage"></span>
<button @click="reloadPage()" class="btn btn-primary btn-sm mt-2">
Reload page
</button>
</div>
<!-- Empty due to Threshold notice -->
<div
x-show="showEmptyDueToThresholdNotice"
x-cloak
class="alert alert-info absolute right-4 bottom-4 left-4 z-10"
>
<span x-html="emptyDueToThresholdMessage"></span>
</div>
<svg id="pwsvg">
<defs>
<marker
id="arrow"
viewBox="0 0 10 10"
refX="43"
refY="5"
markerWidth="6"
markerHeight="6"
orient="auto-start-reverse"
markerUnits="userSpaceOnUse"
>
<path d="M 0 0 L 10 5 L 0 10 z" fill="#999" />
</marker>
<marker
id="doublearrow"
viewBox="0 0 20 30"
refX="53"
refY="5"
markerWidth="18"
markerHeight="18"
orient="auto-start-reverse"
markerUnits="userSpaceOnUse"
>
<!-- first triangle -->
<path d="M 0 0 L 10 5 L 0 10 z" fill="#999" />
<!-- second triangle -->
<path d="M 10 0 L 20 5 L 10 10 Z" fill="#999" />
</marker>
<marker
id="arrow_passes_app_domain"
viewBox="0 0 10 10"
refX="43"
refY="5"
markerWidth="6"
markerHeight="6"
orient="auto-start-reverse"
markerUnits="userSpaceOnUse"
>
<path d="M 0 0 L 10 5 L 0 10 z" fill="green" />
</marker>
<marker
id="arrow_fails_app_domain"
viewBox="0 0 10 10"
refX="43"
refY="5"
markerWidth="6"
markerHeight="6"
orient="auto-start-reverse"
markerUnits="userSpaceOnUse"
>
<path d="M 0 0 L 10 5 L 0 10 z" fill="red" />
</marker>
</defs>
<g id="zoomable"></g>
</svg>
<div id="tooltip" class="tooltip-content"></div>
</div>
</div>
</div>
<!-- Description -->
<div class="collapse-arrow bg-base-200 collapse">
<input type="checkbox" checked />
<div class="collapse-title text-xl font-medium">Description</div>
<div class="collapse-content">
<div id="DescriptionContent">{{ pathway.description | safe }}</div>
</div>
</div>
{% if pathway.aliases %}
<!-- Aliases -->
<div class="collapse-arrow bg-base-200 collapse">
<input type="checkbox" checked />
<div class="collapse-title text-xl font-medium">Aliases</div>
<div class="collapse-content">
<ul class="menu bg-base-100 rounded-box">
{% for alias in pathway.aliases %}
<li><a class="hover:bg-base-200">{{ alias }}</a></li>
{% endfor %}
</ul>
</div>
</div>
{% endif %}
{% if pathway.scenarios.all %}
<!-- Scenarios -->
<div class="collapse-arrow bg-base-200 collapse">
<input type="checkbox" checked />
<div class="collapse-title text-xl font-medium">Scenarios</div>
<div class="collapse-content">
<ul class="menu bg-base-100 rounded-box">
{% for s in pathway.scenarios.all %}
<li>
<a href="{{ s.url }}" class="hover:bg-base-200">
{{ s.name }}
<span class="text-sm opacity-70">({{ s.package.name }})</span>
</a>
</li>
{% endfor %}
</ul>
</div>
</div>
{% endif %}
{% if pathway.setting %}
<!-- Setting -->
<div class="collapse-arrow bg-base-200 collapse">
<input type="checkbox" />
<div class="collapse-title text-xl font-medium">Setting</div>
<div class="collapse-content">
{% with setting_to_render=pathway.setting can_be_default=False %}
{% include "objects/setting_template.html" %}
{% endwith %}
</div>
</div>
{% endif %}
</div>
{{ pathway.d3_json|json_script:"pathway" }}
<script>
// Global switch for app domain view
var appDomainViewEnabled = false;
// Global switch for timeseries view
var timeseriesViewEnabled = false;
// Predicted Property View
var predictedPropertyViewEnabled = false;
function goFullscreen(id) {
var element = document.getElementById(id);
if (element.mozRequestFullScreen) {
element.mozRequestFullScreen();
} else if (element.webkitRequestFullScreen) {
element.webkitRequestFullScreen();
}
}
function transformReferences(text) {
return text.replace(
/\[\s*(http[^\]|]+)\s*\|\s*([^\]]+)\s*\]/g,
'<a target="parent" href="$1">$2</a>',
);
}
var pathway = JSON.parse(document.getElementById("pathway").textContent);
document.addEventListener("DOMContentLoaded", function () {
draw(pathway, "vizdiv");
// Transform references in description
const descContent = document.getElementById("DescriptionContent");
if (descContent) {
const newDesc = transformReferences(descContent.innerText);
descContent.innerHTML = newDesc;
}
// App domain toggle
const appDomainBtn = document.getElementById("app-domain-toggle-button");
if (appDomainBtn) {
appDomainBtn.addEventListener("click", function () {
appDomainViewEnabled = !appDomainViewEnabled;
const icon = document.getElementById("app-domain-icon");
if (appDomainViewEnabled) {
// Change to eye-off icon
icon.innerHTML =
'<path d="M9.88 9.88a3 3 0 1 0 4.24 4.24"/><path d="M10.73 5.08A10.43 10.43 0 0 1 12 5c7 0 10 7 10 7a13.16 13.16 0 0 1-1.67 2.68"/><path d="M6.61 6.61A13.526 13.526 0 0 0 2 12s3 7 10 7a9.74 9.74 0 0 0 5.39-1.61"/><line x1="2" x2="22" y1="2" y2="22"/>';
nodes.forEach((x) => {
if (x.app_domain) {
if (x.app_domain.inside_app_domain) {
d3.select(x.el)
.select("circle")
.classed("inside_app_domain", true);
} else {
d3.select(x.el)
.select("circle")
.classed("outside_app_domain", true);
}
}
});
links.forEach((x) => {
if (x.app_domain) {
if (x.app_domain.passes_app_domain) {
d3.select(x.el).attr("marker-end", (d) =>
d.target.pseudo ? "" : "url(#arrow_passes_app_domain)",
);
d3.select(x.el).classed("passes_app_domain", true);
} else {
d3.select(x.el).attr("marker-end", (d) =>
d.target.pseudo ? "" : "url(#arrow_fails_app_domain)",
);
d3.select(x.el).classed("fails_app_domain", true);
}
}
});
} else {
// Change back to eye icon
icon.innerHTML =
'<path d="M2 12s3-7 10-7 10 7 10 7-3 7-10 7-10-7-10-7Z"/><circle cx="12" cy="12" r="3"/>';
nodes.forEach((x) => {
d3.select(x.el)
.select("circle")
.classed("inside_app_domain", false);
d3.select(x.el)
.select("circle")
.classed("outside_app_domain", false);
});
links.forEach((x) => {
d3.select(x.el).attr("marker-end", (d) =>
d.target.pseudo ? "" : "url(#arrow)",
);
d3.select(x.el).classed("passes_app_domain", false);
d3.select(x.el).classed("fails_app_domain", false);
});
}
});
}
// Timeseries toggle
const timeseriesBtn = document.getElementById("timeseries-toggle-button");
if (timeseriesBtn) {
timeseriesBtn.addEventListener("click", function () {
timeseriesViewEnabled = !timeseriesViewEnabled;
const icon = document.getElementById("timeseries-icon");
if (timeseriesViewEnabled) {
icon.innerHTML =
'<polyline points="22 12 18 12 15 21 9 3 6 12 2 12" style="stroke:#3b82f6"/>';
nodes.forEach((x) => {
if (x.timeseries) {
d3.select(x.el)
.select("circle")
.classed("has_timeseries", true);
}
});
} else {
icon.innerHTML =
'<polyline points="22 12 18 12 15 21 9 3 6 12 2 12"/>';
nodes.forEach((x) => {
d3.select(x.el).select("circle").classed("has_timeseries", false);
});
}
});
}
// Predicted Propertes toggle
const predPropBtn = document.getElementById("pred-prop-toggle-button");
if (predPropBtn) {
predPropBtn.addEventListener("click", function () {
predictedPropertyViewEnabled = !predictedPropertyViewEnabled;
const icon = document.getElementById("pred-prop-icon");
if (predictedPropertyViewEnabled) {
icon.innerHTML +=
'<svg class="slash" viewBox="0 0 100 30" preserveAspectRatio="none" aria-hidden="true"><line x1="0" y1="30" x2="100" y2="0"/></svg>';
} else {
icon.innerHTML =
'<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 6.75h12M8.25 12h12m-12 5.25h12M3.75 6.75h.007v.008H3.75V6.75Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0ZM3.75 12h.007v.008H3.75V12Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Zm-.375 5.25h.007v.008H3.75v-.008Zm.375 0a.375.375 0 1 1-.75 0 .375.375 0 0 1 .75 0Z" />';
}
});
}
// Show actions button if there are actions
const actionsButton = document.getElementById("actionsButton");
const actionsList = actionsButton?.querySelector("ul");
if (actionsList && actionsList.children.length > 0) {
actionsButton?.classList.remove("hidden");
}
});
</script>
{% endblock content %}