[Feature] Modern UI roll out (#236)

This PR moves all the collection pages into the new UI in a rough push.
I did not put the same amount of care into these as into search, index, and predict.

## Major changes

- All modals are now migrated to a state based alpine.js implementation.
- jQuery is no longer present in the base layout; ajax is replace by native fetch api
- most of the pps.js is now obsolte (as I understand it; the code is not referenced any more @jebus  please double check)
- in-memory pagination for large result lists (set to 50; we can make that configurable later; performance degrades at around 1k) stukk a bit rough tracked in #235

## Minor things

- Sarch and index also use alpine now
- The loading spinner is now CSS animated (not sure if it currently gets correctly called)

## Not done

- Ihave not even cheked the admin pages. Not sure If these need migrations
- The temporary migration pages still use the old template. Not sure what is supposed to happen with those? @jebus

## What I did to test

- opend all pages in browse, and user ; plus all pages reachable from there.
- Interacted and tested the functionality of each modal superfically with exception of the API key modal (no functional test).

---
This PR is massive sorry for that; just did not want to push half-brokenn state.
@jebus @liambrydon I would be glad if you could click around and try to break it :)

Finally closes #133

Co-authored-by: Tim Lorsbach <tim@lorsba.ch>
Reviewed-on: enviPath/enviPy#236
Co-authored-by: Tobias O <tobias.olenyi@envipath.com>
Co-committed-by: Tobias O <tobias.olenyi@envipath.com>
This commit is contained in:
2025-11-26 23:16:44 +13:00
committed by jebus
parent 7f6f209b4a
commit 1a2c9bb543
110 changed files with 10784 additions and 9465 deletions

View File

@ -1,4 +1,4 @@
{% extends "framework.html" %}
{% extends "framework_modern.html" %}
{% load static %}
{% block content %}
<script src="https://d3js.org/d3.v7.min.js"></script>
@ -7,6 +7,7 @@
width: 100%;
height: 600px;
background: white;
position: relative;
}
#pwsvg {
@ -18,6 +19,7 @@
.link {
stroke: #999;
stroke-opacity: 0.6;
/* marker-end: url(#arrow); */
}
.link_no_arrow {
@ -63,15 +65,6 @@
stroke: red;
stroke-width: 3px;
}
.tooltip {
position: absolute;
background: lightgrey;
padding: 5px;
border-radius: 5px;
visibility: hidden;
opacity: 1;
}
</style>
<script src="{% static 'js/pw.js' %}"></script>
@ -90,141 +83,217 @@
{% include "modals/objects/generic_delete_modal.html" %}
{% endblock action_modals %}
<p></p>
<div id="pwcontent">
<div class="panel-group" id="pwAccordion">
<div class="panel panel-default">
<div
class="panel-heading"
id="headingPanel"
style="font-size:2rem;height: 46px"
>
{{ pathway.name|safe }}
</div>
</div>
<div
class="panel panel-default panel-heading list-group-item"
style="background-color:silver"
>
<h4 class="panel-title">
<a
id="vizLink"
data-toggle="collapse"
data-parent="#pwAccordion"
href="#viz"
>
Graphical Representation
</a>
</h4>
</div>
<div id="viz" class="panel-collapse in collapse">
<nav role="navigation" class="navbar navbar-default" style="margin: 0;">
<div class="navbar-header"></div>
<div id="editbarCollapse" class="navbar-collapse collapse">
<ul class="nav navbar-nav">
<li class="dropdown requiresWritePerm">
<a
href="#"
class="dropdown-toggle"
data-toggle="dropdown"
role="button"
aria-haspopup="true"
aria-expanded="false"
>
<span class="glyphicon glyphicon-edit"></span>
Edit
<span class="caret"></span
></a>
<ul id="editingList" class="dropdown-menu">
{% block actions %}
{% include "actions/objects/pathway.html" %}
{% endblock %}
</ul>
</li>
{% if pathway.setting.model.app_domain %}
<li class="dropdown">
<a
href="#"
class="dropdown-toggle"
data-toggle="dropdown"
role="button"
aria-haspopup="true"
aria-expanded="false"
>
<span class="glyphicon glyphicon-eye-open"></span>
View
<span class="caret"></span
></a>
<ul id="editingList" class="dropdown-menu">
<li>
<a class="button" id="app-domain-toggle-button">
<i
id="app-domain-toggle-button"
class="glyphicon glyphicon-eye-open"
></i>
App Domain View</a
>
</li>
</ul>
</li>
{% endif %}
</ul>
<ul class="nav navbar-nav navbar-right">
<li>
<a
role="button"
data-toggle="modal"
onclick="goFullscreen('vizdiv')"
>
<span class="glyphicon glyphicon-fullscreen"></span>
Fullscreen
</a>
</li>
<li>
{% if pathway.completed %}
<button
type="button"
class="btn btn-default navbar-btn"
data-toggle="popover"
id="status"
data-original-title=""
title=""
data-content="Pathway prediction complete."
>
<span class="glyphicon glyphicon-ok"></span>
</button>
{% elif pathway.failed %}
<button
type="button"
class="btn btn-default navbar-btn"
data-toggle="popover"
id="status"
data-original-title=""
title=""
data-content="Pathway prediction failed."
>
<span class="glyphicon glyphicon-remove"></span>
</button>
{% else %}
<button
type="button"
class="btn btn-default navbar-btn"
data-toggle="popover"
id="status"
data-original-title=""
title=""
data-content="Pathway prediction running."
>
<img height="20" src="{% static '/images/wait.gif' %}" />
</button>
{% endif %}
&nbsp;
</li>
<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 id="actionsButton" class="dropdown dropdown-end 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/pathway.html" %}
{% endblock %}
</ul>
</div>
</nav>
</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">
{% if meta.can_edit %}
<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>
Edit
</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>
{% endif %}
{% if pathway.setting.model.app_domain %}
<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-52 p-2"
>
<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>
</ul>
</div>
{% endif %}
</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">
{% if pathway.completed %}
<div class="tooltip tooltip-bottom absolute top-4 right-4 z-10">
<div class="tooltip-content">Pathway prediction complete.</div>
<div id="status" class="flex items-center">
<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>
</div>
</div>
{% elif pathway.failed %}
<div class="tooltip tooltip-bottom absolute top-4 right-4 z-10">
<div class="tooltip-content">Pathway prediction failed.</div>
<div id="status" class="flex items-center">
<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>
</div>
</div>
{% else %}
<div class="tooltip tooltip-bottom absolute top-4 right-4 z-10">
<div class="tooltip-content">Pathway prediction running.</div>
<div id="status" class="flex items-center">
<div
id="status-loading-spinner"
style="width: 20px; height: 20px;"
></div>
</div>
</div>
{% endif %}
<svg id="pwsvg">
<defs>
<marker
@ -266,184 +335,137 @@
</defs>
<g id="zoomable"></g>
</svg>
<div id="tooltip" class="tooltip"></div>
</div>
</div>
<!-- Description -->
<div
class="panel panel-default panel-heading list-group-item"
style="background-color:silver"
>
<h4 class="panel-title">
<a
id="DescriptionLink"
data-toggle="collapse"
data-parent="#pathwayAccordion"
href="#Description"
>Description</a
>
</h4>
</div>
<div id="Description" class="panel-collapse in collapse">
<div class="panel-body list-group-item" id="DescriptionContent">
{{ pathway.description | safe }}
<div id="tooltip" class="tooltip-content"></div>
</div>
</div>
</div>
{% if pathway.aliases %}
<!-- Aliases -->
<div
class="panel panel-default panel-heading list-group-item"
style="background-color:silver"
>
<h4 class="panel-title">
<a
id="pathway-aliases-link"
data-toggle="collapse"
data-parent="#pathway-detail"
href="#pathway-aliases"
>Aliases</a
>
</h4>
</div>
<div id="pathway-aliases" class="panel-collapse in collapse">
<div class="panel-body list-group-item">
<!-- 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 %}
<a class="list-group-item">{{ alias }}</a>
<li><a class="hover:bg-base-200">{{ alias }}</a></li>
{% endfor %}
</div>
</ul>
</div>
{% endif %}
</div>
{% endif %}
{% if pathway.scenarios.all %}
<div
class="panel panel-default panel-heading list-group-item"
style="background-color:silver"
>
<h4 class="panel-title">
<a
id="pathway-scenario-link"
data-toggle="collapse"
data-parent="#pathway-detail"
href="#pathway-scenario"
>Scenarios</a
>
</h4>
</div>
<div id="pathway-scenario" class="panel-collapse in collapse">
<div class="panel-body list-group-item">
{% 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 %}
<a class="list-group-item" href="{{ s.url }}"
>{{ s.name|safe }} <i>({{ s.package.name|safe }})</i></a
>
<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 %}
</div>
</ul>
</div>
{% endif %}
</div>
{% endif %}
{% if pathway.setting %}
<div
class="panel panel-default panel-heading list-group-item"
style="background-color:silver"
>
<h4 class="panel-title">
<a
id="pathwaySettingLink"
data-toggle="collapse"
data-parent="#pathwayAccordion"
href="#pathwaySetting"
>Setting</a
>
</h4>
</div>
<div id="pathwaySetting" class="panel-collapse collapse">
<div class="panel-body list-group-item" id="pathwaySettingContent">
<table class="table-bordered table-hover table">
<tr style="background-color: rgba(0, 0, 0, 0.08);">
<th scope="col" width="20%">Parameter</th>
<th scope="col" width="80%">Value</th>
</tr>
{% 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">
<div class="overflow-x-auto">
<table class="table-zebra table">
<thead>
<tr>
<th>Parameter</th>
<th>Value</th>
</tr>
</thead>
<tbody>
{% if pathway.setting.model %}
<tr>
<td width="20%">Model</td>
<td width="80%">
<table
width="100%"
class="table-bordered table-hover table"
>
<tbody>
<tr>
<td colspan="2">
<li class="list-group-item">
<a href="{{ pathway.setting.model.url }}">
{{ pathway.setting.model.name|safe }}
</a>
</li>
</td>
</tr>
<tr>
<th width="20%">Model Parameter</th>
<th width="80%">Parameter Value</th>
</tr>
<tr>
<td width="20%">Threshold</td>
<td width="80%">
{{ pathway.setting.model_threshold }}
</td>
</tr>
</tbody>
</table>
<td>Model</td>
<td>
<div class="space-y-2">
<div>
<a
href="{{ pathway.setting.model.url }}"
class="link link-primary"
>
{{ pathway.setting.model.name }}
</a>
</div>
<div class="overflow-x-auto">
<table class="table-xs table">
<thead>
<tr>
<th>Model Parameter</th>
<th>Parameter Value</th>
</tr>
</thead>
<tbody>
<tr>
<td>Threshold</td>
<td>{{ pathway.setting.model_threshold }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</td>
</tr>
{% endif %}
{% if pathway.setting.rule_packages.all %}
<tr>
<td width="20%">Rule Packages</td>
<td width="80%">
<table
width="100%"
class="table-bordered table-hover table"
>
<tbody>
<tr>
<td colspan="2">
{% for p in pathway.setting.rule_packages.all %}
<li class="list-group-item">
<a href="{{ p.url }}"> {{ p.name|safe }} </a>
</li>
{% endfor %}
</td>
</tr>
</tbody>
</table>
<td>Rule Packages</td>
<td>
<ul class="menu bg-base-100 rounded-box">
{% for p in pathway.setting.rule_packages.all %}
<li>
<a href="{{ p.url }}" class="hover:bg-base-200"
>{{ p.name }}</a
>
</li>
{% endfor %}
</ul>
</td>
</tr>
{% endif %}
<tr>
<td>
<p>Max Nodes</p>
</td>
<td>
<p>{{ pathway.setting.max_nodes }}</p>
</td>
<td>Max Nodes</td>
<td>{{ pathway.setting.max_nodes }}</td>
</tr>
<tr>
<td>
<p>Max Depth</p>
</td>
<td>
<p>{{ pathway.setting.max_depth }}</p>
</td>
<td>Max Depth</td>
<td>{{ pathway.setting.max_depth }}</td>
</tr>
</tbody>
</table>
</div>
</div>
{% endif %}
</div>
</div>
{% endif %}
</div>
{# prettier-ignore-start #}
{# FIXME: This is a hack to get the pathway data into the JavaScript code. #}
<script>
// Global switch for app domain view
var appDomainViewEnabled = false;
@ -461,15 +483,17 @@
return text.replace(/\[\s*(http[^\]|]+)\s*\|\s*([^\]]+)\s*\]/g, '<a target="parent" href="$1">$2</a>');
}
pathway = {{ pathway.d3_json | safe }};
$(function () {
var pathway = {{ pathway.d3_json | safe }};
$('#status').popover({
trigger: 'manual',
placement: 'bottom',
html: true
});
document.addEventListener('DOMContentLoaded', function() {
// Initialize loading spinner if pathway is running
if (pathway.status === 'running') {
const spinnerContainer = document.getElementById('status-loading-spinner');
if (spinnerContainer) {
showLoadingSpinner(spinnerContainer);
}
}
// If prediction is still running, regularly check status
if (pathway.status === 'running') {
@ -481,29 +505,32 @@
const data = await response.json();
if (data.modified > last_modified) {
var msg = 'Prediction '
var btn = '<button type="button" onclick="location.reload()" class="btn btn-primary" id="reloadBtn">Reload page</button>'
var msg = 'Prediction ';
var btn = '<button type="button" onclick="location.reload()" class="btn btn-primary btn-sm mt-2" id="reloadBtn">Reload page</button>';
if (data.status === "running") {
msg += 'is still running. But the Pathway was updated.<br>' + btn;
} else if (data.status === "completed") {
msg += 'is completed. Reload the page to see the updated Pathway.<br>' + btn;
} else if (data.status === "failed") {
msg += 'failed. Reload the page to see the current shape<br>' + btn;
msg += 'failed. Reload the page to see the current shape.<br>' + btn;
}
$('#status').attr(
'data-content', msg
).popover('show');
showStatusPopover(msg);
}
if (data.status === "completed" || data.status === "failed") {
$('#status img').remove();
const statusBtn = document.getElementById('status');
const tooltipContent = statusBtn.parentElement.querySelector('.tooltip-content');
const spinner = statusBtn.querySelector('#status-loading-spinner');
if (spinner) spinner.remove();
if (data.status === "completed") {
$('#status').append('<span class="glyphicon glyphicon-ok"></span>')
statusBtn.innerHTML = `<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>`;
tooltipContent.textContent = 'Pathway prediction complete.';
} else {
$('#status').append('<span class="glyphicon glyphicon-remove"></span>')
statusBtn.innerHTML = `<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>`;
tooltipContent.textContent = 'Pathway prediction failed.';
}
clearInterval(pollInterval);
}
@ -515,54 +542,68 @@
}
draw(pathway, 'vizdiv');
// TODO fix somewhere else...
var newDesc = transformReferences($('#DescriptionContent')[0].innerText);
$('#DescriptionContent').html(newDesc);
// Transform references in description
const descContent = document.getElementById('DescriptionContent');
if (descContent) {
const newDesc = transformReferences(descContent.innerText);
descContent.innerHTML = newDesc;
}
$('#app-domain-toggle-button').on('click', function () {
// glyphicon glyphicon-eye-close
// glyphicon glyphicon-eye-open
appDomainViewEnabled = !appDomainViewEnabled;
// 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) {
$('#app-domain-toggle-button > i').removeClass('glyphicon-eye-open');
$('#app-domain-toggle-button > i').addClass('glyphicon-eye-close');
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);
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);
});
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 {
$('#app-domain-toggle-button > i').removeClass('glyphicon-eye-close');
$('#app-domain-toggle-button > i').addClass('glyphicon-eye-open');
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);
});
}
})
});
} 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);
});
}
});
}
// 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>