mirror of
https://github.com/Combodo/iTop.git
synced 2026-04-26 12:08:47 +02:00
N°6934 - Symfony 6.4 - upgrade Symfony bundles to 6.4 (#580)
* Update Symfony lib to version ~6.4.0 * Update code missing return type * Add an iTop general configuration entry to store application secret (Symfony mandatory parameter) * Use dependency injection in ExceptionListener & UserProvider classes
This commit is contained in:
@@ -0,0 +1,340 @@
|
||||
{% 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;
|
||||
}
|
||||
|
||||
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 %}
|
||||
Reference in New Issue
Block a user