mirror of
https://github.com/Combodo/iTop.git
synced 2026-04-30 14:08:46 +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:
@@ -2,7 +2,7 @@
|
||||
|
||||
{% block toolbar %}
|
||||
{% set icon %}
|
||||
{{ include('@WebProfiler/Icon/ajax.svg') }}
|
||||
{{ source('@WebProfiler/Icon/ajax.svg') }}
|
||||
<span class="sf-toolbar-value sf-toolbar-ajax-request-counter">0</span>
|
||||
{% endset %}
|
||||
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{% block toolbar %}
|
||||
{% if collector.totals.calls > 0 %}
|
||||
{% set icon %}
|
||||
{{ include('@WebProfiler/Icon/cache.svg') }}
|
||||
{{ source('@WebProfiler/Icon/cache.svg') }}
|
||||
<span class="sf-toolbar-value">{{ collector.totals.calls }}</span>
|
||||
<span class="sf-toolbar-info-piece-additional-detail">
|
||||
<span class="sf-toolbar-label">in</span>
|
||||
@@ -36,92 +36,47 @@
|
||||
|
||||
{% block menu %}
|
||||
<span class="label {{ collector.totals.calls == 0 ? 'disabled' }}">
|
||||
<span class="icon">
|
||||
{{ include('@WebProfiler/Icon/cache.svg') }}
|
||||
<span class="icon">
|
||||
{{ source('@WebProfiler/Icon/cache.svg') }}
|
||||
</span>
|
||||
<strong>Cache</strong>
|
||||
</span>
|
||||
<strong>Cache</strong>
|
||||
</span>
|
||||
{% endblock %}
|
||||
|
||||
{% block panel %}
|
||||
<h2>Cache</h2>
|
||||
|
||||
{% if collector.totals.calls == 0 %}
|
||||
<div class="empty">
|
||||
<div class="empty empty-panel">
|
||||
<p>No cache calls were made.</p>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="metrics">
|
||||
<div class="metric">
|
||||
<span class="value">{{ collector.totals.calls }}</span>
|
||||
<span class="label">Total calls</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<span class="value">{{ '%0.2f'|format(collector.totals.time * 1000) }} <span class="unit">ms</span></span>
|
||||
<span class="label">Total time</span>
|
||||
</div>
|
||||
<div class="metric-divider"></div>
|
||||
<div class="metric">
|
||||
<span class="value">{{ collector.totals.reads }}</span>
|
||||
<span class="label">Total reads</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<span class="value">{{ collector.totals.writes }}</span>
|
||||
<span class="label">Total writes</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<span class="value">{{ collector.totals.deletes }}</span>
|
||||
<span class="label">Total deletes</span>
|
||||
</div>
|
||||
<div class="metric-divider"></div>
|
||||
<div class="metric">
|
||||
<span class="value">{{ collector.totals.hits }}</span>
|
||||
<span class="label">Total hits</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<span class="value">{{ collector.totals.misses }}</span>
|
||||
<span class="label">Total misses</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<span class="value">
|
||||
{{ collector.totals.hit_read_ratio ?? 0 }} <span class="unit">%</span>
|
||||
</span>
|
||||
<span class="label">Hits/reads</span>
|
||||
</div>
|
||||
</div>
|
||||
{{ _self.render_metrics(collector.totals, true) }}
|
||||
|
||||
<h2>Pools</h2>
|
||||
<div class="sf-tabs">
|
||||
{% for name, calls in collector.calls %}
|
||||
{# the empty merge is needed to turn the iterator into an array #}
|
||||
{% set cache_pools_with_calls = collector.calls|filter(calls => calls|length > 0)|merge([]) %}
|
||||
{% for name, calls in cache_pools_with_calls %}
|
||||
<div class="tab {{ calls|length == 0 ? 'disabled' }}">
|
||||
<h3 class="tab-title">{{ name }} <span class="badge">{{ collector.statistics[name].calls }}</span></h3>
|
||||
|
||||
<div class="tab-content">
|
||||
<h4>Adapter</h4>
|
||||
<div class="card">
|
||||
{% if collector.adapters[name] is defined %}
|
||||
<code>{{ collector.adapters[name] }}</code>
|
||||
{% else %}
|
||||
<span class="text-muted">Unable to get the adapter class.</span>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% if calls|length == 0 %}
|
||||
<div class="empty">
|
||||
<p>No calls were made for {{ name }} pool.</p>
|
||||
</div>
|
||||
{% else %}
|
||||
<h4>Metrics</h4>
|
||||
<div class="metrics">
|
||||
{% for key, value in collector.statistics[name] %}
|
||||
<div class="metric">
|
||||
<span class="value">
|
||||
{% if key == 'time' %}
|
||||
{{ '%0.2f'|format(1000 * value) }} <span class="unit">ms</span>
|
||||
{% elseif key == 'hit_read_ratio' %}
|
||||
{{ value ?? 0 }} <span class="unit">%</span>
|
||||
{% else %}
|
||||
{{ value }}
|
||||
{% endif %}
|
||||
</span>
|
||||
<span class="label">{{ key == 'hit_read_ratio' ? 'Hits/reads' : key|capitalize }}</span>
|
||||
</div>
|
||||
{% if key == 'time' or key == 'deletes' %}
|
||||
<div class="metric-divider"></div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{{ _self.render_metrics(collector.statistics[name]) }}
|
||||
|
||||
<h4>Calls</h4>
|
||||
<table>
|
||||
@@ -147,7 +102,77 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if loop.last %}
|
||||
<div class="tab">
|
||||
<h3 class="tab-title">Pools without calls <span class="badge">{{ collector.calls|filter(calls => 0 == calls|length)|length }}</span></h3>
|
||||
|
||||
<div class="tab-content">
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Cache pools that received no calls</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for cache_pool in collector.calls|filter(calls => 0 == calls|length)|keys|sort %}
|
||||
<tr><td>{{ cache_pool }}</td></tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% macro render_metrics(pool, is_total = false) %}
|
||||
<div class="metrics">
|
||||
<div class="metric">
|
||||
<span class="value">{{ pool.calls }}</span>
|
||||
<span class="label">{{ is_total ? 'Total calls' : 'Calls' }}</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<span class="value">{{ '%0.2f'|format(pool.time * 1000) }} <span class="unit">ms</span></span>
|
||||
<span class="label">{{ is_total ? 'Total time' : 'Time' }}</span>
|
||||
</div>
|
||||
|
||||
<div class="metric-divider"></div>
|
||||
|
||||
<div class="metric-group">
|
||||
<div class="metric">
|
||||
<span class="value">{{ pool.reads }}</span>
|
||||
<span class="label">{{ is_total ? 'Total reads' : 'Reads' }}</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<span class="value">{{ pool.writes }}</span>
|
||||
<span class="label">{{ is_total ? 'Total writes' : 'Writes' }}</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<span class="value">{{ pool.deletes }}</span>
|
||||
<span class="label">{{ is_total ? 'Total deletes' : 'Deletes' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="metric-divider"></div>
|
||||
|
||||
<div class="metric-group">
|
||||
<div class="metric">
|
||||
<span class="value">{{ pool.hits }}</span>
|
||||
<span class="label">{{ is_total ? 'Total hits' : 'Hits' }}</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<span class="value">{{ pool.misses }}</span>
|
||||
<span class="label">{{ is_total ? 'Total misses' : 'Misses' }}</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<span class="value">
|
||||
{{ pool.hit_read_ratio ?? 0 }} <span class="unit">%</span>
|
||||
</span>
|
||||
<span class="label">Hits/reads</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
@@ -0,0 +1,249 @@
|
||||
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
|
||||
|
||||
{% block menu %}
|
||||
<span class="label">
|
||||
<span class="icon">{{ source('@WebProfiler/Icon/command.svg') }}</span>
|
||||
<strong>Console Command</strong>
|
||||
</span>
|
||||
{% endblock %}
|
||||
|
||||
{% block panel %}
|
||||
<h2>
|
||||
{% set command = collector.command %}
|
||||
<a href="{{ command.file|file_link(command.line) }}">
|
||||
{% if command.executor is defined %}
|
||||
{{ command.executor|abbr_method }}
|
||||
{% else %}
|
||||
{{ command.class|abbr_class }}
|
||||
{% endif %}
|
||||
</a>
|
||||
</h2>
|
||||
|
||||
<div class="sf-tabs">
|
||||
<div class="tab">
|
||||
<h3 class="tab-title">Command</h3>
|
||||
|
||||
<div class="tab-content">
|
||||
<div class="metrics">
|
||||
<div class="metric">
|
||||
<span class="value">{{ collector.duration }}</span>
|
||||
<span class="label">Duration</span>
|
||||
</div>
|
||||
|
||||
<div class="metric">
|
||||
<span class="value">{{ collector.maxMemoryUsage }}</span>
|
||||
<span class="label">Peak Memory Usage</span>
|
||||
</div>
|
||||
|
||||
<div class="metric">
|
||||
<span class="value">{{ collector.verbosityLevel }}</span>
|
||||
<span class="label">Verbosity Level</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="metrics">
|
||||
<div class="metric">
|
||||
<span class="value">{{ source('@WebProfiler/Icon/' ~ (collector.signalable is not empty ? 'yes' : 'no') ~ '.svg') }}</span>
|
||||
<span class="label">Signalable</span>
|
||||
</div>
|
||||
|
||||
<div class="metric">
|
||||
<span class="value">{{ source('@WebProfiler/Icon/' ~ (collector.interactive ? 'yes' : 'no') ~ '.svg') }}</span>
|
||||
<span class="label">Interactive</span>
|
||||
</div>
|
||||
|
||||
<div class="metric">
|
||||
<span class="value">{{ source('@WebProfiler/Icon/' ~ (collector.validateInput ? 'yes' : 'no') ~ '.svg') }}</span>
|
||||
<span class="label">Validate Input</span>
|
||||
</div>
|
||||
|
||||
<div class="metric">
|
||||
<span class="value">{{ source('@WebProfiler/Icon/' ~ (collector.enabled ? 'yes' : 'no') ~ '.svg') }}</span>
|
||||
<span class="label">Enabled</span>
|
||||
</div>
|
||||
|
||||
<div class="metric">
|
||||
<span class="value">{{ source('@WebProfiler/Icon/' ~ (collector.visible ? 'yes' : 'no') ~ '.svg') }}</span>
|
||||
<span class="label">Visible</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>Arguments</h3>
|
||||
|
||||
{% if collector.arguments is empty %}
|
||||
<div class="empty">
|
||||
<p>No arguments were set</p>
|
||||
</div>
|
||||
{% else %}
|
||||
{{ include('@WebProfiler/Profiler/table.html.twig', { data: collector.arguments, labels: ['Argument', 'Value'], maxDepth: 2 }, with_context=false) }}
|
||||
{% endif %}
|
||||
|
||||
<h3>Options</h3>
|
||||
|
||||
{% if collector.options is empty %}
|
||||
<div class="empty">
|
||||
<p>No options were set</p>
|
||||
</div>
|
||||
{% else %}
|
||||
{{ include('@WebProfiler/Profiler/table.html.twig', { data: collector.options, labels: ['Option', 'Value'], maxDepth: 2 }, with_context=false) }}
|
||||
{% endif %}
|
||||
|
||||
{% if collector.interactive %}
|
||||
<h3>Interactive Inputs</h3>
|
||||
|
||||
<p class="help">
|
||||
The values which have been set interactively.
|
||||
</p>
|
||||
|
||||
{% if collector.interactiveInputs is empty %}
|
||||
<div class="empty">
|
||||
<p>No inputs were set</p>
|
||||
</div>
|
||||
{% else %}
|
||||
{{ include('@WebProfiler/Profiler/table.html.twig', { data: collector.interactiveInputs, labels: ['Input', 'Value'], maxDepth: 2 }, with_context=false) }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<h3>Application inputs</h3>
|
||||
|
||||
{% if collector.applicationInputs is empty %}
|
||||
<div class="empty">
|
||||
<p>No application inputs are set</p>
|
||||
</div>
|
||||
{% else %}
|
||||
{{ include('@WebProfiler/Profiler/table.html.twig', { data: collector.applicationInputs, labels: ['Input', 'Value'], maxDepth: 2 }, with_context=false) }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab">
|
||||
<h3 class="tab-title">Input / Output</h3>
|
||||
|
||||
<div class="tab-content">
|
||||
<table>
|
||||
<tr>
|
||||
<td class="font-normal">Input</td>
|
||||
<td class="font-normal">{{ profiler_dump(collector.input) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="font-normal">Output</td>
|
||||
<td class="font-normal">{{ profiler_dump(collector.output) }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab">
|
||||
<h3 class="tab-title">Helper Set</h3>
|
||||
|
||||
<div class="tab-content">
|
||||
{% if collector.helperSet is empty %}
|
||||
<div class="empty">
|
||||
<p>No helpers</p>
|
||||
</div>
|
||||
{% else %}
|
||||
<table class="{{ class|default('') }}">
|
||||
<thead>
|
||||
<tr>
|
||||
<th scope="col">Helpers</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for helper in collector.helperSet|sort %}
|
||||
<tr>
|
||||
<td>{{ profiler_dump(helper) }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab">
|
||||
{% set request_collector = profile.collectors.request %}
|
||||
<h3 class="tab-title">Server Parameters</h3>
|
||||
<div class="tab-content">
|
||||
<h3>Server Parameters</h3>
|
||||
<h4>Defined in .env</h4>
|
||||
{{ include('@WebProfiler/Profiler/bag.html.twig', { bag: request_collector.dotenvvars }, with_context = false) }}
|
||||
|
||||
<h4>Defined as regular env variables</h4>
|
||||
{% set requestserver = [] %}
|
||||
{% for key, value in request_collector.requestserver|filter((_, key) => key not in request_collector.dotenvvars.keys) %}
|
||||
{% set requestserver = requestserver|merge({(key): value}) %}
|
||||
{% endfor %}
|
||||
{{ include('@WebProfiler/Profiler/table.html.twig', { data: requestserver }, with_context = false) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if collector.signalable is not empty %}
|
||||
<div class="tab">
|
||||
<h3 class="tab-title">Signals</h3>
|
||||
|
||||
<div class="tab-content">
|
||||
<h3>Subscribed signals</h3>
|
||||
{{ collector.signalable|join(', ') }}
|
||||
|
||||
<h3>Handled signals</h3>
|
||||
{% if collector.handledSignals is empty %}
|
||||
<div class="empty">
|
||||
<p>No signals handled</p>
|
||||
</div>
|
||||
{% else %}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Signal</th>
|
||||
<th>Times handled</th>
|
||||
<th>Total execution time</th>
|
||||
<th>Memory peak</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for signal, data in collector.handledSignals %}
|
||||
<tr>
|
||||
<td>{{ signal }}</td>
|
||||
<td>{{ data.handled }}</td>
|
||||
<td>{{ data.duration }} ms</td>
|
||||
<td>{{ data.memory }} MiB</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if profile.parent %}
|
||||
<div class="tab">
|
||||
<h3 class="tab-title">Parent Command</h3>
|
||||
|
||||
<div class="tab-content">
|
||||
<h3>
|
||||
<a href="{{ path('_profiler', { token: profile.parent.token }) }}">Return to parent command</a>
|
||||
<small>(token = {{ profile.parent.token }})</small>
|
||||
</h3>
|
||||
|
||||
{{ profile.parent.url }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
{% if profile.children|length %}
|
||||
<div class="tab">
|
||||
<h3 class="tab-title">Sub Commands <span class="badge">{{ profile.children|length }}</span></h3>
|
||||
|
||||
<div class="tab-content">
|
||||
{% for child in profile.children %}
|
||||
<h3>
|
||||
{{ child.url }}
|
||||
<small>(token = <a href="{{ path('_profiler', { token: child.token }) }}">{{ child.token }}</a>)</small>
|
||||
</h3>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
@@ -1,5 +1,51 @@
|
||||
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
|
||||
|
||||
{% block head %}
|
||||
{{ parent() }}
|
||||
|
||||
<style>
|
||||
.config-symfony-version-lts {
|
||||
border: 0;
|
||||
color: var(--color-muted);
|
||||
font-size: 21px;
|
||||
line-height: 33px;
|
||||
}
|
||||
.config-symfony-version-lts[title] {
|
||||
text-decoration: none;
|
||||
}
|
||||
.config-symfony-version-status-badge {
|
||||
background-color: var(--badge-background);
|
||||
border-radius: 4px;
|
||||
color: var(--badge-color);
|
||||
display: inline-block;
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
margin: 10px 0 5px;
|
||||
padding: 3px 7px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.config-symfony-version-status-badge.status-success {
|
||||
background-color: var(--badge-success-background);
|
||||
color: var(--badge-success-color);
|
||||
}
|
||||
.config-symfony-version-status-badge.status-warning {
|
||||
background-color: var(--badge-warning-background);
|
||||
color: var(--badge-warning-color);
|
||||
}
|
||||
.config-symfony-version-status-badge.status-error {
|
||||
background-color: var(--badge-danger-background);
|
||||
color: var(--badge-danger-color);
|
||||
}
|
||||
.config-symfony-version-roadmap-link {
|
||||
display: inline-block;
|
||||
margin: 10px 5px 5px;
|
||||
}
|
||||
.config-symfony-eol {
|
||||
margin-top: 5px;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block toolbar %}
|
||||
{% if 'unknown' == collector.symfonyState %}
|
||||
{% set block_status = '' %}
|
||||
@@ -20,7 +66,7 @@
|
||||
|
||||
{% set icon %}
|
||||
<span class="sf-toolbar-label">
|
||||
{{ include('@WebProfiler/Icon/symfony.svg') }}
|
||||
{{ source('@WebProfiler/Icon/symfony.svg') }}
|
||||
</span>
|
||||
<span class="sf-toolbar-value">{{ collector.symfonyState is defined ? collector.symfonyversion : 'n/a' }}</span>
|
||||
{% endset %}
|
||||
@@ -64,7 +110,13 @@
|
||||
|
||||
<div class="sf-toolbar-info-piece sf-toolbar-info-php-ext">
|
||||
<b>PHP Extensions</b>
|
||||
<span class="sf-toolbar-status sf-toolbar-status-{{ collector.hasxdebug ? 'green' : 'gray' }}">xdebug {{ collector.hasxdebug ? '✓' : '✗' }}</span>
|
||||
{% if collector.hasXdebugInfo %}
|
||||
<a href="{{ path('_profiler_xdebug') }}" title="View xdebug_info()">
|
||||
{% endif %}
|
||||
<span class="sf-toolbar-status sf-toolbar-status-{{ collector.hasXdebug ? 'green' : 'gray' }}">Xdebug {{ collector.hasXdebug ? '✓' : '✗' }}</span>
|
||||
{% if collector.hasXdebugInfo %}
|
||||
</a>
|
||||
{% endif %}
|
||||
<span class="sf-toolbar-status sf-toolbar-status-{{ collector.hasapcu ? 'green' : 'gray' }}">APCu {{ collector.hasapcu ? '✓' : '✗' }}</span>
|
||||
<span class="sf-toolbar-status sf-toolbar-status-{{ collector.haszendopcache ? 'green' : 'red' }}">OPcache {{ collector.haszendopcache ? '✓' : '✗' }}</span>
|
||||
</div>
|
||||
@@ -102,7 +154,7 @@
|
||||
|
||||
{% block menu %}
|
||||
<span class="label label-status-{{ collector.symfonyState == 'eol' ? 'red' : collector.symfonyState in ['eom', 'dev'] ? 'yellow' }}">
|
||||
<span class="icon">{{ include('@WebProfiler/Icon/config.svg') }}</span>
|
||||
<span class="icon">{{ source('@WebProfiler/Icon/config.svg') }}</span>
|
||||
<strong>Configuration</strong>
|
||||
</span>
|
||||
{% endblock %}
|
||||
@@ -112,7 +164,13 @@
|
||||
|
||||
<div class="metrics">
|
||||
<div class="metric">
|
||||
<span class="value">{{ collector.symfonyversion }}</span>
|
||||
<span class="value">
|
||||
{{ collector.symfonyversion }}
|
||||
|
||||
{% if collector.symfonylts %}
|
||||
<abbr class="config-symfony-version-lts" title="This is a Long-Term Support version">(LTS)</abbr>
|
||||
{% endif %}
|
||||
</span>
|
||||
<span class="label">Symfony version</span>
|
||||
</div>
|
||||
|
||||
@@ -131,33 +189,39 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
{% set symfony_status = { dev: 'Unstable Version', stable: 'Stable Version', eom: 'Maintenance Ended', eol: 'Version Expired' } %}
|
||||
{% set symfony_status = { dev: 'In Development', stable: 'Maintained', eom: 'Security Fixes Only', eol: 'Unmaintained' } %}
|
||||
{% set symfony_status_class = { dev: 'warning', stable: 'success', eom: 'warning', eol: 'error' } %}
|
||||
<table>
|
||||
<thead class="small">
|
||||
<tr>
|
||||
<th>Symfony Status</th>
|
||||
<th>Bugs {{ collector.symfonystate in ['eom', 'eol'] ? 'were' : 'are' }} fixed until</th>
|
||||
<th>Security issues {{ collector.symfonystate == 'eol' ? 'were' : 'are' }} fixed until</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td class="font-normal">
|
||||
<span class="label status-{{ symfony_status_class[collector.symfonystate] }}">{{ symfony_status[collector.symfonystate]|upper }}</span>
|
||||
{% if collector.symfonylts %}
|
||||
<span class="label status-success">Long-Term Support</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
<td class="font-normal">{{ collector.symfonyeom }}</td>
|
||||
<td class="font-normal">{{ collector.symfonyeol }}</td>
|
||||
<td class="font-normal">
|
||||
<a href="https://symfony.com/releases/{{ collector.symfonyminorversion }}#release-checker">View roadmap</a>
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="metrics">
|
||||
<div class="metric-group">
|
||||
<div class="metric">
|
||||
<span class="value">
|
||||
<span class="config-symfony-version-status-badge status-{{ symfony_status_class[collector.symfonystate] }}">{{ symfony_status[collector.symfonystate]|upper }}</span>
|
||||
</span>
|
||||
<span class="label">Your Symfony version status</span>
|
||||
</div>
|
||||
|
||||
{% if collector.symfonylts %}
|
||||
<div class="metric">
|
||||
<span class="value config-symfony-eol">
|
||||
{{ collector.symfonyeom }}
|
||||
</span>
|
||||
<span class="label">Bug fixes {{ collector.symfonystate in ['eom', 'eol'] ? 'ended on' : 'until' }}</span>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="metric">
|
||||
<span class="value config-symfony-eol">
|
||||
{{ collector.symfonyeol }}
|
||||
</span>
|
||||
<span class="label">
|
||||
{{ collector.symfonylts ? 'Security fixes' : 'Bug fixes and security fixes' }}
|
||||
{{ 'eol' == collector.symfonystate ? 'ended on' : 'until' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<a class="config-symfony-version-roadmap-link" href="https://symfony.com/releases/{{ collector.symfonyminorversion }}">View Symfony {{ collector.symfonyversion }} release details</a>
|
||||
|
||||
<h2>PHP Configuration</h2>
|
||||
|
||||
@@ -184,19 +248,21 @@
|
||||
</div>
|
||||
|
||||
<div class="metrics">
|
||||
<div class="metric">
|
||||
<span class="value">{{ include('@WebProfiler/Icon/' ~ (collector.haszendopcache ? 'yes' : 'no') ~ '.svg') }}</span>
|
||||
<span class="label">OPcache</span>
|
||||
</div>
|
||||
<div class="metric-group">
|
||||
<div class="metric">
|
||||
<span class="value value-is-icon {{ not collector.haszendopcache ? 'value-shows-no-color' }}">{{ source('@WebProfiler/Icon/' ~ (collector.haszendopcache ? 'yes' : 'no') ~ '.svg') }}</span>
|
||||
<span class="label">OPcache</span>
|
||||
</div>
|
||||
|
||||
<div class="metric">
|
||||
<span class="value">{{ include('@WebProfiler/Icon/' ~ (collector.hasapcu ? 'yes' : 'no-gray') ~ '.svg') }}</span>
|
||||
<span class="label">APCu</span>
|
||||
</div>
|
||||
<div class="metric">
|
||||
<span class="value value-is-icon {{ not collector.hasapcu ? 'value-shows-no-color' }}">{{ source('@WebProfiler/Icon/' ~ (collector.hasapcu ? 'yes' : 'no') ~ '.svg') }}</span>
|
||||
<span class="label">APCu</span>
|
||||
</div>
|
||||
|
||||
<div class="metric">
|
||||
<span class="value">{{ include('@WebProfiler/Icon/' ~ (collector.hasxdebug ? 'yes' : 'no-gray') ~ '.svg') }}</span>
|
||||
<span class="label">Xdebug</span>
|
||||
<div class="metric">
|
||||
<span class="value value-is-icon {{ not collector.hasxdebug ? 'value-shows-no-color' }}">{{ source('@WebProfiler/Icon/' ~ (collector.hasxdebug ? 'yes' : 'no') ~ '.svg') }}</span>
|
||||
<span class="label">Xdebug</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -1,85 +1,92 @@
|
||||
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
|
||||
|
||||
{% import _self as helper %}
|
||||
|
||||
{% block menu %}
|
||||
<span class="label">
|
||||
<span class="icon">{{ include('@WebProfiler/Icon/event.svg') }}</span>
|
||||
<span class="icon">{{ source('@WebProfiler/Icon/event.svg') }}</span>
|
||||
<strong>Events</strong>
|
||||
</span>
|
||||
{% endblock %}
|
||||
|
||||
{% block panel %}
|
||||
<h2>Event Dispatcher</h2>
|
||||
<h2>Dispatched Events</h2>
|
||||
|
||||
{% if collector.calledlisteners is empty %}
|
||||
<div class="empty">
|
||||
<p>No events have been recorded. Check that debugging is enabled in the kernel.</p>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="sf-tabs">
|
||||
<div class="sf-tabs">
|
||||
{% for dispatcherName, dispatcherData in collector.data %}
|
||||
<div class="tab">
|
||||
<h3 class="tab-title">Called Listeners <span class="badge">{{ collector.calledlisteners|length }}</span></h3>
|
||||
|
||||
<h3 class="tab-title">{{ dispatcherName }}</h3>
|
||||
<div class="tab-content">
|
||||
{{ helper.render_table(collector.calledlisteners) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab">
|
||||
<h3 class="tab-title">Not Called Listeners <span class="badge">{{ collector.notcalledlisteners|length }}</span></h3>
|
||||
<div class="tab-content">
|
||||
{% if collector.notcalledlisteners is empty %}
|
||||
<div class="empty">
|
||||
<p>
|
||||
<strong>There are no uncalled listeners</strong>.
|
||||
</p>
|
||||
<p>
|
||||
All listeners were called for this request or an error occurred
|
||||
when trying to collect uncalled listeners (in which case check the
|
||||
logs to get more information).
|
||||
</p>
|
||||
{% if dispatcherData['called_listeners'] is empty %}
|
||||
<div class="empty empty-panel">
|
||||
<p>No events have been recorded. Check that debugging is enabled in the kernel.</p>
|
||||
</div>
|
||||
{% else %}
|
||||
{{ helper.render_table(collector.notcalledlisteners) }}
|
||||
<div class="sf-tabs">
|
||||
<div class="tab">
|
||||
<h3 class="tab-title">Called Listeners <span class="badge">{{ dispatcherData['called_listeners']|length }}</span></h3>
|
||||
|
||||
<div class="tab-content">
|
||||
{{ _self.render_table(dispatcherData['called_listeners']) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab">
|
||||
<h3 class="tab-title">Not Called Listeners <span class="badge">{{ dispatcherData['not_called_listeners']|length }}</span></h3>
|
||||
<div class="tab-content">
|
||||
{% if dispatcherData['not_called_listeners'] is empty %}
|
||||
<div class="empty">
|
||||
<p>
|
||||
<strong>There are no uncalled listeners</strong>.
|
||||
</p>
|
||||
<p>
|
||||
All listeners were called or an error occurred
|
||||
when trying to collect uncalled listeners (in which case check the
|
||||
logs to get more information).
|
||||
</p>
|
||||
</div>
|
||||
{% else %}
|
||||
{{ _self.render_table(dispatcherData['not_called_listeners']) }}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab">
|
||||
<h3 class="tab-title">Orphaned Events <span class="badge">{{ dispatcherData['orphaned_events']|length }}</span></h3>
|
||||
<div class="tab-content">
|
||||
{% if dispatcherData['orphaned_events'] is empty %}
|
||||
<div class="empty">
|
||||
<p>
|
||||
<strong>There are no orphaned events</strong>.
|
||||
</p>
|
||||
<p>
|
||||
All dispatched events were handled or an error occurred
|
||||
when trying to collect orphaned events (in which case check the
|
||||
logs to get more information).
|
||||
</p>
|
||||
</div>
|
||||
{% else %}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Event</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for event in dispatcherData['orphaned_events'] %}
|
||||
<tr>
|
||||
<td class="font-normal">{{ event }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab">
|
||||
<h3 class="tab-title">Orphaned Events <span class="badge">{{ collector.orphanedEvents|length }}</span></h3>
|
||||
<div class="tab-content">
|
||||
{% if collector.orphanedEvents is empty %}
|
||||
<div class="empty">
|
||||
<p>
|
||||
<strong>There are no orphaned events</strong>.
|
||||
</p>
|
||||
<p>
|
||||
All dispatched events were handled or an error occurred
|
||||
when trying to collect orphaned events (in which case check the
|
||||
logs to get more information).
|
||||
</p>
|
||||
</div>
|
||||
{% else %}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Event</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for event in collector.orphanedEvents %}
|
||||
<tr>
|
||||
<td class="font-normal">{{ event }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% macro render_table(listeners) %}
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
|
||||
{% block menu %}
|
||||
<span class="label {{ collector.hasexception ? 'label-status-error' : 'disabled' }}">
|
||||
<span class="icon">{{ include('@WebProfiler/Icon/exception.svg') }}</span>
|
||||
<span class="icon">{{ source('@WebProfiler/Icon/exception.svg') }}</span>
|
||||
<strong>Exception</strong>
|
||||
{% if collector.hasexception %}
|
||||
<span class="count">
|
||||
@@ -23,11 +23,19 @@
|
||||
{% endblock %}
|
||||
|
||||
{% block panel %}
|
||||
{# these styles are needed to override some styles from Exception page, which wasn't
|
||||
updated yet to the new style of the Symfony Profiler #}
|
||||
<style>
|
||||
.tab-navigation li { background: none; border: 0; font-size: 14px; }
|
||||
.tab-navigation li.active { border-radius: 6px; }
|
||||
.tab-navigation li.active .badge { background-color: var(--selected-badge-background); color: var(--selected-badge-color); }
|
||||
</style>
|
||||
|
||||
<h2>Exceptions</h2>
|
||||
|
||||
{% if not collector.hasexception %}
|
||||
<div class="empty">
|
||||
<p>No exception was thrown and caught during the request.</p>
|
||||
<div class="empty empty-panel">
|
||||
<p>No exception was thrown and caught.</p>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="sf-reset">
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,9 +1,45 @@
|
||||
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
|
||||
|
||||
{% block head %}
|
||||
{{ parent() }}
|
||||
|
||||
<style>
|
||||
.sf-profiler-httpclient-requests thead th {
|
||||
vertical-align: top;
|
||||
}
|
||||
.sf-profiler-httpclient-requests .http-method {
|
||||
border: 1px solid var(--header-status-request-method-color);
|
||||
border-radius: 5px;
|
||||
color: var(--header-status-request-method-color);
|
||||
display: inline-block;
|
||||
font-weight: 500;
|
||||
line-height: 1;
|
||||
margin-right: 6px;
|
||||
padding: 2px 4px;
|
||||
text-align: center;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.sf-profiler-httpclient-requests .status-response-status-code {
|
||||
background: var(--gray-600);
|
||||
border-radius: 4px;
|
||||
color: var(--white);
|
||||
display: inline-block;
|
||||
font-size: 12px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 2px;
|
||||
padding: 1px 3px;
|
||||
}
|
||||
.sf-profiler-httpclient-requests .status-response-status-code.status-success { background: var(--header-success-status-code-background); color: var(--header-success-status-code-color); }
|
||||
.sf-profiler-httpclient-requests .status-response-status-code.status-warning { background: var(--header-warning-status-code-background); color: var(--header-warning-status-code-color); }
|
||||
.sf-profiler-httpclient-requests .status-response-status-code.status-error { background: var(--header-error-status-code-background); color: var(--header-error-status-code-color); }
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
|
||||
{% block toolbar %}
|
||||
{% if collector.requestCount %}
|
||||
{% set icon %}
|
||||
{{ include('@WebProfiler/Icon/http-client.svg') }}
|
||||
{{ source('@WebProfiler/Icon/http-client.svg') }}
|
||||
{% set status_color = '' %}
|
||||
<span class="sf-toolbar-value">{{ collector.requestCount }}</span>
|
||||
{% endset %}
|
||||
@@ -25,7 +61,7 @@
|
||||
|
||||
{% block menu %}
|
||||
<span class="label {{ collector.requestCount == 0 ? 'disabled' }}">
|
||||
<span class="icon">{{ include('@WebProfiler/Icon/http-client.svg') }}</span>
|
||||
<span class="icon">{{ source('@WebProfiler/Icon/http-client.svg') }}</span>
|
||||
<strong>HTTP Client</strong>
|
||||
{% if collector.requestCount %}
|
||||
<span class="count">
|
||||
@@ -38,7 +74,7 @@
|
||||
{% block panel %}
|
||||
<h2>HTTP Client</h2>
|
||||
{% if collector.requestCount == 0 %}
|
||||
<div class="empty">
|
||||
<div class="empty empty-panel">
|
||||
<p>No HTTP requests were made.</p>
|
||||
</div>
|
||||
{% else %}
|
||||
@@ -77,28 +113,37 @@
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
<table>
|
||||
<table class="sf-profiler-httpclient-requests">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>
|
||||
<span class="label">{{ trace.method }}</span>
|
||||
<span class="http-method">{{ trace.method }}</span>
|
||||
</th>
|
||||
<th class="full-width">
|
||||
{{ trace.url }}
|
||||
{% if trace.options is not empty %}
|
||||
{{ profiler_dump(trace.options, maxDepth=1) }}
|
||||
{% endif %}
|
||||
</th>
|
||||
{% if profiler_token and profiler_link %}
|
||||
<th>
|
||||
Profile
|
||||
</th>
|
||||
{% endif %}
|
||||
{% if trace.curlCommand is defined and trace.curlCommand %}
|
||||
<th>
|
||||
<button class="btn btn-sm hidden" title="Copy as cURL" data-clipboard-text="{{ trace.curlCommand }}">Copy as cURL</button>
|
||||
</th>
|
||||
{% endif %}
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% if trace.options is not empty %}
|
||||
<tr>
|
||||
<th class="font-normal">Request options</th>
|
||||
<td>{{ profiler_dump(trace.options, maxDepth=1) }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<th>
|
||||
<th class="font-normal">Response</th>
|
||||
<td{% if trace.curlCommand is defined and trace.curlCommand %} colspan="2"{% endif %}>
|
||||
{% if trace.http_code >= 500 %}
|
||||
{% set responseStatus = 'error' %}
|
||||
{% elseif trace.http_code >= 400 %}
|
||||
@@ -106,11 +151,10 @@
|
||||
{% else %}
|
||||
{% set responseStatus = 'success' %}
|
||||
{% endif %}
|
||||
<span class="label status-{{ responseStatus }}">
|
||||
<span class="font-normal status-response-status-code status-{{ responseStatus }}">
|
||||
{{ trace.http_code }}
|
||||
</span>
|
||||
</th>
|
||||
<td>
|
||||
|
||||
{{ profiler_dump(trace.info, maxDepth=1) }}
|
||||
</td>
|
||||
{% if profiler_token and profiler_link %}
|
||||
|
||||
@@ -1,12 +1,330 @@
|
||||
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
|
||||
|
||||
{% import _self as helper %}
|
||||
{% block stylesheets %}
|
||||
{{ parent() }}
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--log-filter-active-num-color: #2563EB;
|
||||
--log-timestamp-color: #555;
|
||||
}
|
||||
.theme-dark {
|
||||
--log-filter-active-num-color: #2563EB;
|
||||
--log-timestamp-color: #ccc;
|
||||
}
|
||||
|
||||
.log-filters {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
}
|
||||
.log-filters .log-filter {
|
||||
flex-shrink: 0;
|
||||
margin-right: 15px;
|
||||
position: relative;
|
||||
}
|
||||
.log-filters .log-filter summary {
|
||||
align-items: center;
|
||||
background: var(--button-background);
|
||||
border-radius: 6px;
|
||||
border: 1px solid var(--button-border-color);
|
||||
box-shadow: var(--button-box-shadow);
|
||||
color: var(--button-color);
|
||||
cursor: pointer;
|
||||
display: flex;
|
||||
font-size: 13px;
|
||||
font-weight: 500;
|
||||
padding: 4px 8px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.log-filters .log-filter summary:active {
|
||||
box-shadow: none;
|
||||
transform: translateY(1px);
|
||||
}
|
||||
.log-filters .log-filter summary .icon {
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
margin: 0 7px 0 0;
|
||||
}
|
||||
.log-filters .log-filter summary svg {
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
opacity: 0.7;
|
||||
}
|
||||
.log-filters .log-filter summary svg {
|
||||
stroke-width: 2;
|
||||
}
|
||||
.log-filters .log-filter summary .filter-active-num {
|
||||
color: var(--log-filter-active-num-color);
|
||||
font-weight: bold;
|
||||
padding: 0 1px;
|
||||
}
|
||||
.log-filter .tab-navigation {
|
||||
position: relative;
|
||||
}
|
||||
.log-filter .tab-navigation input[type="radio"] {
|
||||
position: absolute;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
}
|
||||
.tab-navigation input[type="radio"]:checked + .tab-control {
|
||||
background-color: var(--tab-active-background);
|
||||
border-radius: 6px;
|
||||
box-shadow: inset 0 0 0 1.5px var(--tab-active-border-color);
|
||||
color: var(--tab-active-color);
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
.theme-dark .tab-navigation input[type="radio"]:checked + .tab-control {
|
||||
box-shadow: inset 0 0 0 1px var(--tab-border-color);
|
||||
}
|
||||
.tab-navigation input[type="radio"]:focus-visible + .tab-control {
|
||||
outline: 1px solid var(--color-link);
|
||||
}
|
||||
.tab-navigation input[type="radio"]:checked + .tab-control + input[type="radio"] + .tab-control:before{
|
||||
width: 0;
|
||||
}
|
||||
|
||||
.log-filters .log-filter .log-filter-content {
|
||||
background: var(--base-0);
|
||||
border: 1px solid var(--table-border-color);
|
||||
border-radius: 6px;
|
||||
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
|
||||
padding: 15px;
|
||||
position: absolute;
|
||||
left: 0;
|
||||
top: 32px;
|
||||
max-width: 400px;
|
||||
min-width: 200px;
|
||||
z-index: 9999;
|
||||
}
|
||||
.log-filters .log-filter .log-filter-content .log-filter-option {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
.log-filter .filter-select-all-or-none {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.log-filter .filter-select-all-or-none button + button {
|
||||
margin-left: 15px;
|
||||
}
|
||||
.log-filters .log-filter .log-filter-content .log-filter-option + .log-filter-option {
|
||||
margin: 7px 0 0;
|
||||
}
|
||||
.log-filters .log-filter .log-filter-content .log-filter-option label {
|
||||
cursor: pointer;
|
||||
flex: 1;
|
||||
padding-left: 10px;
|
||||
}
|
||||
|
||||
table.logs {
|
||||
border-bottom-width: 0;
|
||||
border-collapse: collapse;
|
||||
}
|
||||
table.logs tr + tr td {
|
||||
border-width: 1px 0 0;
|
||||
}
|
||||
table.logs .metadata {
|
||||
display: block;
|
||||
font-size: 12px;
|
||||
}
|
||||
.theme-dark tr.status-error td,
|
||||
.theme-dark tr.status-warning td { border-bottom: unset; border-top: unset; }
|
||||
|
||||
table.logs .log-timestamp {
|
||||
color: var(--log-timestamp-color);
|
||||
}
|
||||
table.logs .log-metadata {
|
||||
margin: 8px 0 0;
|
||||
}
|
||||
table.logs .log-metadata > span {
|
||||
display: inline-block;
|
||||
}
|
||||
table.logs .log-metadata > span + span {
|
||||
margin-left: 10px;
|
||||
}
|
||||
table.logs .log-metadata .log-channel {
|
||||
color: var(--base-1);
|
||||
font-size: 13px;
|
||||
font-weight: bold;
|
||||
}
|
||||
table.logs .log-metadata .badge {
|
||||
background: var(--badge-light-background);
|
||||
color: var(--badge-light-color);
|
||||
}
|
||||
table.logs .log-metadata .log-num-occurrences {
|
||||
color: var(--color-muted);
|
||||
font-size: 13px;
|
||||
}
|
||||
table.logs .log-metadata .context {
|
||||
background: var(--code-block-background);
|
||||
border-radius: 4px;
|
||||
padding: 5px;
|
||||
}
|
||||
table.logs .log-metadata .context + .context {
|
||||
margin-top: 10px;
|
||||
}
|
||||
.log-type-badge {
|
||||
background: var(--badge-light-background);
|
||||
box-shadow: none;
|
||||
color: var(--badge-light-color);
|
||||
display: inline-block;
|
||||
font-family: var(--font-family-system);
|
||||
margin-top: 5px;
|
||||
}
|
||||
.log-type-badge.badge-deprecation,
|
||||
.log-type-badge.badge-warning {
|
||||
background: var(--badge-warning-background);
|
||||
color: var(--badge-warning-color);
|
||||
}
|
||||
.log-type-badge.badge-error {
|
||||
background: var(--badge-danger-background);
|
||||
color: var(--badge-danger-color);
|
||||
}
|
||||
.log-type-badge.badge-silenced {
|
||||
background: #EDE9FE;
|
||||
color: #6D28D9;
|
||||
}
|
||||
.theme-dark .log-type-badge.badge-silenced {
|
||||
background: #5B21B6;
|
||||
color: #EDE9FE;
|
||||
}
|
||||
|
||||
tr.log-status-warning > td:first-child,
|
||||
tr.log-status-error > td:first-child,
|
||||
tr.log-status-silenced > td:first-child {
|
||||
position: relative;
|
||||
}
|
||||
tr.log-status-warning > td:first-child:before,
|
||||
tr.log-status-error > td:first-child:before,
|
||||
tr.log-status-silenced > td:first-child:before {
|
||||
background: transparent;
|
||||
border-radius: 0;
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 4px;
|
||||
height: 100%;
|
||||
}
|
||||
tr.log-status-warning > td:first-child:before {
|
||||
background: var(--yellow-400);
|
||||
}
|
||||
tr.log-status-error > td:first-child:before {
|
||||
background: var(--red-400);
|
||||
}
|
||||
tr.log-status-silenced > td:first-child:before {
|
||||
background: #a78bfa;
|
||||
}
|
||||
|
||||
.container-compilation-logs {
|
||||
background: var(--table-background);
|
||||
border: 1px solid var(--base-2);
|
||||
border-radius: 6px;
|
||||
margin-top: 30px;
|
||||
padding: 15px;
|
||||
}
|
||||
.container-compilation-logs summary {
|
||||
cursor: pointer;
|
||||
}
|
||||
.container-compilation-logs summary h4 {
|
||||
margin: 0 0 5px;
|
||||
}
|
||||
.container-compilation-logs summary p {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block javascripts %}
|
||||
<script>
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
new SymfonyProfilerLoggerPanel();
|
||||
});
|
||||
|
||||
class SymfonyProfilerLoggerPanel {
|
||||
constructor() {
|
||||
this.#initializeLogsTable();
|
||||
}
|
||||
|
||||
#initializeLogsTable() {
|
||||
this.#updateLogsTable();
|
||||
|
||||
document.querySelectorAll('.log-filter input').forEach((input) => {
|
||||
input.addEventListener('change', () => { this.#updateLogsTable(); });
|
||||
});
|
||||
|
||||
document.querySelectorAll('.filter-select-all-or-none button').forEach((link) => {
|
||||
link.addEventListener('click', () => {
|
||||
const selectAll = link.classList.contains('select-all');
|
||||
link.closest('.log-filter-content').querySelectorAll('input').forEach((input) => {
|
||||
input.checked = selectAll;
|
||||
});
|
||||
|
||||
this.#updateLogsTable();
|
||||
});
|
||||
});
|
||||
|
||||
document.body.addEventListener('click', (event) => {
|
||||
document.querySelectorAll('details.log-filter').forEach((filterElement) => {
|
||||
if (!filterElement.contains(event.target) && filterElement.open) {
|
||||
filterElement.open = false;
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#updateLogsTable() {
|
||||
const logs = document.querySelector('table.logs');
|
||||
if (null === logs) {
|
||||
return;
|
||||
}
|
||||
|
||||
const selectedType = document.querySelector('#log-filter-type input:checked').value;
|
||||
const priorities = document.querySelectorAll('#log-filter-priority input');
|
||||
const allPriorities = Array.from(priorities).map((input) => input.value);
|
||||
const selectedPriorities = Array.from(priorities).filter((input) => input.checked).map((input) => input.value);
|
||||
const channels = document.querySelectorAll('#log-filter-channel input');
|
||||
const selectedChannels = Array.from(channels).filter((input) => input.checked).map((input) => input.value);
|
||||
|
||||
/* hide rows that don't match the current filters */
|
||||
let numVisibleRows = 0;
|
||||
logs.querySelectorAll('tbody tr').forEach((row) => {
|
||||
if ('all' !== selectedType && selectedType !== row.getAttribute('data-type')) {
|
||||
row.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
const priority = row.getAttribute('data-priority');
|
||||
if (false === selectedPriorities.includes(priority) && true === allPriorities.includes(priority)) {
|
||||
row.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
if ('' !== row.getAttribute('data-channel') && false === selectedChannels.includes(row.getAttribute('data-channel'))) {
|
||||
row.style.display = 'none';
|
||||
return;
|
||||
}
|
||||
|
||||
row.style.display = 'table-row';
|
||||
numVisibleRows++;
|
||||
});
|
||||
|
||||
document.querySelector('table.logs').style.display = 0 === numVisibleRows ? 'none' : 'table';
|
||||
document.querySelector('.no-logs-message').style.display = 0 === numVisibleRows ? 'block' : 'none';
|
||||
|
||||
/* update the selected totals of all filters */
|
||||
document.querySelector('#log-filter-priority .filter-active-num').innerText = (priorities.length === selectedPriorities.length) ? 'All' : selectedPriorities.length;
|
||||
document.querySelector('#log-filter-channel .filter-active-num').innerText = (channels.length === selectedChannels.length) ? 'All' : selectedChannels.length;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block toolbar %}
|
||||
{% if collector.counterrors or collector.countdeprecations or collector.countwarnings %}
|
||||
{% set icon %}
|
||||
{% set status_color = collector.counterrors ? 'red' : collector.countwarnings ? 'yellow' : 'none' %}
|
||||
{{ include('@WebProfiler/Icon/logger.svg') }}
|
||||
{{ source('@WebProfiler/Icon/logger.svg') }}
|
||||
<span class="sf-toolbar-value">{{ collector.counterrors ?: (collector.countdeprecations + collector.countwarnings) }}</span>
|
||||
{% endset %}
|
||||
|
||||
@@ -33,7 +351,7 @@
|
||||
|
||||
{% block menu %}
|
||||
<span class="label label-status-{{ collector.counterrors ? 'error' : collector.countwarnings ? 'warning' : 'none' }} {{ collector.logs is empty ? 'disabled' }}">
|
||||
<span class="icon">{{ include('@WebProfiler/Icon/logger.svg') }}</span>
|
||||
<span class="icon">{{ source('@WebProfiler/Icon/logger.svg') }}</span>
|
||||
<strong>Logs</strong>
|
||||
{% if collector.counterrors or collector.countdeprecations or collector.countwarnings %}
|
||||
<span class="count">
|
||||
@@ -47,7 +365,7 @@
|
||||
<h2>Log Messages</h2>
|
||||
|
||||
{% if collector.processedLogs is empty %}
|
||||
<div class="empty">
|
||||
<div class="empty empty-panel">
|
||||
<p>No log messages available.</p>
|
||||
</div>
|
||||
{% else %}
|
||||
@@ -57,33 +375,30 @@
|
||||
{% set filters = collector.filters %}
|
||||
<div class="log-filters">
|
||||
<div id="log-filter-type" class="log-filter">
|
||||
<ul class="tab-navigation">
|
||||
<li class="{{ not has_error_logs and not has_deprecation_logs ? 'active' }}">
|
||||
<input {{ not has_error_logs and not has_deprecation_logs ? 'checked' }} type="radio" id="filter-log-type-all" name="filter-log-type" value="all">
|
||||
<label for="filter-log-type-all">All messages</label>
|
||||
</li>
|
||||
<div class="tab-navigation">
|
||||
{% set initially_active_tab = has_error_logs ? 'error' : has_deprecation_logs ? 'deprecation' : 'all' %}
|
||||
<input type="radio" id="filter-log-type-all" name="filter-log-type" value="all" {{ 'all' == initially_active_tab ? 'checked' }}>
|
||||
<label for="filter-log-type-all" class="tab-control">
|
||||
All messages
|
||||
</label>
|
||||
|
||||
<li class="{{ has_error_logs ? 'active' }}">
|
||||
<input {{ has_error_logs ? 'checked' }} type="radio" id="filter-log-type-error" name="filter-log-type" value="error">
|
||||
<label for="filter-log-type-error">
|
||||
Errors
|
||||
<span class="badge status-{{ collector.counterrors ? 'error' }}">{{ collector.counterrors|default(0) }}</span>
|
||||
</label>
|
||||
</li>
|
||||
<input type="radio" id="filter-log-type-error" name="filter-log-type" value="error" {{ 'error' == initially_active_tab ? 'checked' }}>
|
||||
<label for="filter-log-type-error" class="tab-control">
|
||||
Errors
|
||||
<span class="badge status-{{ collector.counterrors ? 'error' }}">{{ collector.counterrors|default(0) }}</span>
|
||||
</label>
|
||||
|
||||
<li class="{{ not has_error_logs and has_deprecation_logs ? 'active' }}">
|
||||
<input {{ not has_error_logs and has_deprecation_logs ? 'checked' }} type="radio" id="filter-log-type-deprecation" name="filter-log-type" value="deprecation">
|
||||
<label for="filter-log-type-deprecation">
|
||||
Deprecations
|
||||
<span class="badge status-{{ collector.countdeprecations ? 'warning' }}">{{ collector.countdeprecations|default(0) }}</span>
|
||||
</label>
|
||||
</li>
|
||||
</ul>
|
||||
<input type="radio" id="filter-log-type-deprecation" name="filter-log-type" value="deprecation" {{ 'deprecation' == initially_active_tab ? 'checked' }}>
|
||||
<label for="filter-log-type-deprecation" class="tab-control">
|
||||
Deprecations
|
||||
<span class="badge status-{{ collector.countdeprecations ? 'warning' }}">{{ collector.countdeprecations|default(0) }}</span>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<details id="log-filter-priority" class="log-filter">
|
||||
<summary>
|
||||
<span class="icon">{{ include('@WebProfiler/Icon/filter.svg') }}</span>
|
||||
<span class="icon">{{ source('@WebProfiler/Icon/filter.svg') }}</span>
|
||||
Level (<span class="filter-active-num">{{ filters.priority|length - 1 }}</span>)
|
||||
</summary>
|
||||
|
||||
@@ -104,7 +419,7 @@
|
||||
|
||||
<details id="log-filter-channel" class="log-filter">
|
||||
<summary>
|
||||
<span class="icon">{{ include('@WebProfiler/Icon/filter.svg') }}</span>
|
||||
<span class="icon">{{ source('@WebProfiler/Icon/filter.svg') }}</span>
|
||||
Channel (<span class="filter-active-num">{{ filters.channel|length - 1 }}</span>)
|
||||
</summary>
|
||||
|
||||
@@ -126,13 +441,15 @@
|
||||
|
||||
<table class="logs">
|
||||
<colgroup>
|
||||
<col width="140px">
|
||||
<col style="width: 140px">
|
||||
<col>
|
||||
</colgroup>
|
||||
|
||||
<thead>
|
||||
<th>Time</th>
|
||||
<th>Message</th>
|
||||
<tr>
|
||||
<th>Time</th>
|
||||
<th>Message</th>
|
||||
</tr>
|
||||
</thead>
|
||||
|
||||
<tbody>
|
||||
@@ -143,7 +460,7 @@
|
||||
%}
|
||||
<tr class="log-status-{{ css_class }}" data-type="{{ log.type }}" data-priority="{{ log.priority }}" data-channel="{{ log.channel }}" style="{{ 'event' == log.channel or 'DEBUG' == log.priorityName ? 'display: none' }}">
|
||||
<td class="log-timestamp">
|
||||
<time title="{{ log.timestamp|date('r') }}" datetime="{{ log.timestamp|date('c') }}">
|
||||
<time class="newline" title="{{ log.timestamp|date('r') }}" datetime="{{ log.timestamp|date(constant('DateTimeInterface::RFC3339_EXTENDED')) }}" data-convert-to-user-timezone data-render-as-time data-render-with-millisecond-precision>
|
||||
{{ log.timestamp|date('H:i:s.v') }}
|
||||
</time>
|
||||
|
||||
@@ -163,7 +480,7 @@
|
||||
</td>
|
||||
|
||||
<td class="font-normal">
|
||||
{{ helper.render_log_message('debug', loop.index, log) }}
|
||||
{{ _self.render_log_message('debug', loop.index, log) }}
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
@@ -173,19 +490,13 @@
|
||||
<div class="no-logs-message empty">
|
||||
<p>There are no log messages.</p>
|
||||
</div>
|
||||
|
||||
<script>Sfjs.initializeLogsTable();</script>
|
||||
{% endif %}
|
||||
|
||||
{% set compilerLogTotal = 0 %}
|
||||
{% for logs in collector.compilerLogs %}
|
||||
{% set compilerLogTotal = compilerLogTotal + logs|length %}
|
||||
{% endfor %}
|
||||
|
||||
{% set compilerLogTotal = collector.compilerLogs|reduce((total, logs) => total + logs|length, 0) %}
|
||||
<details class="container-compilation-logs">
|
||||
<summary>
|
||||
<h4>Container Compilation Logs <span class="text-muted">({{ compilerLogTotal }})</span></h4>
|
||||
<p class="text-muted">Log messages generated during the compilation of the service container.</p>
|
||||
<span class="text-muted">Log messages generated during the compilation of the service container.</span>
|
||||
</summary>
|
||||
|
||||
{% if collector.compilerLogs is empty %}
|
||||
|
||||
@@ -1,11 +1,199 @@
|
||||
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
|
||||
|
||||
{% block stylesheets %}
|
||||
{{ parent() }}
|
||||
|
||||
<style>
|
||||
:root {
|
||||
--mailer-email-table-wrapper-background: var(--gray-100);
|
||||
--mailer-email-table-active-row-background: #dbeafe;
|
||||
--mailer-email-table-active-row-color: var(--color-text);
|
||||
}
|
||||
.theme-dark {
|
||||
--mailer-email-table-wrapper-background: var(--gray-900);
|
||||
--mailer-email-table-active-row-background: var(--gray-300);
|
||||
--mailer-email-table-active-row-color: var(--gray-800);
|
||||
}
|
||||
|
||||
.mailer-email-summary-table-wrapper {
|
||||
background: var(--mailer-email-table-wrapper-background);
|
||||
border-bottom: 4px double var(--table-border-color);
|
||||
border-radius: inherit;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
margin: 0 -9px 10px -9px;
|
||||
padding-bottom: 10px;
|
||||
transform: translateY(-9px);
|
||||
max-height: 265px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.mailer-email-summary-table,
|
||||
.mailer-email-summary-table tr,
|
||||
.mailer-email-summary-table td {
|
||||
border: 0;
|
||||
border-radius: inherit;
|
||||
border-bottom-left-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
box-shadow: none;
|
||||
}
|
||||
.mailer-email-summary-table th {
|
||||
color: var(--color-muted);
|
||||
font-size: 13px;
|
||||
padding: 4px 10px;
|
||||
}
|
||||
.mailer-email-summary-table tr td,
|
||||
.mailer-email-summary-table tr:last-of-type td {
|
||||
border: solid var(--table-border-color);
|
||||
border-width: 1px 0;
|
||||
}
|
||||
.mailer-email-summary-table-row {
|
||||
margin: 5px 0;
|
||||
}
|
||||
.mailer-email-summary-table-row:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
.mailer-email-summary-table-row.active {
|
||||
background: var(--mailer-email-table-active-row-background);
|
||||
color: var(--mailer-email-table-active-row-color);
|
||||
}
|
||||
.mailer-email-summary-table-row td {
|
||||
font-family: var(--font-family-system);
|
||||
font-size: inherit;
|
||||
}
|
||||
.mailer-email-details {
|
||||
display: none;
|
||||
}
|
||||
.mailer-email-details.active {
|
||||
display: block;
|
||||
}
|
||||
.mailer-transport-information {
|
||||
border-bottom: 1px solid var(--form-input-border-color);
|
||||
padding-bottom: 5px;
|
||||
font-size: var(--font-size-body);
|
||||
margin: 5px 0 10px 5px;
|
||||
}
|
||||
.mailer-transport-information .badge {
|
||||
font-size: inherit;
|
||||
font-weight: inherit;
|
||||
}
|
||||
.mailer-message-subject {
|
||||
font-size: 21px;
|
||||
font-weight: bold;
|
||||
margin: 5px;
|
||||
}
|
||||
.mailer-message-headers {
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.mailer-message-headers p {
|
||||
font-size: var(--font-size-body);
|
||||
margin: 2px 5px;
|
||||
}
|
||||
.mailer-message-header-secondary {
|
||||
color: var(--color-muted);
|
||||
}
|
||||
.mailer-message-attachments-title {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
font-size: var(--font-size-body);
|
||||
font-weight: 600;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
.mailer-message-attachments-title svg {
|
||||
color: var(--color-muted);
|
||||
margin-right: 5px;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
}
|
||||
.mailer-message-attachments-title span {
|
||||
font-weight: normal;
|
||||
margin-left: 4px;
|
||||
}
|
||||
.mailer-message-attachments-list {
|
||||
list-style: none;
|
||||
margin: 0 0 5px 20px;
|
||||
padding: 0;
|
||||
}
|
||||
.mailer-message-attachments-list li {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
}
|
||||
.mailer-message-attachments-list li svg {
|
||||
margin-right: 5px;
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
}
|
||||
.mailer-message-attachments-list li a {
|
||||
margin-left: 5px;
|
||||
}
|
||||
.mailer-email-body {
|
||||
margin: 0;
|
||||
padding: 6px 8px;
|
||||
}
|
||||
.mailer-empty-email-body {
|
||||
background-image: url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' stroke='%23e5e5e5' stroke-width='4' stroke-dasharray='6%2c 14' stroke-dashoffset='0' stroke-linecap='square'/%3e%3c/svg%3e");
|
||||
border-radius: 6px;
|
||||
color: var(--color-muted);
|
||||
margin: 1em 0 0;
|
||||
padding: .5em 1em;
|
||||
}
|
||||
.theme-dark .mailer-empty-email-body {
|
||||
background-image: url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' stroke='%23737373' stroke-width='4' stroke-dasharray='6%2c 14' stroke-dashoffset='0' stroke-linecap='square'/%3e%3c/svg%3e");
|
||||
}
|
||||
.mailer-empty-email-body p {
|
||||
font-size: var(--font-size-body);
|
||||
margin: 0;
|
||||
padding: 0.5em 0;
|
||||
}
|
||||
|
||||
.mailer-message-download-raw {
|
||||
align-items: center;
|
||||
display: flex;
|
||||
padding: 5px 0 0 5px;
|
||||
}
|
||||
.mailer-message-download-raw svg {
|
||||
height: 18px;
|
||||
width: 18px;
|
||||
margin-right: 3px;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block javascripts %}
|
||||
{{ parent() }}
|
||||
|
||||
<script>
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
new SymfonyProfilerMailerPanel();
|
||||
});
|
||||
|
||||
class SymfonyProfilerMailerPanel {
|
||||
constructor() {
|
||||
this.#initializeEmailsTable();
|
||||
}
|
||||
|
||||
#initializeEmailsTable() {
|
||||
const emailRows = document.querySelectorAll('.mailer-email-summary-table-row');
|
||||
|
||||
emailRows.forEach((emailRow) => {
|
||||
emailRow.addEventListener('click', () => {
|
||||
emailRows.forEach((row) => row.classList.remove('active'));
|
||||
emailRow.classList.add('active');
|
||||
|
||||
document.querySelectorAll('.mailer-email-details').forEach((emailDetails) => emailDetails.style.display = 'none');
|
||||
document.querySelector(emailRow.getAttribute('data-target')).style.display = 'block';
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% block toolbar %}
|
||||
{% set events = collector.events %}
|
||||
|
||||
{% if events.messages|length %}
|
||||
{% set icon %}
|
||||
{% include('@WebProfiler/Icon/mailer.svg') %}
|
||||
{{ source('@WebProfiler/Icon/mailer.svg') }}
|
||||
<span class="sf-toolbar-value">{{ events.messages|length }}</span>
|
||||
{% endset %}
|
||||
|
||||
@@ -24,50 +212,13 @@
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block head %}
|
||||
{{ parent() }}
|
||||
<style type="text/css">
|
||||
/* utility classes */
|
||||
.m-t-0 { margin-top: 0 !important; }
|
||||
.m-t-10 { margin-top: 10px !important; }
|
||||
|
||||
/* basic grid */
|
||||
.row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
margin-right: -15px;
|
||||
margin-left: -15px;
|
||||
}
|
||||
.col {
|
||||
flex-basis: 0;
|
||||
flex-grow: 1;
|
||||
max-width: 100%;
|
||||
position: relative;
|
||||
width: 100%;
|
||||
min-height: 1px;
|
||||
padding-right: 15px;
|
||||
padding-left: 15px;
|
||||
}
|
||||
.col-4 {
|
||||
flex: 0 0 33.333333%;
|
||||
max-width: 33.333333%;
|
||||
}
|
||||
|
||||
/* small tabs */
|
||||
.sf-tabs-sm .tab-navigation li {
|
||||
font-size: 14px;
|
||||
padding: .3em .5em;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block menu %}
|
||||
{% set events = collector.events %}
|
||||
|
||||
<span class="label {{ events.messages is empty ? 'disabled' }}">
|
||||
<span class="icon">{{ include('@WebProfiler/Icon/mailer.svg') }}</span>
|
||||
<span class="icon">{{ source('@WebProfiler/Icon/mailer.svg') }}</span>
|
||||
|
||||
<strong>E-mails</strong>
|
||||
<strong>Emails</strong>
|
||||
{% if events.messages|length > 0 %}
|
||||
<span class="count">
|
||||
<span>{{ events.messages|length }}</span>
|
||||
@@ -78,140 +229,225 @@
|
||||
|
||||
{% block panel %}
|
||||
{% set events = collector.events %}
|
||||
|
||||
<h2>Emails</h2>
|
||||
|
||||
{% if not events.messages|length %}
|
||||
<div class="empty">
|
||||
<div class="empty empty-panel">
|
||||
<p>No emails were sent.</p>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="metrics">
|
||||
<div class="metric-group">
|
||||
<div class="metric">
|
||||
<span class="value">{{ events.events|filter(e => e.isQueued())|length }}</span>
|
||||
<span class="label">Queued</span>
|
||||
</div>
|
||||
|
||||
<div class="metric">
|
||||
<span class="value">{{ events.events|filter(e => not e.isQueued())|length }}</span>
|
||||
<span class="label">Sent</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="metrics">
|
||||
<div class="metric">
|
||||
<span class="value">{{ events.events|filter(e => e.isQueued())|length }}</span>
|
||||
<span class="label">Queued</span>
|
||||
{% if events.transports|length > 1 %}
|
||||
{% for transport in events.transports %}
|
||||
<h2><code>{{ transport }}</code> transport</h2>
|
||||
{{ _self.render_transport_details(collector, transport) }}
|
||||
{% endfor %}
|
||||
{% elseif events.transports is not empty %}
|
||||
{{ _self.render_transport_details(collector, events.transports|first, true) }}
|
||||
{% endif %}
|
||||
|
||||
{% macro render_transport_details(collector, transport, show_transport_name = false) %}
|
||||
<div class="card">
|
||||
{% set num_emails = collector.events.events(transport)|length %}
|
||||
{% if num_emails > 1 %}
|
||||
<div class="mailer-email-summary-table-wrapper">
|
||||
<table class="mailer-email-summary-table">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Subject</th>
|
||||
<th>To</th>
|
||||
<th class="visually-hidden">Actions</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for event in collector.events.events(transport) %}
|
||||
<tr class="mailer-email-summary-table-row {{ loop.first ? 'active' }}" data-target="#email-{{ loop.index }}">
|
||||
<td>{{ loop.index }}</td>
|
||||
<td>{{ event.message.getSubject() ?? '(No subject)' }}</td>
|
||||
<td>{{ event.message.getTo()|map(addr => addr.toString())|join(', ')|default('(empty)') }}</td>
|
||||
<td class="visually-hidden"><button class="mailer-email-summary-table-row-button" data-target="#email-{{ loop.index }}">View email details</button></td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
{% for event in collector.events.events(transport) %}
|
||||
<div class="mailer-email-details {{ loop.first ? 'active' }}" id="email-{{ loop.index }}">
|
||||
{{ _self.render_email_details(collector, transport, event.message, event.isQueued, show_transport_name) }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% set event = (collector.events.events(transport)|first) %}
|
||||
{{ _self.render_email_details(collector, transport, event.message, event.isQueued, show_transport_name) }}
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
<div class="metric">
|
||||
<span class="value">{{ events.events|filter(e => not e.isQueued())|length }}</span>
|
||||
<span class="label">Sent</span>
|
||||
</div>
|
||||
</div>
|
||||
{% macro render_email_details(collector, transport, message, message_is_queued, show_transport_name = false) %}
|
||||
{% if show_transport_name %}
|
||||
<p class="mailer-transport-information">
|
||||
<strong>Status:</strong> <span class="badge badge-{{ message_is_queued ? 'warning' : 'success' }}">{{ message_is_queued ? 'Queued' : 'Sent' }}</span>
|
||||
•
|
||||
<strong>Transport:</strong> <code>{{ transport }}</code>
|
||||
</p>
|
||||
{% endif %}
|
||||
|
||||
{% for transport in events.transports %}
|
||||
<div class="card-block">
|
||||
<div class="sf-tabs sf-tabs-sm">
|
||||
{% for event in events.events(transport) %}
|
||||
{% set message = event.message %}
|
||||
<div class="tab">
|
||||
<h3 class="tab-title">Email {{ event.isQueued() ? 'queued' : 'sent via ' ~ transport }}</h3>
|
||||
<div class="tab-content">
|
||||
<div class="card">
|
||||
{% if message.headers is not defined %}
|
||||
{# RawMessage instance #}
|
||||
<div class="card-block">
|
||||
<pre class="prewrap" style="max-height: 600px">{{ message.toString() }}</pre>
|
||||
</div>
|
||||
{% else %}
|
||||
{# Message instance #}
|
||||
<div class="card-block">
|
||||
<div class="sf-tabs sf-tabs-sm">
|
||||
<div class="tab">
|
||||
<h3 class="tab-title">Headers</h3>
|
||||
<div class="tab-content">
|
||||
<span class="label">Subject</span>
|
||||
<h2 class="m-t-10">{{ message.headers.get('subject').bodyAsString() ?? '(empty)' }}</h2>
|
||||
<div class="row">
|
||||
<div class="col col-4">
|
||||
<span class="label">From</span>
|
||||
<pre class="prewrap">{{ (message.headers.get('from').bodyAsString() ?? '(empty)')|replace({'From:': ''}) }}</pre>
|
||||
{% if message.headers is not defined %}
|
||||
{# render the raw message contents #}
|
||||
<a class="mailer-message-download-raw" href="data:application/octet-stream;base64,{{ collector.base64Encode(message.toString()) }}" download="email.eml">
|
||||
{{ source('@WebProfiler/Icon/download.svg') }}
|
||||
Download as EML file
|
||||
</a>
|
||||
|
||||
<span class="label">To</span>
|
||||
<pre class="prewrap">{{ (message.headers.get('to').bodyAsString() ?? '(empty)')|replace({'To:': ''}) }}</pre>
|
||||
</div>
|
||||
<div class="col">
|
||||
<span class="label">Headers</span>
|
||||
<pre class="prewrap">{% for header in message.headers.all|filter(header => (header.name ?? '') not in ['Subject', 'From', 'To']) %}
|
||||
{{- header.toString }}
|
||||
{%~ endfor %}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% if message.htmlBody is defined %}
|
||||
{# Email instance #}
|
||||
{% set htmlBody = message.htmlBody() %}
|
||||
{% if htmlBody is not null %}
|
||||
<div class="tab">
|
||||
<h3 class="tab-title">HTML Preview</h3>
|
||||
<div class="tab-content">
|
||||
<pre class="prewrap" style="max-height: 600px">
|
||||
<iframe
|
||||
src="data:text/html;charset=utf-8;base64,{{ collector.base64Encode(htmlBody) }}"
|
||||
style="height: 80vh;width: 100%;"
|
||||
>
|
||||
</iframe>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab">
|
||||
<h3 class="tab-title">HTML Content</h3>
|
||||
<div class="tab-content">
|
||||
<pre class="prewrap" style="max-height: 600px">
|
||||
{%- if message.htmlCharset() %}
|
||||
{{- htmlBody|convert_encoding('UTF-8', message.htmlCharset()) }}
|
||||
{%- else %}
|
||||
{{- htmlBody }}
|
||||
{%- endif -%}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% set textBody = message.textBody() %}
|
||||
{% if textBody is not null %}
|
||||
<div class="tab">
|
||||
<h3 class="tab-title">Text Content</h3>
|
||||
<div class="tab-content">
|
||||
<pre class="prewrap" style="max-height: 600px">
|
||||
{%- if message.textCharset() %}
|
||||
{{- textBody|convert_encoding('UTF-8', message.textCharset()) }}
|
||||
{%- else %}
|
||||
{{- textBody }}
|
||||
{%- endif -%}
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% for attachment in message.attachments %}
|
||||
<div class="tab">
|
||||
<h3 class="tab-title">Attachment #{{ loop.index }}</h3>
|
||||
<div class="tab-content">
|
||||
<pre class="prewrap" style="max-height: 600px">{{ attachment.toString() }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
<pre class="prewrap" style="max-height: 600px; margin-left: 5px">{{ message.toString() }}</pre>
|
||||
{% else %}
|
||||
<div class="sf-tabs">
|
||||
<div class="tab">
|
||||
<h3 class="tab-title">Email contents</h3>
|
||||
<div class="tab-content">
|
||||
<div class="card-block">
|
||||
<p class="mailer-message-subject">
|
||||
{{ message.getSubject() ?? '(No subject)' }}
|
||||
</p>
|
||||
<div class="mailer-message-headers">
|
||||
<p><strong>From:</strong> {{ message.getFrom()|map(addr => addr.toString())|join(', ')|default('(empty)') }}</p>
|
||||
<p><strong>To:</strong> {{ message.getTo()|map(addr => addr.toString())|join(', ')|default('(empty)') }}</p>
|
||||
{% for header in message.headers.all|filter(header => (header.name ?? '')|lower not in ['subject', 'from', 'to']) %}
|
||||
<p class="mailer-message-header-secondary">{{ header.toString }}</p>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if message.attachments %}
|
||||
<div class="card-block">
|
||||
{% set num_of_attachments = message.attachments|length %}
|
||||
{% set total_attachments_size_in_bytes = message.attachments|reduce((total_size, attachment) => total_size + attachment.body|length, 0) %}
|
||||
<p class="mailer-message-attachments-title">
|
||||
{{ source('@WebProfiler/Icon/attachment.svg') }}
|
||||
Attachments <span>({{ num_of_attachments }} file{{ num_of_attachments > 1 ? 's' }} / {{ _self.render_file_size_humanized(total_attachments_size_in_bytes) }})</span>
|
||||
</p>
|
||||
|
||||
<ul class="mailer-message-attachments-list">
|
||||
{% for attachment in message.attachments %}
|
||||
<li>
|
||||
{{ source('@WebProfiler/Icon/file.svg') }}
|
||||
|
||||
{% if attachment.filename|default %}
|
||||
{{ attachment.filename }}
|
||||
{% else %}
|
||||
<em>(no filename)</em>
|
||||
{% endif %}
|
||||
<div class="tab">
|
||||
<h3 class="tab-title">Parts Hierarchy</h3>
|
||||
<div class="tab-content">
|
||||
<pre class="prewrap" style="max-height: 600px">{{ message.body().asDebugString() }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
<div class="tab">
|
||||
<h3 class="tab-title">Raw</h3>
|
||||
<div class="tab-content">
|
||||
<pre class="prewrap" style="max-height: 600px">{{ message.toString() }}</pre>
|
||||
</div>
|
||||
|
||||
({{ _self.render_file_size_humanized(attachment.body|length) }})
|
||||
|
||||
<a href="data:{{ attachment.contentType|default('application/octet-stream') }};base64,{{ collector.base64Encode(attachment.body) }}" download="{{ attachment.filename|default('attachment') }}">Download</a>
|
||||
</li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="card-block">
|
||||
{% set textBody = message.textBody %}
|
||||
{% set htmlBody = message.htmlBody %}
|
||||
<div class="sf-tabs sf-tabs-sm">
|
||||
<div class="tab {{ not textBody ? 'disabled' }} {{ textBody ? 'active' }}">
|
||||
<h3 class="tab-title">Text content</h3>
|
||||
<div class="tab-content">
|
||||
{% if textBody %}
|
||||
<pre class="mailer-email-body prewrap" style="max-height: 600px">
|
||||
{%- if message.textCharset() %}
|
||||
{{- textBody|convert_encoding('UTF-8', message.textCharset()) }}
|
||||
{%- else %}
|
||||
{{- textBody }}
|
||||
{%- endif -%}
|
||||
</pre>
|
||||
{% else %}
|
||||
<div class="mailer-empty-email-body">
|
||||
<p>The text body is empty.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if htmlBody %}
|
||||
<div class="tab">
|
||||
<h3 class="tab-title">HTML preview</h3>
|
||||
<div class="tab-content">
|
||||
<pre class="prewrap" style="max-height: 600px"><iframe src="data:text/html;charset=utf-8;base64,{{ collector.base64Encode(htmlBody) }}" style="height: 80vh;width: 100%;"></iframe>
|
||||
</pre>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
|
||||
<div class="tab {{ not htmlBody ? 'disabled' }} {{ not textBody and htmlBody ? 'active' }}">
|
||||
<h3 class="tab-title">HTML content</h3>
|
||||
<div class="tab-content">
|
||||
{% if htmlBody %}
|
||||
<pre class="mailer-email-body prewrap" style="max-height: 600px">
|
||||
{%- if message.htmlCharset() %}
|
||||
{{- htmlBody|convert_encoding('UTF-8', message.htmlCharset()) }}
|
||||
{%- else %}
|
||||
{{- htmlBody }}
|
||||
{%- endif -%}
|
||||
</pre>
|
||||
{% else %}
|
||||
<div class="mailer-empty-email-body">
|
||||
<p>The HTML body is empty.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
|
||||
<div class="tab">
|
||||
<h3 class="tab-title">MIME parts</h3>
|
||||
<div class="tab-content">
|
||||
<pre class="prewrap" style="max-height: 600px; margin-left: 5px">{{ message.body().asDebugString() }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="tab">
|
||||
<h3 class="tab-title">Raw Message</h3>
|
||||
<div class="tab-content">
|
||||
<a class="mailer-message-download-raw" href="data:application/octet-stream;base64,{{ collector.base64Encode(message.toString()) }}" download="email.eml">
|
||||
{{ source('@WebProfiler/Icon/download.svg') }}
|
||||
Download as EML file
|
||||
</a>
|
||||
|
||||
<pre class="prewrap" style="max-height: 600px; margin-left: 5px">{{ message.toString() }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro render_file_size_humanized(bytes) %}
|
||||
{%- if bytes < 1000 -%}
|
||||
{{- bytes ~ ' bytes' -}}
|
||||
{%- elseif bytes < 1000 ** 2 -%}
|
||||
{{- (bytes / 1000)|number_format(2) ~ ' kB' -}}
|
||||
{%- else -%}
|
||||
{{- (bytes / 1000 ** 2)|number_format(2) ~ ' MB' -}}
|
||||
{%- endif -%}
|
||||
{% endmacro %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
{% block toolbar %}
|
||||
{% set icon %}
|
||||
{% set status_color = (collector.memory / 1024 / 1024) > 50 ? 'yellow' %}
|
||||
{{ include('@WebProfiler/Icon/memory.svg') }}
|
||||
{{ source('@WebProfiler/Icon/memory.svg') }}
|
||||
<span class="sf-toolbar-value">{{ '%.1f'|format(collector.memory / 1024 / 1024) }}</span>
|
||||
<span class="sf-toolbar-label">MiB</span>
|
||||
{% endset %}
|
||||
|
||||
@@ -1,12 +1,49 @@
|
||||
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
|
||||
|
||||
{% import _self as helper %}
|
||||
{% block head %}
|
||||
{{ parent() }}
|
||||
|
||||
<style>
|
||||
.message-item thead th { position: relative; cursor: pointer; user-select: none; padding-right: 35px; }
|
||||
.message-item tbody tr td:first-child { width: 170px; }
|
||||
|
||||
.message-item .label { float: right; padding: 1px 5px; opacity: .75; margin-left: 5px; }
|
||||
.message-item .toggle-button { position: absolute; right: 6px; top: 6px; opacity: .5; pointer-events: none; color: inherit; }
|
||||
.message-item .icon svg { height: 24px; width: 24px; }
|
||||
.message-item .icon-close svg { transform: rotate(180deg); }
|
||||
|
||||
.message-item .sf-toggle-off .icon-close, .sf-toggle-on .icon-open { display: none; }
|
||||
.message-item .sf-toggle-off .icon-open, .sf-toggle-on .icon-close { display: block; }
|
||||
|
||||
.message-bus .badge.status-some-errors { line-height: 16px; border-bottom: 2px solid #B0413E; }
|
||||
|
||||
.message-item tbody.sf-toggle-content.sf-toggle-visible { display: table-row-group; }
|
||||
|
||||
#collector-content .message-bus .trace {
|
||||
border: var(--border);
|
||||
background: var(--base-0);
|
||||
padding: 10px;
|
||||
margin: 0.5em 0;
|
||||
overflow: auto;
|
||||
}
|
||||
#collector-content .message-bus .trace {
|
||||
font-size: 12px;
|
||||
}
|
||||
#collector-content .message-bus .trace li {
|
||||
margin-bottom: 0;
|
||||
padding: 0;
|
||||
}
|
||||
#collector-content .message-bus .trace li.selected {
|
||||
background: var(--highlight-selected-line);
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block toolbar %}
|
||||
{% if collector.messages|length > 0 %}
|
||||
{% set status_color = collector.exceptionsCount ? 'red' %}
|
||||
{% set icon %}
|
||||
{{ include('@WebProfiler/Icon/messenger.svg') }}
|
||||
{{ source('@WebProfiler/Icon/messenger.svg') }}
|
||||
<span class="sf-toolbar-value">{{ collector.messages|length }}</span>
|
||||
{% endset %}
|
||||
|
||||
@@ -31,7 +68,7 @@
|
||||
|
||||
{% block menu %}
|
||||
<span class="label{{ collector.exceptionsCount ? ' label-status-error' }}{{ collector.messages is empty ? ' disabled' }}">
|
||||
<span class="icon">{{ include('@WebProfiler/Icon/messenger.svg') }}</span>
|
||||
<span class="icon">{{ source('@WebProfiler/Icon/messenger.svg') }}</span>
|
||||
<strong>Messages</strong>
|
||||
{% if collector.exceptionsCount > 0 %}
|
||||
<span class="count">
|
||||
@@ -41,36 +78,16 @@
|
||||
</span>
|
||||
{% endblock %}
|
||||
|
||||
{% block head %}
|
||||
{{ parent() }}
|
||||
<style>
|
||||
.message-item thead th { position: relative; cursor: pointer; user-select: none; padding-right: 35px; }
|
||||
.message-item tbody tr td:first-child { width: 170px; }
|
||||
|
||||
.message-item .label { float: right; padding: 1px 5px; opacity: .75; margin-left: 5px; }
|
||||
.message-item .toggle-button { position: absolute; right: 6px; top: 6px; opacity: .5; pointer-events: none }
|
||||
.message-item .icon svg { height: 24px; width: 24px; }
|
||||
|
||||
.message-item .sf-toggle-off .icon-close, .sf-toggle-on .icon-open { display: none; }
|
||||
.message-item .sf-toggle-off .icon-open, .sf-toggle-on .icon-close { display: block; }
|
||||
|
||||
.message-bus .badge.status-some-errors { line-height: 16px; border-bottom: 2px solid #B0413E; }
|
||||
|
||||
.message-item tbody.sf-toggle-content.sf-toggle-visible { display: table-row-group; }
|
||||
td.message-bus-dispatch-caller { background: #f1f2f3; }
|
||||
.theme-dark td.message-bus-dispatch-caller { background: var(--base-1); }
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block panel %}
|
||||
{% import _self as helper %}
|
||||
|
||||
<h2>Messages</h2>
|
||||
|
||||
{% if collector.messages is empty %}
|
||||
<div class="empty">
|
||||
<div class="empty empty-panel">
|
||||
<p>No messages have been collected.</p>
|
||||
</div>
|
||||
{% elseif 1 == collector.buses|length %}
|
||||
<p class="text-muted">Ordered list of dispatched messages across all your buses</p>
|
||||
{{ _self.render_bus_messages(collector.messages, true) }}
|
||||
{% else %}
|
||||
<div class="sf-tabs message-bus">
|
||||
<div class="tab">
|
||||
@@ -80,7 +97,7 @@
|
||||
|
||||
<div class="tab-content">
|
||||
<p class="text-muted">Ordered list of dispatched messages across all your buses</p>
|
||||
{{ helper.render_bus_messages(messages, true) }}
|
||||
{{ _self.render_bus_messages(messages, true) }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -92,7 +109,7 @@
|
||||
|
||||
<div class="tab-content">
|
||||
<p class="text-muted">Ordered list of messages dispatched on the <code>{{ bus }}</code> bus</p>
|
||||
{{ helper.render_bus_messages(messages) }}
|
||||
{{ _self.render_bus_messages(messages) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
@@ -112,36 +129,33 @@
|
||||
data-toggle-initial="{{ loop.first ? 'display' }}"
|
||||
>
|
||||
<span class="dump-inline">{{ profiler_dump(dispatchCall.message.type) }}</span>
|
||||
{% if showBus %}
|
||||
<span class="label">{{ dispatchCall.bus }}</span>
|
||||
{% endif %}
|
||||
{% if dispatchCall.exception is defined %}
|
||||
<span class="label status-error">exception</span>
|
||||
{% endif %}
|
||||
<a class="toggle-button">
|
||||
<span class="icon icon-close">{{ include('@WebProfiler/images/icon-minus-square.svg') }}</span>
|
||||
<span class="icon icon-open">{{ include('@WebProfiler/images/icon-plus-square.svg') }}</span>
|
||||
</a>
|
||||
<button class="btn btn-link toggle-button" type="button">
|
||||
<span class="icon icon-close">{{ source('@WebProfiler/Icon/chevron-down.svg') }}</span>
|
||||
<span class="icon icon-open">{{ source('@WebProfiler/Icon/chevron-down.svg') }}</span>
|
||||
</button>
|
||||
</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="message-item-{{ discr }}-{{ loop.index0 }}-details" class="sf-toggle-content">
|
||||
<tr>
|
||||
<td colspan="2" class="message-bus-dispatch-caller">
|
||||
<span class="metadata">In
|
||||
{% set caller = dispatchCall.caller %}
|
||||
{% if caller.line %}
|
||||
{% set link = caller.file|file_link(caller.line) %}
|
||||
{% if link %}
|
||||
<a href="{{ link }}" title="{{ caller.file }}">{{ caller.name }}</a>
|
||||
{% else %}
|
||||
<abbr title="{{ caller.file }}">{{ caller.name }}</abbr>
|
||||
{% endif %}
|
||||
<th scope="row" class="font-normal">Caller</th>
|
||||
<td class="message-bus-dispatch-caller">
|
||||
In
|
||||
{% set caller = dispatchCall.caller %}
|
||||
{% if caller.line %}
|
||||
{% set link = caller.file|file_link(caller.line) %}
|
||||
{% if link %}
|
||||
<a href="{{ link }}" title="{{ caller.file }}">{{ caller.name }}</a>
|
||||
{% else %}
|
||||
{{ caller.name }}
|
||||
<abbr title="{{ caller.file }}">{{ caller.name }}</abbr>
|
||||
{% endif %}
|
||||
line <a class="text-small sf-toggle" data-toggle-selector="#sf-trace-{{ discr }}-{{ loop.index0 }}">{{ caller.line }}</a>
|
||||
</span>
|
||||
{% else %}
|
||||
{{ caller.name }}
|
||||
{% endif %}
|
||||
line <button type="button" class="btn-link text-small sf-toggle" data-toggle-selector="#sf-trace-{{ discr }}-{{ loop.index0 }}">{{ caller.line }}</button>
|
||||
|
||||
<div class="hidden" id="sf-trace-{{ discr }}-{{ loop.index0 }}">
|
||||
<div class="trace">
|
||||
@@ -157,27 +171,27 @@
|
||||
</tr>
|
||||
{% if showBus %}
|
||||
<tr>
|
||||
<td class="text-bold">Bus</td>
|
||||
<th scope="row" class="font-normal">Bus</th>
|
||||
<td>{{ dispatchCall.bus }}</td>
|
||||
</tr>
|
||||
{% endif %}
|
||||
<tr>
|
||||
<td class="text-bold">Message</td>
|
||||
<th scope="row" class="font-normal">Message</th>
|
||||
<td>{{ profiler_dump(dispatchCall.message.value, maxDepth=2) }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td class="text-bold">Envelope stamps <span class="text-muted">when dispatching</span></td>
|
||||
<th scope="row" class="font-normal">Envelope stamps <span class="block text-muted">when dispatching</span></th>
|
||||
<td>
|
||||
{% for item in dispatchCall.stamps %}
|
||||
{{ profiler_dump(item) }}
|
||||
{% else %}
|
||||
<span class="text-muted">No items</span>
|
||||
<span class="text-muted font-normal">No items</span>
|
||||
{% endfor %}
|
||||
</td>
|
||||
</tr>
|
||||
{% if dispatchCall.stamps_after_dispatch is defined %}
|
||||
<tr>
|
||||
<td class="text-bold">Envelope stamps <span class="text-muted">after dispatch</span></td>
|
||||
<th scope="row" class="font-normal">Envelope stamps <span class="block text-muted">after dispatch</span></th>
|
||||
<td>
|
||||
{% for item in dispatchCall.stamps_after_dispatch %}
|
||||
{{ profiler_dump(item) }}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
|
||||
{% if events.messages|length %}
|
||||
{% set icon %}
|
||||
{% include('@WebProfiler/Icon/notifier.svg') %}
|
||||
{{ source('@WebProfiler/Icon/notifier.svg') }}
|
||||
<span class="sf-toolbar-value">{{ events.messages|length }}</span>
|
||||
{% endset %}
|
||||
|
||||
@@ -17,7 +17,7 @@
|
||||
|
||||
{% for transport in events.transports %}
|
||||
<div class="sf-toolbar-info-piece">
|
||||
<b>{{ transport }}</b>
|
||||
<b>{{ transport ?: '<em>Empty Transport Name</em>' }}</b>
|
||||
<span class="sf-toolbar-status">{{ events.messages(transport)|length }}</span>
|
||||
</div>
|
||||
{% endfor %}
|
||||
@@ -29,7 +29,7 @@
|
||||
|
||||
{% block head %}
|
||||
{{ parent() }}
|
||||
<style type="text/css">
|
||||
<style>
|
||||
/* utility classes */
|
||||
.m-t-0 { margin-top: 0 !important; }
|
||||
.m-t-10 { margin-top: 10px !important; }
|
||||
@@ -68,7 +68,7 @@
|
||||
{% set events = collector.events %}
|
||||
|
||||
<span class="label {{ events.messages|length ? '' : 'disabled' }}">
|
||||
<span class="icon">{{ include('@WebProfiler/Icon/notifier.svg') }}</span>
|
||||
<span class="icon">{{ source('@WebProfiler/Icon/notifier.svg') }}</span>
|
||||
|
||||
<strong>Notifications</strong>
|
||||
{% if events.messages|length > 0 %}
|
||||
@@ -85,7 +85,7 @@
|
||||
<h2>Notifications</h2>
|
||||
|
||||
{% if not events.messages|length %}
|
||||
<div class="empty">
|
||||
<div class="empty empty-panel">
|
||||
<p>No notifications were sent.</p>
|
||||
</div>
|
||||
{% endif %}
|
||||
@@ -100,7 +100,7 @@
|
||||
</div>
|
||||
|
||||
{% for transport in events.transports %}
|
||||
<h3>{{ transport }}</h3>
|
||||
<h3>{{ transport ?: '<em>Empty Transport Name</em>' }}</h3>
|
||||
|
||||
<div class="card-block">
|
||||
<div class="sf-tabs sf-tabs-sm">
|
||||
@@ -114,31 +114,31 @@
|
||||
<span class="label">Subject</span>
|
||||
<h2 class="m-t-10">{{ message.getSubject() ?? '(empty)' }}</h2>
|
||||
</div>
|
||||
{% if message.getNotification is defined %}
|
||||
{% set notification = message.notification ?? null %}
|
||||
{% if notification %}
|
||||
<div class="card-block">
|
||||
<div class="row">
|
||||
<div class="col">
|
||||
<span class="label">Content</span>
|
||||
<pre class="prewrap">{{ message.getNotification().getContent() ?? '(empty)' }}</pre>
|
||||
<pre class="prewrap">{{ notification.getContent() ?? '(empty)' }}</pre>
|
||||
<span class="label">Importance</span>
|
||||
<pre class="prewrap">{{ message.getNotification().getImportance() }}</pre>
|
||||
<pre class="prewrap">{{ notification.getImportance() }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
<div class="card-block">
|
||||
<div class="sf-tabs sf-tabs-sm">
|
||||
{% if message.getNotification is defined %}
|
||||
{% if notification %}
|
||||
<div class="tab">
|
||||
<h3 class="tab-title">Notification</h3>
|
||||
{% set notification = event.message.getNotification() %}
|
||||
<div class="tab-content">
|
||||
<pre class="prewrap" style="max-height: 600px">
|
||||
{{- 'Subject: ' ~ notification.getSubject() }}<br/>
|
||||
{{- 'Content: ' ~ notification.getContent() }}<br/>
|
||||
{{- 'Importance: ' ~ notification.getImportance() }}<br/>
|
||||
{{- 'Emoji: ' ~ (notification.getEmoji() is empty ? '(empty)' : notification.getEmoji()) }}<br/>
|
||||
{{- 'Exception: ' ~ notification.getException() ?? '(empty)' }}<br/>
|
||||
{{- 'Subject: ' ~ notification.getSubject() }}<br>
|
||||
{{- 'Content: ' ~ notification.getContent() }}<br>
|
||||
{{- 'Importance: ' ~ notification.getImportance() }}<br>
|
||||
{{- 'Emoji: ' ~ (notification.getEmoji() is empty ? '(empty)' : notification.getEmoji()) }}<br>
|
||||
{{- 'Exception: ' ~ notification.getException() ?? '(empty)' }}<br>
|
||||
{{- 'ExceptionAsString: ' ~ (notification.getExceptionAsString() is empty ? '(empty)' : notification.getExceptionAsString()) }}
|
||||
</pre>
|
||||
</div>
|
||||
|
||||
@@ -1,21 +1,43 @@
|
||||
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
|
||||
|
||||
{% block head %}
|
||||
{{ parent() }}
|
||||
|
||||
<style>
|
||||
.empty-query-post-files {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.empty-query-post-files > div {
|
||||
flex: 1;
|
||||
}
|
||||
.empty-query-post-files > div + div {
|
||||
margin-left: 30px;
|
||||
}
|
||||
.empty-query-post-files h3 {
|
||||
margin-top: 0;
|
||||
}
|
||||
.empty-query-post-files .empty {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block toolbar %}
|
||||
{% import _self as helper %}
|
||||
{% set request_handler %}
|
||||
{{ helper.set_handler(collector.controller) }}
|
||||
{{ _self.set_handler(collector.controller) }}
|
||||
{% endset %}
|
||||
|
||||
{% if collector.redirect %}
|
||||
{% set redirect_handler %}
|
||||
{{ helper.set_handler(collector.redirect.controller, collector.redirect.route, 'GET' != collector.redirect.method ? collector.redirect.method) }}
|
||||
{{ _self.set_handler(collector.redirect.controller, collector.redirect.route, 'GET' != collector.redirect.method ? collector.redirect.method) }}
|
||||
{% endset %}
|
||||
{% endif %}
|
||||
|
||||
{% if collector.forwardtoken %}
|
||||
{% set forward_profile = profile.childByToken(collector.forwardtoken) %}
|
||||
{% set forward_handler %}
|
||||
{{ helper.set_handler(forward_profile ? forward_profile.collector('request').controller : 'n/a') }}
|
||||
{{ _self.set_handler(forward_profile ? forward_profile.collector('request').controller : 'n/a') }}
|
||||
{% endset %}
|
||||
{% endif %}
|
||||
|
||||
@@ -24,8 +46,8 @@
|
||||
{% set icon %}
|
||||
<span class="sf-toolbar-status sf-toolbar-status-{{ request_status_code_color }}">{{ collector.statuscode }}</span>
|
||||
{% if collector.route %}
|
||||
{% if collector.redirect %}{{ include('@WebProfiler/Icon/redirect.svg') }}{% endif %}
|
||||
{% if collector.forwardtoken %}{{ include('@WebProfiler/Icon/forward.svg') }}{% endif %}
|
||||
{% if collector.redirect %}<span class="sf-toolbar-request-icon">{{ source('@WebProfiler/Icon/redirect.svg') }}</span>{% endif %}
|
||||
{% if collector.forwardtoken %}<span class="sf-toolbar-request-icon">{{ source('@WebProfiler/Icon/forward.svg') }}</span>{% endif %}
|
||||
<span class="sf-toolbar-label">{{ 'GET' != collector.method ? collector.method }} @</span>
|
||||
<span class="sf-toolbar-value sf-toolbar-info-piece-additional">{{ collector.route }}</span>
|
||||
{% endif %}
|
||||
@@ -99,16 +121,15 @@
|
||||
|
||||
{% block menu %}
|
||||
<span class="label">
|
||||
<span class="icon">{{ include('@WebProfiler/Icon/request.svg') }}</span>
|
||||
<span class="icon">{{ source('@WebProfiler/Icon/request.svg') }}</span>
|
||||
<strong>Request / Response</strong>
|
||||
</span>
|
||||
{% endblock %}
|
||||
|
||||
{% block panel %}
|
||||
{% import _self as helper %}
|
||||
|
||||
{% set controller_name = _self.set_handler(collector.controller) %}
|
||||
<h2>
|
||||
{{ helper.set_handler(collector.controller) }}
|
||||
{{ 'n/a' in controller_name ? 'Request / Response' : controller_name }}
|
||||
</h2>
|
||||
|
||||
<div class="sf-tabs">
|
||||
@@ -116,34 +137,52 @@
|
||||
<h3 class="tab-title">Request</h3>
|
||||
|
||||
<div class="tab-content">
|
||||
<h3>GET Parameters</h3>
|
||||
|
||||
{% if collector.requestquery.all is empty %}
|
||||
<div class="empty">
|
||||
<p>No GET parameters</p>
|
||||
{% set has_no_query_post_or_files = collector.requestquery.all is empty and collector.requestrequest.all is empty and collector.requestfiles is empty %}
|
||||
{% if has_no_query_post_or_files %}
|
||||
<div class="empty-query-post-files" style="display: flex; align-items: stretch">
|
||||
<div>
|
||||
<h3>GET Parameters</h3>
|
||||
<div class="empty"><p>None</p></div>
|
||||
</div>
|
||||
<div>
|
||||
<h3>POST Parameters</h3>
|
||||
<div class="empty"><p>None</p></div>
|
||||
</div>
|
||||
<div>
|
||||
<h3>Uploaded Files</h3>
|
||||
<div class="empty"><p>None</p></div>
|
||||
</div>
|
||||
</div>
|
||||
{% else %}
|
||||
{{ include('@WebProfiler/Profiler/bag.html.twig', { bag: collector.requestquery, maxDepth: 1 }, with_context = false) }}
|
||||
{% endif %}
|
||||
<h3>GET Parameters</h3>
|
||||
|
||||
<h3>POST Parameters</h3>
|
||||
{% if collector.requestquery.all is empty %}
|
||||
<div class="empty">
|
||||
<p>No GET parameters</p>
|
||||
</div>
|
||||
{% else %}
|
||||
{{ include('@WebProfiler/Profiler/bag.html.twig', { bag: collector.requestquery, maxDepth: 1 }, with_context = false) }}
|
||||
{% endif %}
|
||||
|
||||
{% if collector.requestrequest.all is empty %}
|
||||
<div class="empty">
|
||||
<p>No POST parameters</p>
|
||||
</div>
|
||||
{% else %}
|
||||
{{ include('@WebProfiler/Profiler/bag.html.twig', { bag: collector.requestrequest, maxDepth: 1 }, with_context = false) }}
|
||||
{% endif %}
|
||||
<h3>POST Parameters</h3>
|
||||
|
||||
<h4>Uploaded Files</h4>
|
||||
{% if collector.requestrequest.all is empty %}
|
||||
<div class="empty">
|
||||
<p>No POST parameters</p>
|
||||
</div>
|
||||
{% else %}
|
||||
{{ include('@WebProfiler/Profiler/bag.html.twig', { bag: collector.requestrequest, maxDepth: 1 }, with_context = false) }}
|
||||
{% endif %}
|
||||
|
||||
{% if collector.requestfiles is empty %}
|
||||
<div class="empty">
|
||||
<p>No files were uploaded</p>
|
||||
</div>
|
||||
{% else %}
|
||||
{{ include('@WebProfiler/Profiler/bag.html.twig', { bag: collector.requestfiles, maxDepth: 1 }, with_context = false) }}
|
||||
<h4>Uploaded Files</h4>
|
||||
|
||||
{% if collector.requestfiles is empty %}
|
||||
<div class="empty">
|
||||
<p>No files were uploaded</p>
|
||||
</div>
|
||||
{% else %}
|
||||
{{ include('@WebProfiler/Profiler/bag.html.twig', { bag: collector.requestfiles, maxDepth: 1 }, with_context = false) }}
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
|
||||
<h3>Request Attributes</h3>
|
||||
@@ -265,7 +304,7 @@
|
||||
</div>
|
||||
|
||||
<div class="metric">
|
||||
<span class="value">{{ include('@WebProfiler/Icon/' ~ (collector.statelesscheck ? 'yes' : 'no') ~ '.svg') }}</span>
|
||||
<span class="value">{{ source('@WebProfiler/Icon/' ~ (collector.statelesscheck ? 'yes' : 'no') ~ '.svg') }}</span>
|
||||
<span class="label">Stateless check enabled</span>
|
||||
</div>
|
||||
</div>
|
||||
@@ -360,7 +399,7 @@
|
||||
<div class="tab-content">
|
||||
{% for child in profile.children %}
|
||||
<h3>
|
||||
{{ helper.set_handler(child.getcollector('request').controller) }}
|
||||
{{ _self.set_handler(child.getcollector('request').controller) }}
|
||||
<small>(token = <a href="{{ path('_profiler', { token: child.token }) }}">{{ child.token }}</a>)</small>
|
||||
</h3>
|
||||
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
|
||||
{% block menu %}
|
||||
<span class="label">
|
||||
<span class="icon">{{ include('@WebProfiler/Icon/router.svg') }}</span>
|
||||
<span class="icon">{{ source('@WebProfiler/Icon/router.svg') }}</span>
|
||||
<strong>Routing</strong>
|
||||
</span>
|
||||
{% endblock %}
|
||||
|
||||
@@ -0,0 +1,355 @@
|
||||
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
|
||||
|
||||
{% block head %}
|
||||
{{ parent() }}
|
||||
|
||||
<style>
|
||||
#collector-content .sf-serializer {
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
#collector-content .sf-serializer .trace {
|
||||
border: var(--border);
|
||||
background: var(--page-background);
|
||||
padding: 10px;
|
||||
margin: 0.5em 0;
|
||||
overflow: auto;
|
||||
}
|
||||
#collector-content .sf-serializer .trace {
|
||||
font-size: 12px;
|
||||
}
|
||||
#collector-content .sf-serializer .trace li {
|
||||
margin-bottom: 0;
|
||||
padding: 0;
|
||||
}
|
||||
#collector-content .sf-serializer .trace li.selected {
|
||||
background: var(--highlight-selected-line);
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block toolbar %}
|
||||
{% if collector.handledCount > 0 %}
|
||||
{% set icon %}
|
||||
{{ source('@WebProfiler/Icon/serializer.svg') }}
|
||||
<span class="sf-toolbar-value">
|
||||
{{ collector.handledCount }}
|
||||
</span>
|
||||
{% endset %}
|
||||
|
||||
{% set text %}
|
||||
<div class="sf-toolbar-info-piece">
|
||||
<b>Total calls</b>
|
||||
<span class="sf-toolbar-status">{{ collector.handledCount }}</span>
|
||||
</div>
|
||||
<div class="sf-toolbar-info-piece">
|
||||
<b>Total time</b>
|
||||
<span>
|
||||
{{ '%.2f'|format(collector.totalTime * 1000) }} <span class="unit">ms</span>
|
||||
</span>
|
||||
</div>
|
||||
|
||||
<div class="detailed-metrics">
|
||||
<div>
|
||||
<div class="sf-toolbar-info-piece">
|
||||
<b>Serialize</b>
|
||||
<span class="sf-toolbar-status">{{ collector.data.serialize|length }}</span>
|
||||
</div>
|
||||
<div class="sf-toolbar-info-piece">
|
||||
<b>Deserialize</b>
|
||||
<span class="sf-toolbar-status">{{ collector.data.deserialize|length }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="sf-toolbar-info-piece">
|
||||
<b>Encode</b>
|
||||
<span class="sf-toolbar-status">{{ collector.data.encode|length }}</span>
|
||||
</div>
|
||||
<div class="sf-toolbar-info-piece">
|
||||
<b>Decode</b>
|
||||
<span class="sf-toolbar-status">{{ collector.data.decode|length }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="sf-toolbar-info-piece">
|
||||
<b>Normalize</b>
|
||||
<span class="sf-toolbar-status">{{ collector.data.normalize|length }}</span>
|
||||
</div>
|
||||
<div class="sf-toolbar-info-piece">
|
||||
<b>Denormalize</b>
|
||||
<span class="sf-toolbar-status">{{ collector.data.denormalize|length }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
|
||||
{{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { link: profiler_url }) }}
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
|
||||
{% block menu %}
|
||||
<span class="label {{ not collector.handledCount ? 'disabled' }}">
|
||||
<span class="icon">{{ source('@WebProfiler/Icon/serializer.svg') }}</span>
|
||||
<strong>Serializer</strong>
|
||||
</span>
|
||||
{% endblock %}
|
||||
|
||||
{% block panel %}
|
||||
<h2>Serializer</h2>
|
||||
<div class="sf-serializer sf-reset">
|
||||
{% if not collector.handledCount %}
|
||||
<div class="empty empty-panel">
|
||||
<p>Nothing was handled by the serializer.</p>
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="metrics">
|
||||
<div class="metric">
|
||||
<span class="value">{{ collector.handledCount }}</span>
|
||||
<span class="label">Handled</span>
|
||||
</div>
|
||||
|
||||
<div class="metric">
|
||||
<span class="value">{{ '%.2f'|format(collector.totalTime * 1000) }} <span class="unit">ms</span></span>
|
||||
<span class="label">Total time</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sf-tabs">
|
||||
{{ _self.render_serialize_tab(collector.data, true) }}
|
||||
{{ _self.render_serialize_tab(collector.data, false) }}
|
||||
|
||||
{{ _self.render_normalize_tab(collector.data, true) }}
|
||||
{{ _self.render_normalize_tab(collector.data, false) }}
|
||||
|
||||
{{ _self.render_encode_tab(collector.data, true) }}
|
||||
{{ _self.render_encode_tab(collector.data, false) }}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% macro render_serialize_tab(collectorData, serialize) %}
|
||||
{% set data = serialize ? collectorData.serialize : collectorData.deserialize %}
|
||||
{% set cellPrefix = serialize ? 'serialize' : 'deserialize' %}
|
||||
|
||||
<div class="tab {{ not data ? 'disabled' }}">
|
||||
<h3 class="tab-title">{{ serialize ? 'serialize' : 'deserialize' }} <span class="badge">{{ data|length }}</h3>
|
||||
<div class="tab-content">
|
||||
{% if not data|length %}
|
||||
<div class="empty">
|
||||
<p>Nothing was {{ serialize ? 'serialized' : 'deserialized' }}.</p>
|
||||
</div>
|
||||
{% else %}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Data</th>
|
||||
<th>Context</th>
|
||||
<th>Normalizer</th>
|
||||
<th>Encoder</th>
|
||||
<th>Time</th>
|
||||
<th>Caller</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in data %}
|
||||
<tr>
|
||||
<td>{{ _self.render_data_cell(item, loop.index, cellPrefix) }}</td>
|
||||
<td>{{ _self.render_context_cell(item, loop.index, cellPrefix) }}</td>
|
||||
<td>{{ _self.render_normalizer_cell(item, loop.index, cellPrefix) }}</td>
|
||||
<td>{{ _self.render_encoder_cell(item, loop.index, cellPrefix) }}</td>
|
||||
<td>{{ _self.render_time_cell(item) }}</td>
|
||||
<td>{{ _self.render_caller_cell(item, loop.index, cellPrefix) }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro render_caller_cell(item, index, method) %}
|
||||
{% if item.caller is defined %}
|
||||
<span class="metadata">
|
||||
{% set caller = item.caller %}
|
||||
{% if caller.line %}
|
||||
{% set link = caller.file|file_link(caller.line) %}
|
||||
{% if link %}
|
||||
<a href="{{ link }}" title="{{ caller.file }}">{{ caller.name }}</a>
|
||||
{% else %}
|
||||
<abbr title="{{ caller.file }}">{{ caller.name }}</abbr>
|
||||
{% endif %}
|
||||
{% else %}
|
||||
{{ caller.name }}
|
||||
{% endif %}
|
||||
line <a class="text-small sf-toggle" data-toggle-selector="#sf-trace-{{ method }}-{{ index }}">{{ caller.line }}</a>
|
||||
</span>
|
||||
|
||||
<div class="sf-serializer-compact hidden" id="sf-trace-{{ method }}-{{ index }}">
|
||||
<div class="trace">
|
||||
{{ caller.file|file_excerpt(caller.line)|replace({
|
||||
'#DD0000': 'var(--highlight-string)',
|
||||
'#007700': 'var(--highlight-keyword)',
|
||||
'#0000BB': 'var(--highlight-default)',
|
||||
'#FF8000': 'var(--highlight-comment)'
|
||||
})|raw }}
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro render_normalize_tab(collectorData, normalize) %}
|
||||
{% set data = normalize ? collectorData.normalize : collectorData.denormalize %}
|
||||
{% set cellPrefix = normalize ? 'normalize' : 'denormalize' %}
|
||||
|
||||
<div class="tab {{ not data ? 'disabled' }}">
|
||||
<h3 class="tab-title">{{ normalize ? 'normalize' : 'denormalize' }} <span class="badge">{{ data|length }}</h3>
|
||||
<div class="tab-content">
|
||||
{% if not data|length %}
|
||||
<div class="empty">
|
||||
<p>Nothing was {{ normalize ? 'normalized' : 'denormalized' }}.</p>
|
||||
</div>
|
||||
{% else %}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Data</th>
|
||||
<th>Context</th>
|
||||
<th>Normalizer</th>
|
||||
<th>Time</th>
|
||||
<th>Caller</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in data %}
|
||||
<tr>
|
||||
<td>{{ _self.render_data_cell(item, loop.index, cellPrefix) }}</td>
|
||||
<td>{{ _self.render_context_cell(item, loop.index, cellPrefix) }}</td>
|
||||
<td>{{ _self.render_normalizer_cell(item, loop.index, cellPrefix) }}</td>
|
||||
<td>{{ _self.render_time_cell(item) }}</td>
|
||||
<td>{{ _self.render_caller_cell(item, loop.index, cellPrefix) }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro render_encode_tab(collectorData, encode) %}
|
||||
{% set data = encode ? collectorData.encode : collectorData.decode %}
|
||||
{% set cellPrefix = encode ? 'encode' : 'decode' %}
|
||||
|
||||
<div class="tab {{ not data ? 'disabled' }}">
|
||||
<h3 class="tab-title">{{ encode ? 'encode' : 'decode' }} <span class="badge">{{ data|length }}</h3>
|
||||
<div class="tab-content">
|
||||
{% if not data|length %}
|
||||
<div class="empty">
|
||||
<p>Nothing was {{ encode ? 'encoded' : 'decoded' }}.</p>
|
||||
</div>
|
||||
{% else %}
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Data</th>
|
||||
<th>Context</th>
|
||||
<th>Encoder</th>
|
||||
<th>Time</th>
|
||||
<th>Caller</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for item in data %}
|
||||
<tr>
|
||||
<td>{{ _self.render_data_cell(item, loop.index, cellPrefix) }}</td>
|
||||
<td>{{ _self.render_context_cell(item, loop.index, cellPrefix) }}</td>
|
||||
<td>{{ _self.render_encoder_cell(item, loop.index, cellPrefix) }}</td>
|
||||
<td>{{ _self.render_time_cell(item) }}</td>
|
||||
<td>{{ _self.render_caller_cell(item, loop.index, cellPrefix) }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</tbody>
|
||||
</table>
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro render_data_cell(item, index, method) %}
|
||||
{% set data_id = 'data-' ~ method ~ '-' ~ index %}
|
||||
|
||||
<span class="nowrap">{{ item.dataType }}</span>
|
||||
|
||||
<div>
|
||||
<a class="btn btn-link text-small sf-toggle" data-toggle-selector="#{{ data_id }}" data-toggle-alt-content="Hide contents">Show contents</a>
|
||||
<div id="{{ data_id }}" class="context sf-toggle-content sf-toggle-hidden">
|
||||
{{ profiler_dump(item.data) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro render_context_cell(item, index, method) %}
|
||||
{% set context_id = 'context-' ~ method ~ '-' ~ index %}
|
||||
|
||||
{% if item.type %}
|
||||
<span class="nowrap">Type: {{ item.type }}</span>
|
||||
<div class="nowrap">Format: {{ item.format ? item.format : 'none' }}</div>
|
||||
{% else %}
|
||||
<span class="nowrap">Format: {{ item.format ? item.format : 'none' }}</span>
|
||||
{% endif %}
|
||||
|
||||
<div>
|
||||
<a class="btn btn-link text-small sf-toggle" data-toggle-selector="#{{ context_id }}" data-toggle-alt-content="Hide context">Show context</a>
|
||||
<div id="{{ context_id }}" class="context sf-toggle-content sf-toggle-hidden">
|
||||
{{ profiler_dump(item.context) }}
|
||||
</div>
|
||||
</div>
|
||||
{% endmacro %}
|
||||
|
||||
{% macro render_normalizer_cell(item, index, method) %}
|
||||
{% set nested_normalizers_id = 'nested-normalizers-' ~ method ~ '-' ~ index %}
|
||||
|
||||
{% if item.normalizer is defined %}
|
||||
<span class="nowrap"><a href="{{ item.normalizer.file|file_link(item.normalizer.line) }}" title="{{ item.normalizer.file }}">{{ item.normalizer.class }}</a> ({{ '%.2f'|format(item.normalizer.time * 1000) }} ms)</span>
|
||||
{% endif %}
|
||||
|
||||
{% if item.normalization|length > 1 %}
|
||||
<div>
|
||||
<a class="btn btn-link text-small sf-toggle" data-toggle-selector="#{{ nested_normalizers_id }}" data-toggle-alt-content="Hide nested normalizers">Show nested normalizers</a>
|
||||
<div id="{{ nested_normalizers_id }}" class="context sf-toggle-content sf-toggle-hidden">
|
||||
<ul class="text-small" style="line-height:80%;margin-top:10px">
|
||||
{% for normalizer in item.normalization %}
|
||||
<li><span class="nowrap">x{{ normalizer.calls }} <a href="{{ normalizer.file|file_link(normalizer.line) }}" title="{{ normalizer.file }}">{{ normalizer.class }}</a> ({{ '%.2f'|format(normalizer.time * 1000) }} ms)</span></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro render_encoder_cell(item, index, method) %}
|
||||
{% set nested_encoders_id = 'nested-encoders-' ~ method ~ '-' ~ index %}
|
||||
|
||||
{% if item.encoder is defined %}
|
||||
<span class="nowrap"><a href="{{ item.encoder.file|file_link(item.encoder.line) }}" title="{{ item.encoder.file }}">{{ item.encoder.class }}</a> ({{ '%.2f'|format(item.encoder.time * 1000) }} ms)</span>
|
||||
{% endif %}
|
||||
|
||||
{% if item.encoding|length > 1 %}
|
||||
<div>
|
||||
<a class="btn btn-link text-small sf-toggle" data-toggle-selector="#{{ nested_encoders_id }}" data-toggle-alt-content="Hide nested encoders">Show nested encoders</a>
|
||||
<div id="{{ nested_encoders_id }}" class="context sf-toggle-content sf-toggle-hidden">
|
||||
<ul class="text-small" style="line-height:80%;margin-top:10px">
|
||||
{% for encoder in item.encoding %}
|
||||
<li><span class="nowrap">x{{ encoder.calls }} <a href="{{ encoder.file|file_link(encoder.line) }}" title="{{ encoder.file }}">{{ encoder.class }}</a> ({{ '%.2f'|format(encoder.time * 1000) }} ms)</span></li>
|
||||
{% endfor %}
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endmacro %}
|
||||
|
||||
{% macro render_time_cell(item) %}
|
||||
<span class="nowrap">{{ '%.2f'|format(item.time * 1000) }} ms</span>
|
||||
{% endmacro %}
|
||||
@@ -23,7 +23,7 @@
|
||||
margin: 1em 0;
|
||||
width: 100%;
|
||||
background-color: var(--table-background);
|
||||
border: 1px solid var(--table-border);
|
||||
border: 1px solid var(--table-border-color);
|
||||
}
|
||||
|
||||
/* Typography */
|
||||
@@ -44,7 +44,7 @@
|
||||
.timeline-graph .timeline-subrequest,
|
||||
.timeline-graph .timeline-border {
|
||||
fill: none;
|
||||
stroke: var(--table-border);
|
||||
stroke: var(--table-border-color);
|
||||
stroke-width: 1px;
|
||||
}
|
||||
|
||||
@@ -54,7 +54,10 @@
|
||||
}
|
||||
|
||||
.timeline-subrequest-pattern {
|
||||
fill: var(--table-border);
|
||||
fill: var(--gray-200);
|
||||
}
|
||||
.theme-dark .timeline-subrequest-pattern {
|
||||
fill: var(--gray-600);
|
||||
}
|
||||
|
||||
/* Timeline periods */
|
||||
|
||||
@@ -1,6 +1,42 @@
|
||||
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
|
||||
|
||||
{% import _self as helper %}
|
||||
{% block head %}
|
||||
{{ parent() }}
|
||||
|
||||
<style>
|
||||
#timeline-control {
|
||||
background: var(--table-background);
|
||||
box-shadow: var(--shadow);
|
||||
margin: 1em 0;
|
||||
padding: 10px;
|
||||
}
|
||||
#timeline-control label {
|
||||
font-weight: bold;
|
||||
margin-right: 1em;
|
||||
}
|
||||
#timeline-control input {
|
||||
background: var(--metric-value-background);
|
||||
font-size: 16px;
|
||||
padding: 4px;
|
||||
text-align: right;
|
||||
width: 5em;
|
||||
}
|
||||
#timeline-control .help {
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.sf-profiler-timeline .legends {
|
||||
font-size: 12px;
|
||||
line-height: 1.5em;
|
||||
}
|
||||
.sf-profiler-timeline .legends button {
|
||||
color: var(--color-text);
|
||||
}
|
||||
.sf-profiler-timeline + p.help {
|
||||
margin-top: 0;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block toolbar %}
|
||||
{% set has_time_events = collector.events|length > 0 %}
|
||||
@@ -9,7 +45,7 @@
|
||||
{% set status_color = has_time_events and collector.duration > 1000 ? 'yellow' %}
|
||||
|
||||
{% set icon %}
|
||||
{{ include('@WebProfiler/Icon/time.svg') }}
|
||||
{{ source('@WebProfiler/Icon/time.svg') }}
|
||||
<span class="sf-toolbar-value">{{ total_time }}</span>
|
||||
<span class="sf-toolbar-label">ms</span>
|
||||
{% endset %}
|
||||
@@ -30,7 +66,7 @@
|
||||
|
||||
{% block menu %}
|
||||
<span class="label">
|
||||
<span class="icon">{{ include('@WebProfiler/Icon/time.svg') }}</span>
|
||||
<span class="icon">{{ source('@WebProfiler/Icon/time.svg') }}</span>
|
||||
<strong>Performance</strong>
|
||||
</span>
|
||||
{% endblock %}
|
||||
@@ -40,17 +76,21 @@
|
||||
<h2>Performance metrics</h2>
|
||||
|
||||
<div class="metrics">
|
||||
<div class="metric">
|
||||
<span class="value">{{ '%.0f'|format(collector.duration) }} <span class="unit">ms</span></span>
|
||||
<span class="label">Total execution time</span>
|
||||
</div>
|
||||
<div class="metric-group">
|
||||
<div class="metric">
|
||||
<span class="value">{{ '%.0f'|format(collector.duration) }} <span class="unit">ms</span></span>
|
||||
<span class="label">Total execution time</span>
|
||||
</div>
|
||||
|
||||
<div class="metric">
|
||||
<span class="value">{{ '%.0f'|format(collector.inittime) }} <span class="unit">ms</span></span>
|
||||
<span class="label">Symfony initialization</span>
|
||||
<div class="metric">
|
||||
<span class="value">{{ '%.0f'|format(collector.inittime) }} <span class="unit">ms</span></span>
|
||||
<span class="label">Symfony initialization</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{% if profile.collectors.memory %}
|
||||
<div class="metric-divider"></div>
|
||||
|
||||
<div class="metric">
|
||||
<span class="value">{{ '%.2f'|format(profile.collectors.memory.memory / 1024 / 1024) }} <span class="unit">MiB</span></span>
|
||||
<span class="label">Peak memory usage</span>
|
||||
@@ -60,23 +100,20 @@
|
||||
{% if profile.children|length > 0 %}
|
||||
<div class="metric-divider"></div>
|
||||
|
||||
<div class="metric">
|
||||
<span class="value">{{ profile.children|length }}</span>
|
||||
<span class="label">Sub-Request{{ profile.children|length > 1 ? 's' }}</span>
|
||||
</div>
|
||||
<div class="metric-group">
|
||||
<div class="metric">
|
||||
<span class="value">{{ profile.children|length }}</span>
|
||||
<span class="label">Sub-{{ profile_type|title }}{{ profile.children|length > 1 ? 's' }}</span>
|
||||
</div>
|
||||
|
||||
{% if has_time_events %}
|
||||
{% set subrequests_time = 0 %}
|
||||
{% for child in profile.children %}
|
||||
{% set subrequests_time = subrequests_time + child.getcollector('time').events.__section__.duration %}
|
||||
{% endfor %}
|
||||
{% else %}
|
||||
{% set subrequests_time = 'n/a' %}
|
||||
{% endif %}
|
||||
{% set subrequests_time = has_time_events
|
||||
? profile.children|reduce((total, child) => total + child.getcollector('time').events.__section__.duration, 0)
|
||||
: 'n/a' %}
|
||||
|
||||
<div class="metric">
|
||||
<span class="value">{{ subrequests_time }} <span class="unit">ms</span></span>
|
||||
<span class="label">Sub-Request{{ profile.children|length > 1 ? 's' }} time</span>
|
||||
<div class="metric">
|
||||
<span class="value">{{ subrequests_time }} <span class="unit">ms</span></span>
|
||||
<span class="label">Sub-{{ profile_type|title }}{{ profile.children|length > 1 ? 's' }} time</span>
|
||||
</div>
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -84,7 +121,7 @@
|
||||
<h2>Execution timeline</h2>
|
||||
|
||||
{% if not collector.isStopwatchInstalled() %}
|
||||
<div class="empty">
|
||||
<div class="empty empty-panel">
|
||||
<p>The Stopwatch component is not installed. If you want to see timing events, run: <code>composer require symfony/stopwatch</code>.</p>
|
||||
</div>
|
||||
{% elseif collector.events is empty %}
|
||||
@@ -106,24 +143,24 @@
|
||||
|
||||
{% if profile.parent %}
|
||||
<h3 class="dump-inline">
|
||||
Sub-Request {{ profiler_dump(profile.getcollector('request').requestattributes.get('_controller')) }}
|
||||
Sub-{{ profile_type|title }} {{ profiler_dump(profile.getcollector('request').requestattributes.get('_controller')) }}
|
||||
<small>
|
||||
{{ collector.events.__section__.duration }} ms
|
||||
<a class="newline" href="{{ path('_profiler', { token: profile.parent.token, panel: 'time' }) }}">Return to parent request</a>
|
||||
<a class="newline" href="{{ path('_profiler', { token: profile.parent.token, panel: 'time' }) }}">Return to parent {{ profile_type }}</a>
|
||||
</small>
|
||||
</h3>
|
||||
{% elseif profile.children|length > 0 %}
|
||||
<h3>
|
||||
Main Request <small>{{ collector.events.__section__.duration }} ms</small>
|
||||
Main {{ profile_type|title }} <small>{{ collector.events.__section__.duration }} ms</small>
|
||||
</h3>
|
||||
{% endif %}
|
||||
|
||||
{{ helper.display_timeline(token, collector.events, collector.events.__section__.origin) }}
|
||||
{{ _self.display_timeline(token, collector.events, collector.events.__section__.origin) }}
|
||||
|
||||
{% if profile.children|length %}
|
||||
<p class="help">Note: sections with a striped background correspond to sub-requests.</p>
|
||||
<p class="help">Note: sections with a striped background correspond to sub-{{ profile_type }}s.</p>
|
||||
|
||||
<h3>Sub-requests <small>({{ profile.children|length }})</small></h3>
|
||||
<h3>Sub-{{ profile_type }}s <small>({{ profile.children|length }})</small></h3>
|
||||
|
||||
{% for child in profile.children %}
|
||||
{% set events = child.getcollector('time').events %}
|
||||
@@ -132,7 +169,7 @@
|
||||
<small>{{ events.__section__.duration }} ms</small>
|
||||
</h4>
|
||||
|
||||
{{ helper.display_timeline(child.token, events, collector.events.__section__.origin) }}
|
||||
{{ _self.display_timeline(child.token, events, collector.events.__section__.origin) }}
|
||||
{% endfor %}
|
||||
{% endif %}
|
||||
|
||||
@@ -143,22 +180,21 @@
|
||||
</pattern>
|
||||
</defs>
|
||||
</svg>
|
||||
<style type="text/css">
|
||||
{% include '@WebProfiler/Collector/time.css.twig' %}
|
||||
<style>
|
||||
{{ include('@WebProfiler/Collector/time.css.twig') }}
|
||||
</style>
|
||||
<script>
|
||||
{% include '@WebProfiler/Collector/time.js' %}
|
||||
{{ source('@WebProfiler/Collector/time.js') }}
|
||||
</script>
|
||||
{% endblock %}
|
||||
|
||||
{% macro dump_request_data(token, events, origin) %}
|
||||
{% autoescape 'js' %}
|
||||
{% from _self import dump_events %}
|
||||
{
|
||||
id: "{{ token }}",
|
||||
left: {{ "%F"|format(events.__section__.origin - origin) }},
|
||||
end: "{{ '%F'|format(events.__section__.endtime) }}",
|
||||
events: [ {{ dump_events(events) }} ],
|
||||
events: [ {{ _self.dump_events(events) }} ],
|
||||
}
|
||||
{% endautoescape %}
|
||||
{% endmacro %}
|
||||
@@ -193,7 +229,6 @@
|
||||
{% endmacro %}
|
||||
|
||||
{% macro display_timeline(token, events, origin) %}
|
||||
{% import _self as helper %}
|
||||
<div class="sf-profiler-timeline">
|
||||
<div id="legend-{{ token }}" class="legends"></div>
|
||||
<svg id="timeline-{{ token }}" class="timeline-graph"></svg>
|
||||
@@ -206,7 +241,7 @@
|
||||
new SvgRenderer(document.getElementById('timeline-{{ token }}')),
|
||||
new Legend(document.getElementById('legend-{{ token }}'), theme),
|
||||
document.getElementById('threshold'),
|
||||
{{ helper.dump_request_data(token, events, origin) }}
|
||||
{{ _self.dump_request_data(token, events, origin) }}
|
||||
);
|
||||
});
|
||||
{% endautoescape %}</script>
|
||||
|
||||
@@ -406,23 +406,23 @@ class SvgRenderer {
|
||||
class Theme {
|
||||
constructor(element) {
|
||||
this.reservedCategoryColors = {
|
||||
'default': '#777',
|
||||
'section': '#999',
|
||||
'event_listener': '#00b8f5',
|
||||
'template': '#66cc00',
|
||||
'doctrine': '#ff6633',
|
||||
'messenger_middleware': '#bdb81e',
|
||||
'controller.argument_value_resolver': '#8c5de6',
|
||||
'http_client': '#ffa333',
|
||||
'default': '#737373',
|
||||
'section': '#a3a3a3',
|
||||
'event_listener': '#54aeff',
|
||||
'template': '#4ac26b',
|
||||
'doctrine': '#fd8c73',
|
||||
'messenger_middleware': '#ff8182',
|
||||
'controller.argument_value_resolver': '#c297ff',
|
||||
'http_client': '#d4a72c',
|
||||
};
|
||||
|
||||
this.customCategoryColors = [
|
||||
'#dbab09', // dark yellow
|
||||
'#ea4aaa', // pink
|
||||
'#964b00', // brown
|
||||
'#22863a', // dark green
|
||||
'#0366d6', // dark blue
|
||||
'#17a2b8', // teal
|
||||
'#d4a72c', // dark yellow
|
||||
'#ffaba8', // light red
|
||||
'#e6af05', // yellow
|
||||
'#6fdd8b', // light green
|
||||
'#76e3ea', // cyan
|
||||
'#a475f9', // light purple
|
||||
];
|
||||
|
||||
this.getCategoryColor = this.getCategoryColor.bind(this);
|
||||
|
||||
@@ -1,11 +1,9 @@
|
||||
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
|
||||
|
||||
{% import _self as helper %}
|
||||
|
||||
{% block toolbar %}
|
||||
{% if collector.messages|length %}
|
||||
{% set icon %}
|
||||
{{ include('@WebProfiler/Icon/translation.svg') }}
|
||||
{{ source('@WebProfiler/Icon/translation.svg') }}
|
||||
{% set status_color = collector.countMissings ? 'red' : collector.countFallbacks ? 'yellow' %}
|
||||
{% set error_count = collector.countMissings + collector.countFallbacks %}
|
||||
<span class="sf-toolbar-value">{{ error_count ?: collector.countDefines }}</span>
|
||||
@@ -44,7 +42,7 @@
|
||||
|
||||
{% block menu %}
|
||||
<span class="label label-status-{{ collector.countMissings ? 'error' : collector.countFallbacks ? 'warning' }} {{ collector.messages is empty ? 'disabled' }}">
|
||||
<span class="icon">{{ include('@WebProfiler/Icon/translation.svg') }}</span>
|
||||
<span class="icon">{{ source('@WebProfiler/Icon/translation.svg') }}</span>
|
||||
<strong>Translation</strong>
|
||||
{% if collector.countMissings or collector.countFallbacks %}
|
||||
{% set error_count = collector.countMissings + collector.countFallbacks %}
|
||||
@@ -72,7 +70,7 @@
|
||||
<h2>Messages</h2>
|
||||
|
||||
{% if collector.messages is empty %}
|
||||
<div class="empty">
|
||||
<div class="empty empty-panel">
|
||||
<p>No translations have been called.</p>
|
||||
</div>
|
||||
{% else %}
|
||||
@@ -105,7 +103,7 @@
|
||||
</div>
|
||||
{% else %}
|
||||
{% block defined_messages %}
|
||||
{{ helper.render_table(messages_defined) }}
|
||||
{{ _self.render_table(messages_defined) }}
|
||||
{% endblock %}
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -126,7 +124,7 @@
|
||||
</div>
|
||||
{% else %}
|
||||
{% block fallback_messages %}
|
||||
{{ helper.render_table(messages_fallback, true) }}
|
||||
{{ _self.render_table(messages_fallback, true) }}
|
||||
{% endblock %}
|
||||
{% endif %}
|
||||
</div>
|
||||
@@ -148,29 +146,27 @@
|
||||
</div>
|
||||
{% else %}
|
||||
{% block missing_messages %}
|
||||
{{ helper.render_table(messages_missing) }}
|
||||
{{ _self.render_table(messages_missing) }}
|
||||
{% endblock %}
|
||||
{% endif %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>Sfjs.createFilters();</script>
|
||||
|
||||
{% endblock messages %}
|
||||
{% endif %}
|
||||
|
||||
{% endblock %}
|
||||
|
||||
{% macro render_table(messages, is_fallback) %}
|
||||
<table data-filters>
|
||||
<table>
|
||||
<thead>
|
||||
<tr>
|
||||
<th data-filter="locale">Locale</th>
|
||||
<th>Locale</th>
|
||||
{% if is_fallback %}
|
||||
<th>Fallback locale</th>
|
||||
{% endif %}
|
||||
<th data-filter="domain">Domain</th>
|
||||
<th>Domain</th>
|
||||
<th>Times used</th>
|
||||
<th>Message ID</th>
|
||||
<th>Message Preview</th>
|
||||
@@ -178,7 +174,7 @@
|
||||
</thead>
|
||||
<tbody>
|
||||
{% for message in messages %}
|
||||
<tr data-filter-locale="{{ message.locale }}" data-filter-domain="{{ message.domain }}">
|
||||
<tr>
|
||||
<td class="font-normal text-small nowrap">{{ message.locale }}</td>
|
||||
{% if is_fallback %}
|
||||
<td class="font-normal text-small nowrap">{{ message.fallbackLocale|default('-') }}</td>
|
||||
@@ -186,7 +182,7 @@
|
||||
<td class="font-normal text-small text-bold nowrap">{{ message.domain }}</td>
|
||||
<td class="font-normal text-small nowrap">{{ message.count }}</td>
|
||||
<td>
|
||||
<span class="nowrap">{{ message.id }}</span>
|
||||
<span class="{{ message.id|length < 64 ? 'nowrap' }}">{{ message.id }}</span>
|
||||
|
||||
{% if message.transChoiceNumber is not null %}
|
||||
<small class="newline">(pluralization is used)</small>
|
||||
|
||||
@@ -1,14 +1,66 @@
|
||||
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
|
||||
|
||||
{% block head %}
|
||||
{{ parent() }}
|
||||
|
||||
<style>
|
||||
#twig-dump pre {
|
||||
font-size: var(--font-size-monospace);
|
||||
line-height: 1.7;
|
||||
background-color: var(--page-background);
|
||||
border: var(--border);
|
||||
border-radius: 6px;
|
||||
padding: 15px;
|
||||
box-shadow: 0 0 1px rgba(128, 128, 128, .2);
|
||||
}
|
||||
#twig-dump span {
|
||||
border-radius: 2px;
|
||||
padding: 1px 2px;
|
||||
}
|
||||
#twig-dump .status-error { background: transparent; color: var(--color-error); }
|
||||
#twig-dump .status-warning { background: rgba(240, 181, 24, 0.3); }
|
||||
#twig-dump .status-success { background: rgba(100, 189, 99, 0.2); }
|
||||
#twig-dump .status-info { background: var(--info-background); }
|
||||
.theme-dark #twig-dump .status-warning { color: var(--yellow-200); }
|
||||
.theme-dark #twig-dump .status-success { color: var(--green-200); }
|
||||
|
||||
#twig-table tbody td {
|
||||
position: relative;
|
||||
}
|
||||
#twig-table tbody td div {
|
||||
margin: 0;
|
||||
}
|
||||
#twig-table .template-file-path {
|
||||
color: var(--color-muted);
|
||||
display: block;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block toolbar %}
|
||||
{% set time = collector.templatecount ? '%0.0f'|format(collector.time) : 'n/a' %}
|
||||
{% set icon %}
|
||||
{{ include('@WebProfiler/Icon/twig.svg') }}
|
||||
{{ source('@WebProfiler/Icon/twig.svg') }}
|
||||
<span class="sf-toolbar-value">{{ time }}</span>
|
||||
<span class="sf-toolbar-label">ms</span>
|
||||
{% endset %}
|
||||
|
||||
{% set text %}
|
||||
{% set template = collector.templates|keys|first %}
|
||||
{% set file = collector.templatePaths[template]|default(false) %}
|
||||
{% set link = file ? file|file_link(1) : false %}
|
||||
<div class="sf-toolbar-info-piece">
|
||||
<b>Entry View</b>
|
||||
<span>
|
||||
{% if link %}
|
||||
<a href="{{ link }}" title="{{ file }}">
|
||||
{{ template }}
|
||||
</a>
|
||||
{% else %}
|
||||
{{ template }}
|
||||
{% endif %}
|
||||
</span>
|
||||
</div>
|
||||
<div class="sf-toolbar-info-piece">
|
||||
<b>Render Time</b>
|
||||
<span>{{ time }} ms</span>
|
||||
@@ -32,7 +84,7 @@
|
||||
|
||||
{% block menu %}
|
||||
<span class="label {{ 0 == collector.templateCount ? 'disabled' }}">
|
||||
<span class="icon">{{ include('@WebProfiler/Icon/twig.svg') }}</span>
|
||||
<span class="icon">{{ source('@WebProfiler/Icon/twig.svg') }}</span>
|
||||
<strong>Twig</strong>
|
||||
</span>
|
||||
{% endblock %}
|
||||
@@ -41,8 +93,8 @@
|
||||
{% if collector.templatecount == 0 %}
|
||||
<h2>Twig</h2>
|
||||
|
||||
<div class="empty">
|
||||
<p>No Twig templates were rendered for this request.</p>
|
||||
<div class="empty empty-panel">
|
||||
<p>No Twig templates were rendered.</p>
|
||||
</div>
|
||||
{% else %}
|
||||
<h2>Twig Metrics</h2>
|
||||
@@ -53,19 +105,23 @@
|
||||
<span class="label">Render time</span>
|
||||
</div>
|
||||
|
||||
<div class="metric">
|
||||
<span class="value">{{ collector.templatecount }}</span>
|
||||
<span class="label">Template calls</span>
|
||||
</div>
|
||||
<div class="metric-divider"></div>
|
||||
|
||||
<div class="metric">
|
||||
<span class="value">{{ collector.blockcount }}</span>
|
||||
<span class="label">Block calls</span>
|
||||
</div>
|
||||
<div class="metric-group">
|
||||
<div class="metric">
|
||||
<span class="value">{{ collector.templatecount }}</span>
|
||||
<span class="label">Template calls</span>
|
||||
</div>
|
||||
|
||||
<div class="metric">
|
||||
<span class="value">{{ collector.macrocount }}</span>
|
||||
<span class="label">Macro calls</span>
|
||||
<div class="metric">
|
||||
<span class="value">{{ collector.blockcount }}</span>
|
||||
<span class="label">Block calls</span>
|
||||
</div>
|
||||
|
||||
<div class="metric">
|
||||
<span class="value">{{ collector.macrocount }}</span>
|
||||
<span class="label">Macro calls</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -85,17 +141,14 @@
|
||||
<tbody>
|
||||
{% for template, count in collector.templates %}
|
||||
<tr>
|
||||
{%- set file = collector.templatePaths[template]|default(false) -%}
|
||||
{%- set link = file ? file|file_link(1) : false -%}
|
||||
<td>
|
||||
<span class="sf-icon icon-twig">{{ include('@WebProfiler/Icon/twig.svg') }}</span>
|
||||
{% set file = collector.templatePaths[template]|default(false) %}
|
||||
{% set link = file ? file|file_link(1) : false %}
|
||||
<td class="font-normal">
|
||||
{% if link %}
|
||||
<a href="{{ link }}" title="{{ file }}">{{ template }}</a>
|
||||
<div>
|
||||
<a class="text-muted" href="{{ link }}" title="{{ file }}">
|
||||
{{ file|file_relative|default(file) }}
|
||||
</a>
|
||||
</div>
|
||||
<a href="{{ link }}" title="{{ file }}" class="stretched-link">
|
||||
{{ template }}
|
||||
<span class="template-file-path">{{ file|file_relative|default(file) }}</span>
|
||||
</a>
|
||||
{% else %}
|
||||
{{ template }}
|
||||
{% endif %}
|
||||
|
||||
@@ -1,10 +1,39 @@
|
||||
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
|
||||
|
||||
{% block head %}
|
||||
{{ parent() }}
|
||||
|
||||
<style>
|
||||
#collector-content .sf-validator {
|
||||
margin-bottom: 2em;
|
||||
}
|
||||
|
||||
#collector-content .sf-validator .sf-validator-context,
|
||||
#collector-content .sf-validator .trace {
|
||||
border: var(--border);
|
||||
background: var(--base-0);
|
||||
padding: 10px;
|
||||
margin: 0.5em 0;
|
||||
overflow: auto;
|
||||
}
|
||||
#collector-content .sf-validator .trace {
|
||||
font-size: 12px;
|
||||
}
|
||||
#collector-content .sf-validator .trace li {
|
||||
margin-bottom: 0;
|
||||
padding: 0;
|
||||
}
|
||||
#collector-content .sf-validator .trace li.selected {
|
||||
background: var(--highlight-selected-line);
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
{% block toolbar %}
|
||||
{% if collector.violationsCount > 0 or collector.calls|length %}
|
||||
{% set status_color = collector.violationsCount ? 'red' %}
|
||||
{% set icon %}
|
||||
{{ include('@WebProfiler/Icon/validator.svg') }}
|
||||
{{ source('@WebProfiler/Icon/validator.svg') }}
|
||||
<span class="sf-toolbar-value">
|
||||
{{ collector.violationsCount ?: collector.calls|length }}
|
||||
</span>
|
||||
@@ -27,7 +56,7 @@
|
||||
|
||||
{% block menu %}
|
||||
<span class="label {{- collector.violationsCount ? ' label-status-error' }} {{ collector.calls is empty ? 'disabled' }}">
|
||||
<span class="icon">{{ include('@WebProfiler/Icon/validator.svg') }}</span>
|
||||
<span class="icon">{{ source('@WebProfiler/Icon/validator.svg') }}</span>
|
||||
<strong>Validator</strong>
|
||||
{% if collector.violationsCount > 0 %}
|
||||
<span class="count">
|
||||
@@ -54,7 +83,7 @@
|
||||
{% else %}
|
||||
{{ caller.name }}
|
||||
{% endif %}
|
||||
line <a class="text-small sf-toggle" data-toggle-selector="#sf-trace-{{ loop.index0 }}">{{ caller.line }}</a> (<a class="text-small sf-toggle" data-toggle-selector="#sf-context-{{ loop.index0 }}">context</a>):
|
||||
line <button type="button" class="btn-link text-small sf-toggle" data-toggle-selector="#sf-trace-{{ loop.index0 }}">{{ caller.line }}</button> (<button type="button" class="btn-link text-small sf-toggle" data-toggle-selector="#sf-context-{{ loop.index0 }}">context</button>):
|
||||
</span>
|
||||
|
||||
<div class="sf-validator-compact hidden" id="sf-trace-{{ loop.index0 }}">
|
||||
@@ -96,8 +125,8 @@
|
||||
{% endif %}
|
||||
</div>
|
||||
{% else %}
|
||||
<div class="empty">
|
||||
<p>No calls to the validator were collected during this request.</p>
|
||||
<div class="empty empty-panel">
|
||||
<p>No calls to the validator were collected.</p>
|
||||
</div>
|
||||
{% endfor %}
|
||||
{% endblock %}
|
||||
|
||||
@@ -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