mirror of
https://github.com/Combodo/iTop.git
synced 2026-04-26 03:58:45 +02:00
342 lines
12 KiB
Twig
342 lines
12 KiB
Twig
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
|
||
|
||
{% block stylesheets %}
|
||
{{ parent() }}
|
||
<style>
|
||
dialog {
|
||
border: none;
|
||
border-radius: 6px;
|
||
box-shadow: var(--settings-modal-shadow);
|
||
max-width: 94%;
|
||
width: 1200px;
|
||
}
|
||
|
||
dialog::backdrop {
|
||
background: linear-gradient(
|
||
45deg,
|
||
rgb(18, 18, 20, 0.4),
|
||
rgb(17, 17, 20, 0.8)
|
||
);
|
||
}
|
||
|
||
dialog[open] {
|
||
animation: scale 0.3s ease normal;
|
||
}
|
||
|
||
dialog[open]::backdrop {
|
||
animation: backdrop 0.3s ease normal;
|
||
}
|
||
|
||
dialog.hide {
|
||
animation-direction: reverse;
|
||
}
|
||
|
||
dialog h2 {
|
||
margin-top: 0.2em
|
||
}
|
||
|
||
dialog i.cancel {
|
||
cursor: pointer;
|
||
padding: 0 5px;
|
||
float: right;
|
||
}
|
||
|
||
dialog table {
|
||
border: 1px solid #ccc;
|
||
border-collapse: collapse;
|
||
margin: 0 0 1em 0;
|
||
margin-bottom: 1em;
|
||
padding: 0;
|
||
table-layout: fixed;
|
||
}
|
||
|
||
dialog table tr {
|
||
background-color: #f8f8f8;
|
||
border: 1px solid #ddd;
|
||
padding: .35em;
|
||
}
|
||
|
||
dialog table th,
|
||
dialog table td {
|
||
padding: .625em;
|
||
text-align: center;
|
||
word-wrap: break-word;
|
||
}
|
||
|
||
dialog table th {
|
||
font-size: .85em;
|
||
letter-spacing: .1em;
|
||
text-transform: uppercase;
|
||
}
|
||
|
||
dialog menu {
|
||
padding: 0;
|
||
margin: 0;
|
||
display: flex;
|
||
align-items: center;
|
||
flex-direction: row;
|
||
vertical-align: middle;
|
||
justify-content: center;
|
||
}
|
||
|
||
dialog menu small {
|
||
margin-right: auto;
|
||
}
|
||
dialog menu small i {
|
||
margin-right: 3px;
|
||
}
|
||
|
||
@keyframes scale {
|
||
from { transform: scale(0); }
|
||
to { transform: scale(1); }
|
||
}
|
||
|
||
@keyframes backdrop {
|
||
from { opacity: 0; }
|
||
to { opacity: 1; }
|
||
}
|
||
</style>
|
||
{% endblock %}
|
||
|
||
{% block toolbar %}
|
||
{% if collector.callsCount > 0 %}
|
||
{% set icon %}
|
||
{{ source('@WebProfiler/Icon/workflow.svg') }}
|
||
<span class="sf-toolbar-value">{{ collector.callsCount }}</span>
|
||
{% endset %}
|
||
{% set text %}
|
||
<div class="sf-toolbar-info-piece">
|
||
<b>Workflow Calls</b>
|
||
<span>{{ collector.callsCount }}</span>
|
||
</div>
|
||
{% endset %}
|
||
|
||
{{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { link: profiler_url }) }}
|
||
{% endif %}
|
||
{% endblock %}
|
||
|
||
{% block menu %}
|
||
<span class="label {{ collector.workflows|length == 0 ? 'disabled' }}">
|
||
<span class="icon">
|
||
{{ source('@WebProfiler/Icon/workflow.svg') }}
|
||
</span>
|
||
<strong>Workflow</strong>
|
||
</span>
|
||
{% endblock %}
|
||
|
||
{% block panel %}
|
||
<h2>Workflow</h2>
|
||
|
||
{% if collector.workflows|length == 0 %}
|
||
<div class="empty empty-panel">
|
||
<p>There are no workflows configured.</p>
|
||
</div>
|
||
{% else %}
|
||
<script type="module">
|
||
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
|
||
mermaid.initialize({
|
||
flowchart: { useMaxWidth: false },
|
||
securityLevel: 'loose',
|
||
});
|
||
|
||
{% for name, data in collector.workflows %}
|
||
window.showNodeDetails{{ collector.hash(name) }} = function (node) {
|
||
const map = {{ data.listeners|json_encode|raw }};
|
||
showNodeDetails(node, map);
|
||
};
|
||
{% endfor %}
|
||
|
||
const showNodeDetails = function (node, map) {
|
||
const dialog = document.getElementById('detailsDialog');
|
||
|
||
dialog.querySelector('tbody').innerHTML = '';
|
||
for (const [eventName, listeners] of Object.entries(map[node])) {
|
||
listeners.forEach(listener => {
|
||
const row = document.createElement('tr');
|
||
|
||
const eventNameCode = document.createElement('code');
|
||
eventNameCode.textContent = eventName;
|
||
|
||
const eventNameCell = document.createElement('td');
|
||
eventNameCell.appendChild(eventNameCode);
|
||
row.appendChild(eventNameCell);
|
||
|
||
const listenerDetailsCell = document.createElement('td');
|
||
row.appendChild(listenerDetailsCell);
|
||
|
||
let listenerDetails;
|
||
const listenerDetailsCode = document.createElement('code');
|
||
listenerDetailsCode.textContent = listener.title;
|
||
if (listener.file) {
|
||
const link = document.createElement('a');
|
||
link.href = listener.file;
|
||
link.appendChild(listenerDetailsCode);
|
||
listenerDetails = link;
|
||
} else {
|
||
listenerDetails = listenerDetailsCode;
|
||
}
|
||
listenerDetailsCell.appendChild(listenerDetails);
|
||
|
||
if (typeof listener.guardExpressions === 'object') {
|
||
listenerDetailsCell.appendChild(document.createElement('br'));
|
||
|
||
const guardExpressionsWrapper = document.createElement('span');
|
||
guardExpressionsWrapper.appendChild(document.createTextNode('guard expressions: '));
|
||
|
||
listener.guardExpressions.forEach((expression, index) => {
|
||
if (index > 0) {
|
||
guardExpressionsWrapper.appendChild(document.createTextNode(', '));
|
||
}
|
||
|
||
const expressionCode = document.createElement('code');
|
||
expressionCode.textContent = expression;
|
||
guardExpressionsWrapper.appendChild(expressionCode);
|
||
});
|
||
|
||
listenerDetailsCell.appendChild(guardExpressionsWrapper);
|
||
}
|
||
|
||
dialog.querySelector('tbody').appendChild(row);
|
||
});
|
||
};
|
||
|
||
if (dialog.dataset.processed) {
|
||
dialog.showModal();
|
||
return;
|
||
}
|
||
|
||
dialog.addEventListener('click', (e) => {
|
||
const rect = dialog.getBoundingClientRect();
|
||
|
||
const inDialog =
|
||
rect.top <= e.clientY &&
|
||
e.clientY <= rect.top + rect.height &&
|
||
rect.left <= e.clientX &&
|
||
e.clientX <= rect.left + rect.width;
|
||
|
||
!inDialog && dialog.close();
|
||
});
|
||
|
||
dialog.querySelectorAll('.cancel').forEach(elt => {
|
||
elt.addEventListener('click', () => dialog.close());
|
||
});
|
||
|
||
dialog.showModal();
|
||
|
||
dialog.dataset.processed = true;
|
||
};
|
||
// We do not load all mermaid diagrams at once, but only when the tab is opened
|
||
// This is because mermaid diagrams are in a tab, and cannot be renderer with a
|
||
// "good size" if they are not visible
|
||
document.addEventListener('DOMContentLoaded', () => {
|
||
document.querySelectorAll('.tab').forEach((el) => {
|
||
const observer = new MutationObserver(() => {
|
||
if (!el.classList.contains('block')) {
|
||
return;
|
||
}
|
||
const mEl = el.querySelector('.sf-mermaid');
|
||
if (mEl.dataset.processed) {
|
||
return;
|
||
}
|
||
mermaid.run({
|
||
nodes: [mEl],
|
||
});
|
||
});
|
||
observer.observe(el, { attributeFilter: ['class'] });
|
||
});
|
||
});
|
||
</script>
|
||
|
||
<div class="sf-tabs js-tabs">
|
||
{% for name, data in collector.workflows %}
|
||
<div class="tab">
|
||
<h2 class="tab-title">{{ name }}{% if data.calls|length %} ({{ data.calls|length }}){% endif %}</h2>
|
||
|
||
<div class="tab-content">
|
||
<h3>Definition</h3>
|
||
<pre class="sf-mermaid">
|
||
{{ data.dump|raw }}
|
||
{% for nodeId, events in data.listeners %}
|
||
click {{ nodeId }} showNodeDetails{{ collector.hash(name) }}
|
||
{% endfor %}
|
||
</pre>
|
||
|
||
<h3>Calls</h3>
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>#</th>
|
||
<th>Call</th>
|
||
<th>Args</th>
|
||
<th>Return</th>
|
||
<th>Exception</th>
|
||
<th>Duration</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
{% for call in data.calls %}
|
||
<tr>
|
||
<td class="font-normal text-small text-muted nowrap">{{ loop.index }}</td>
|
||
<td>
|
||
<code>{{ call.method }}()</code>
|
||
{% if call.previousMarking ?? null %}
|
||
<hr />
|
||
Previous marking:
|
||
{{ profiler_dump(call.previousMarking) }}
|
||
{% endif %}
|
||
</td>
|
||
<td>
|
||
{{ profiler_dump(call.args) }}
|
||
</td>
|
||
<td>
|
||
{% if call.return is defined %}
|
||
{% if call.return is same as true %}
|
||
<code>true</code>
|
||
{% elseif call.return is same as false %}
|
||
<code>false</code>
|
||
{% else %}
|
||
{{ profiler_dump(call.return) }}
|
||
{% endif %}
|
||
{% endif %}
|
||
</td>
|
||
<td>
|
||
{% if call.exception is defined %}
|
||
{{ profiler_dump(call.exception) }}
|
||
{% endif %}
|
||
</td>
|
||
<td>
|
||
{{ call.duration }}ms
|
||
</td>
|
||
</tr>
|
||
{% endfor %}
|
||
</tbody>
|
||
</table>
|
||
</div>
|
||
</div>
|
||
{% endfor %}
|
||
</div>
|
||
{% endif %}
|
||
|
||
<dialog id="detailsDialog">
|
||
<h2>
|
||
Event listeners
|
||
<i class="cancel">×</i>
|
||
</h2>
|
||
|
||
<table>
|
||
<thead>
|
||
<tr>
|
||
<th>event</th>
|
||
<th>listener</th>
|
||
</tr>
|
||
</thead>
|
||
<tbody>
|
||
</tbody>
|
||
</table>
|
||
<menu>
|
||
<small><i>⌨</i> <kbd>esc</kbd></small>
|
||
<button class="btn btn-sm cancel">Close</button>
|
||
</menu>
|
||
</dialog>
|
||
{% endblock %}
|