migration symfony 5 4 (#300)

* symfony 5.4 (diff dev)

* symfony 5.4 (working)

* symfony 5.4 (update autoload)

* symfony 5.4 (remove swiftmailer mailer implementation)

* symfony 5.4 (php doc and split Global accessor class)


### Impacted packages:

composer require php:">=7.2.5 <8.0.0" symfony/console:5.4.* symfony/dotenv:5.4.* symfony/framework-bundle:5.4.* symfony/twig-bundle:5.4.* symfony/yaml:5.4.* --update-with-dependencies

composer require symfony/stopwatch:5.4.* symfony/web-profiler-bundle:5.4.* --dev --update-with-dependencies
This commit is contained in:
bdalsass
2022-06-16 09:13:24 +02:00
committed by GitHub
parent abb13b70b9
commit 79da71ecf8
2178 changed files with 87439 additions and 59451 deletions

View File

@@ -8,18 +8,22 @@
{% set text %}
<div class="sf-toolbar-info-piece">
<b class="sf-toolbar-ajax-info"></b>
<span class="sf-toolbar-header">
<b class="sf-toolbar-ajax-info"></b>
<b class="sf-toolbar-action">(<a class="sf-toolbar-ajax-clear" href="javascript:void(0);">Clear</a>)</b>
</span>
</div>
<div class="sf-toolbar-info-piece">
<table class="sf-toolbar-ajax-requests">
<thead>
<tr>
<th>#</th>
<th>Profile</th>
<th>Method</th>
<th>Type</th>
<th>Status</th>
<th>URL</th>
<th>Time</th>
<th>Profile</th>
</tr>
</thead>
<tbody class="sf-toolbar-ajax-request-list"></tbody>

View File

@@ -108,9 +108,9 @@
<div class="metric">
<span class="value">
{% if key == 'time' %}
{{ '%0.2f'|format(1000 * value.value) }} <span class="unit">ms</span>
{{ '%0.2f'|format(1000 * value) }} <span class="unit">ms</span>
{% elseif key == 'hit_read_ratio' %}
{{ value.value ?? 0 }} <span class="unit">%</span>
{{ value ?? 0 }} <span class="unit">%</span>
{% else %}
{{ value }}
{% endif %}

View File

@@ -19,26 +19,14 @@
{% endif %}
{% set icon %}
{% if collector.applicationname %}
<span class="sf-toolbar-label">{{ collector.applicationname }}</span>
<span class="sf-toolbar-value">{{ collector.applicationversion }}</span>
{% elseif collector.symfonyState is defined %}
<span class="sf-toolbar-label">
{{ include('@WebProfiler/Icon/symfony.svg') }}
</span>
<span class="sf-toolbar-value">{{ collector.symfonyversion }}</span>
{% endif %}
<span class="sf-toolbar-label">
{{ include('@WebProfiler/Icon/symfony.svg') }}
</span>
<span class="sf-toolbar-value">{{ collector.symfonyState is defined ? collector.symfonyversion : 'n/a' }}</span>
{% endset %}
{% set text %}
<div class="sf-toolbar-info-group">
{% if collector.applicationname %}
<div class="sf-toolbar-info-piece">
<b>{{ collector.applicationname }}</b>
<span>{{ collector.applicationversion }}</span>
</div>
{% endif %}
<div class="sf-toolbar-info-piece">
<b>Profiler token</b>
<span>
@@ -50,13 +38,6 @@
</span>
</div>
{% if 'n/a' is not same as(collector.appname) %}
<div class="sf-toolbar-info-piece">
<b>Kernel name</b>
<span>{{ collector.appname }}</span>
</div>
{% endif %}
{% if 'n/a' is not same as(collector.env) %}
<div class="sf-toolbar-info-piece">
<b>Environment</b>
@@ -83,9 +64,9 @@
<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' : 'red' }}">xdebug</span>
<span class="sf-toolbar-status sf-toolbar-status-{{ collector.hasapcu ? 'green' : 'red' }}">APCu</span>
<span class="sf-toolbar-status sf-toolbar-status-{{ collector.haszendopcache ? 'green' : 'red' }}">OPcache</span>
<span class="sf-toolbar-status sf-toolbar-status-{{ collector.hasxdebug ? 'green' : 'gray' }}">xdebug {{ collector.hasxdebug ? '✓' : '✗' }}</span>
<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>
<div class="sf-toolbar-info-piece">
@@ -99,15 +80,9 @@
<div class="sf-toolbar-info-piece">
<b>Resources</b>
<span>
{% if 'Silex' == collector.applicationname %}
<a href="https://silex.symfony.com/documentation" rel="help">
Read Silex Docs
</a>
{% else %}
<a href="https://symfony.com/doc/{{ collector.symfonyversion }}/index.html" rel="help">
Read Symfony {{ collector.symfonyversion }} Docs
</a>
{% endif %}
<a href="https://symfony.com/doc/{{ collector.symfonyversion }}/index.html" rel="help">
Read Symfony {{ collector.symfonyversion }} Docs
</a>
</span>
</div>
<div class="sf-toolbar-info-piece">
@@ -126,88 +101,63 @@
{% endblock %}
{% block menu %}
<span class="label label-status-{{ collector.symfonyState == 'eol' ? 'red' : collector.symfonyState in ['eom', 'dev'] ? 'yellow' : '' }}">
<span class="label label-status-{{ collector.symfonyState == 'eol' ? 'red' : collector.symfonyState in ['eom', 'dev'] ? 'yellow' }}">
<span class="icon">{{ include('@WebProfiler/Icon/config.svg') }}</span>
<strong>Configuration</strong>
</span>
{% endblock %}
{% block panel %}
{% if collector.applicationname %}
{# this application is not the Symfony framework #}
<h2>Project Configuration</h2>
<h2>Symfony Configuration</h2>
<div class="metrics">
<div class="metric">
<span class="value">{{ collector.applicationname }}</span>
<span class="label">Application name</span>
</div>
<div class="metric">
<span class="value">{{ collector.applicationversion }}</span>
<span class="label">Application version</span>
</div>
<div class="metrics">
<div class="metric">
<span class="value">{{ collector.symfonyversion }}</span>
<span class="label">Symfony version</span>
</div>
<p>
Based on <a class="text-bold" href="https://symfony.com">Symfony {{ collector.symfonyversion }}</a>
</p>
{% else %}
<h2>Symfony Configuration</h2>
<div class="metrics">
{% if 'n/a' is not same as(collector.env) %}
<div class="metric">
<span class="value">{{ collector.symfonyversion }}</span>
<span class="label">Symfony version</span>
<span class="value">{{ collector.env }}</span>
<span class="label">Environment</span>
</div>
{% endif %}
{% if 'n/a' != collector.appname %}
<div class="metric">
<span class="value">{{ collector.appname }}</span>
<span class="label">Application name</span>
</div>
{% endif %}
{% if 'n/a' is not same as(collector.debug) %}
<div class="metric">
<span class="value">{{ collector.debug ? 'enabled' : 'disabled' }}</span>
<span class="label">Debug</span>
</div>
{% endif %}
</div>
{% if 'n/a' != collector.env %}
<div class="metric">
<span class="value">{{ collector.env }}</span>
<span class="label">Environment</span>
</div>
{% endif %}
{% if 'n/a' != collector.debug %}
<div class="metric">
<span class="value">{{ collector.debug ? 'enabled' : 'disabled' }}</span>
<span class="label">Debug</span>
</div>
{% endif %}
</div>
{% set symfony_status = { dev: 'Unstable Version', stable: 'Stable Version', eom: 'Maintenance Ended', eol: 'Version Expired' } %}
{% 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>
</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>
{% endif %}
{% set symfony_status = { dev: 'Unstable Version', stable: 'Stable Version', eom: 'Maintenance Ended', eol: 'Version Expired' } %}
{% 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 %}
&nbsp; <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>
<h2>PHP Configuration</h2>
@@ -240,12 +190,12 @@
</div>
<div class="metric">
<span class="value">{{ include('@WebProfiler/Icon/' ~ (collector.hasapcu ? 'yes' : 'no') ~ '.svg') }}</span>
<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">{{ include('@WebProfiler/Icon/' ~ (collector.hasxdebug ? 'yes' : 'no') ~ '.svg') }}</span>
<span class="value">{{ include('@WebProfiler/Icon/' ~ (collector.hasxdebug ? 'yes' : 'no-gray') ~ '.svg') }}</span>
<span class="label">Xdebug</span>
</div>
</div>
@@ -260,7 +210,7 @@
<thead>
<tr>
<th class="key">Name</th>
<th>Path</th>
<th>Class</th>
</tr>
</thead>
<tbody>

View File

@@ -45,6 +45,39 @@
{% 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 %}
{% endblock %}

View File

@@ -1,7 +1,5 @@
{{ include('@Twig/exception.css.twig') }}
.container {
max-width: auto;
max-width: none;
margin: 0;
padding: 0;
}
@@ -10,8 +8,8 @@
}
.exception-summary {
background: #FFF;
border: 1px solid #E0E0E0;
background: var(--base-0);
border: var(--border);
box-shadow: 0 0 1px rgba(128, 128, 128, .2);
margin: 1em 0;
padding: 10px;
@@ -21,7 +19,7 @@
}
.exception-message {
color: #B0413E;
color: var(--color-error);
}
.exception-metadata,
@@ -30,5 +28,5 @@
}
.exception-message-wrapper .container {
min-height: auto;
min-height: unset;
}

View File

@@ -3,7 +3,8 @@
{% block head %}
{% if collector.hasexception %}
<style>
{{ render(path('_profiler_exception_css', { token: token })) }}
{{ render(controller('web_profiler.controller.exception_panel::stylesheet', { token: token })) }}
{{ include('@WebProfiler/Collector/exception.css.twig') }}
</style>
{% endif %}
{{ parent() }}
@@ -30,7 +31,7 @@
</div>
{% else %}
<div class="sf-reset">
{{ render(path('_profiler_exception', { token: token })) }}
{{ render(controller('web_profiler.controller.exception_panel::body', { token: token })) }}
</div>
{% endif %}
{% endblock %}

View File

@@ -4,7 +4,7 @@
{% block toolbar %}
{% if collector.data.nb_errors > 0 or collector.data.forms|length %}
{% set status_color = collector.data.nb_errors ? 'red' : '' %}
{% set status_color = collector.data.nb_errors ? 'red' %}
{% set icon %}
{{ include('@WebProfiler/Icon/form.svg') }}
<span class="sf-toolbar-value">
@@ -131,8 +131,11 @@
.tree .tree-inner:hover {
background: #dfdfdf;
}
.tree .tree-inner:hover span:not(.has-error) {
color: var(--base-0);
}
.tree .tree-inner.active, .tree .tree-inner.active:hover {
background: #E0E0E0;
background: var(--tree-active-background);
font-weight: bold;
}
.tree .tree-inner.active .toggle-icon, .tree .tree-inner:hover .toggle-icon, .tree .tree-inner.active:hover .toggle-icon {
@@ -153,7 +156,7 @@
}
.badge-error {
float: right;
background: #B0413E;
background: var(--background-error);
color: #FFF;
padding: 1px 4px;
font-size: 10px;
@@ -161,17 +164,17 @@
vertical-align: middle;
}
.has-error {
color: #B0413E;
color: var(--color-error);
}
.errors h3 {
color: #B0413E;
color: var(--color-error);
}
.errors th {
background: #B0413E;
background: var(--background-error);
color: #FFF;
}
.errors .toggle-icon {
background-color: #B0413E;
background-color: var(--background-error);
}
h3 a, h3 a:hover, h3 a:focus {
color: inherit;
@@ -183,6 +186,20 @@
h3.form-data-type + h3 {
margin-top: 1em;
}
.theme-dark .toggle-icon {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAgBAMAAADpp+X/AAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAAVUExURUdwTH+Ag0lNUZiYmGRmbP///zU5P2n9VV4AAAAFdFJOUwCv+yror0g1sQAAAE1JREFUGNNjSFM0YGBgEEpjSGEAAzcGBQiDiUEAwmBkMIAwmBmwgVAgQGWgA7h2uIFwK+CWwp1BpHtYA6DuATEYkBlY3IOmBq6dCPcAAKMtEEs3tfChAAAAAElFTkSuQmCC');
}
.theme-dark .toggle-icon.empty {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQBAMAAADt3eJSAAAABGdBTUEAALGPC/xhBQAAAAFzUkdCAK7OHOkAAAASUExURUdwTDI3OzQ5PS4uLjU3PzU5P4keoyIAAAAFdFJOUwBApgtzrnKGEwAAADJJREFUCNdjCFU0YGBgEAplCGEAA1cGBQiDiUEAwmBkMIAwmBnIA3DtcAPhVsAthTkDACsZBBmrTTSxAAAAAElFTkSuQmCC');
}
.theme-dark .tree .tree-inner.active .toggle-icon, .theme-dark .tree .tree-inner:hover .toggle-icon, .theme-dark .tree .tree-inner.active:hover .toggle-icon {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAgBAMAAADpp+X/AAAAD1BMVEVHcEx/gIOYmJiZmZn///+IJ2wIAAAAA3RSTlMAryoIUq0uAAAAUElEQVQY02NgYFQ2NjYWYGBgMAYDBgZmCMOAQRjCMGRQhjCMoEqAipAYLkCAykBXA9cONxBuBdxShDOIc4+JM9Q9IIYxMgOLe9DUwLUT4R4AznguG0qfEa0AAAAASUVORK5CYII=');
background-color: transparent;
}
.theme-dark .tree .tree-inner.active .toggle-icon.empty, .theme-dark .tree .tree-inner:hover .toggle-icon.empty, .theme-dark .tree .tree-inner.active:hover .toggle-icon.empty {
background-image: url('data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQAgMAAABinRfyAAAACVBMVEVHcEwyNzuqqqrd9nIgAAAAAnRSTlMAQABPjKgAAAArSURBVAjXY2BctcqBgWvVqgUMWqtWrWDIWrVqJcMqICCGACsGawMbADIKANflJYEoGMqtAAAAAElFTkSuQmCC');
background-color: transparent;
}
</style>
{% endblock %}

View File

@@ -0,0 +1,131 @@
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
{% block toolbar %}
{% if collector.requestCount %}
{% set icon %}
{{ include('@WebProfiler/Icon/http-client.svg') }}
{% set status_color = '' %}
<span class="sf-toolbar-value">{{ collector.requestCount }}</span>
{% endset %}
{% set text %}
<div class="sf-toolbar-info-piece">
<b>Total requests</b>
<span>{{ collector.requestCount }}</span>
</div>
<div class="sf-toolbar-info-piece">
<b>HTTP errors</b>
<span class="sf-toolbar-status {{ collector.errorCount > 0 ? 'sf-toolbar-status-red' }}">{{ collector.errorCount }}</span>
</div>
{% endset %}
{{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { link: profiler_url, status: status_color }) }}
{% endif %}
{% endblock %}
{% block menu %}
<span class="label {{ collector.requestCount == 0 ? 'disabled' }}">
<span class="icon">{{ include('@WebProfiler/Icon/http-client.svg') }}</span>
<strong>HTTP Client</strong>
{% if collector.requestCount %}
<span class="count">
{{ collector.requestCount }}
</span>
{% endif %}
</span>
{% endblock %}
{% block panel %}
<h2>HTTP Client</h2>
{% if collector.requestCount == 0 %}
<div class="empty">
<p>No HTTP requests were made.</p>
</div>
{% else %}
<div class="metrics">
<div class="metric">
<span class="value">{{ collector.requestCount }}</span>
<span class="label">Total requests</span>
</div>
<div class="metric">
<span class="value">{{ collector.errorCount }}</span>
<span class="label">HTTP errors</span>
</div>
</div>
<h2>Clients</h2>
<div class="sf-tabs">
{% for name, client in collector.clients %}
<div class="tab {{ client.traces|length == 0 ? 'disabled' }}">
<h3 class="tab-title">{{ name }} <span class="badge">{{ client.traces|length }}</span></h3>
<div class="tab-content">
{% if client.traces|length == 0 %}
<div class="empty">
<p>No requests were made with the "{{ name }}" service.</p>
</div>
{% else %}
<h4>Requests</h4>
{% for trace in client.traces %}
{% set profiler_token = '' %}
{% set profiler_link = '' %}
{% if trace.info.response_headers is defined %}
{% for header in trace.info.response_headers %}
{% if header matches '/^x-debug-token: .*$/i' %}
{% set profiler_token = (header.getValue | slice('x-debug-token: ' | length)) %}
{% endif %}
{% if header matches '/^x-debug-token-link: .*$/i' %}
{% set profiler_link = (header.getValue | slice('x-debug-token-link: ' | length)) %}
{% endif %}
{% endfor %}
{% endif %}
<table>
<thead>
<tr>
<th>
<span class="label">{{ 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 %}
</tr>
</thead>
<tbody>
<tr>
<th>
{% if trace.http_code >= 500 %}
{% set responseStatus = 'error' %}
{% elseif trace.http_code >= 400 %}
{% set responseStatus = 'warning' %}
{% else %}
{% set responseStatus = 'success' %}
{% endif %}
<span class="label status-{{ responseStatus }}">
{{ trace.http_code }}
</span>
</th>
<td>
{{ profiler_dump(trace.info, maxDepth=1) }}
</td>
{% if profiler_token and profiler_link %}
<td>
<span><a href="{{ profiler_link }}" target="_blank">{{ profiler_token }}</a></span>
</td>
{% endif %}
</tr>
</tbody>
</table>
{% endfor %}
{% endif %}
</div>
</div>
{% endfor %}
{% endif %}
</div>
{% endblock %}

View File

@@ -5,7 +5,7 @@
{% block toolbar %}
{% if collector.counterrors or collector.countdeprecations or collector.countwarnings %}
{% set icon %}
{% set status_color = collector.counterrors ? 'red' : 'yellow' %}
{% set status_color = collector.counterrors ? 'red' : collector.countwarnings ? 'yellow' : 'none' %}
{{ include('@WebProfiler/Icon/logger.svg') }}
<span class="sf-toolbar-value">{{ collector.counterrors ?: (collector.countdeprecations + collector.countwarnings) }}</span>
{% endset %}
@@ -23,7 +23,7 @@
<div class="sf-toolbar-info-piece">
<b>Deprecations</b>
<span class="sf-toolbar-status sf-toolbar-status-{{ collector.countdeprecations ? 'yellow' }}">{{ collector.countdeprecations|default(0) }}</span>
<span class="sf-toolbar-status sf-toolbar-status-{{ collector.countdeprecations ? 'none' }}">{{ collector.countdeprecations|default(0) }}</span>
</div>
{% endset %}
@@ -32,7 +32,7 @@
{% endblock %}
{% block menu %}
<span class="label label-status-{{ collector.counterrors ? 'error' : collector.countdeprecations or collector.countwarnings ? 'warning' }} {{ collector.logs is empty ? 'disabled' }}">
<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>
<strong>Logs</strong>
{% if collector.counterrors or collector.countdeprecations or collector.countwarnings %}
@@ -46,182 +46,185 @@
{% block panel %}
<h2>Log Messages</h2>
{% if collector.logs is empty %}
{% if collector.processedLogs is empty %}
<div class="empty">
<p>No log messages available.</p>
</div>
{% else %}
{# sort collected logs in groups #}
{% set deprecation_logs, debug_logs, info_and_error_logs, silenced_logs = [], [], [], [] %}
{% for log in collector.logs %}
{% if log.scream is defined and not log.scream %}
{% set deprecation_logs = deprecation_logs|merge([log]) %}
{% elseif log.scream is defined and log.scream %}
{% set silenced_logs = silenced_logs|merge([log]) %}
{% elseif log.priorityName == 'DEBUG' %}
{% set debug_logs = debug_logs|merge([log]) %}
{% else %}
{% set info_and_error_logs = info_and_error_logs|merge([log]) %}
{% endif %}
{% endfor %}
{% set has_error_logs = collector.processedLogs|column('type')|filter(type => 'error' == type)|length > 0 %}
{% set has_deprecation_logs = collector.processedLogs|column('type')|filter(type => 'deprecation' == type)|length > 0 %}
<div class="sf-tabs">
<div class="tab">
<h3 class="tab-title">Info. &amp; Errors <span class="badge status-{{ collector.counterrors ? 'error' : collector.countwarnings ? 'warning' }}">{{ collector.counterrors ?: info_and_error_logs|length }}</span></h3>
<p class="text-muted">Informational and error log messages generated during the execution of the application.</p>
{% 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-content">
{% if info_and_error_logs is empty %}
<div class="empty">
<p>There are no log messages of this level.</p>
</div>
{% else %}
{{ helper.render_table(info_and_error_logs, 'info', true) }}
{% endif %}
</div>
<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>
<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>
</div>
<div class="tab">
{# 'deprecation_logs|length' is not used because deprecations are
now grouped and the group count doesn't match the message count #}
<h3 class="tab-title">Deprecations <span class="badge status-{{ collector.countdeprecations ? 'warning' }}">{{ collector.countdeprecations|default(0) }}</span></h3>
<p class="text-muted">Log messages generated by using features marked as deprecated.</p>
<details id="log-filter-priority" class="log-filter">
<summary>
<span class="icon">{{ include('@WebProfiler/Icon/filter.svg') }}</span>
Level (<span class="filter-active-num">{{ filters.priority|length - 1 }}</span>)
</summary>
<div class="tab-content">
{% if deprecation_logs is empty %}
<div class="empty">
<p>There are no log messages about deprecated features.</p>
<div class="log-filter-content">
<div class="filter-select-all-or-none">
<button type="button" class="btn btn-link select-all">Select All</button>
<button type="button" class="btn btn-link select-none">Select None</button>
</div>
{% for label, value in filters.priority %}
<div class="log-filter-option">
<input {{ 'debug' != value ? 'checked' }} type="checkbox" id="filter-log-level-{{ value }}" name="filter-log-level-{{ value }}" value="{{ value }}">
<label for="filter-log-level-{{ value }}">{{ label }}</label>
</div>
{% else %}
{{ helper.render_table(deprecation_logs, 'deprecation', false, true) }}
{% endif %}
{% endfor %}
</div>
</div>
</details>
<div class="tab">
<h3 class="tab-title">Debug <span class="badge">{{ debug_logs|length }}</span></h3>
<p class="text-muted">Unimportant log messages generated during the execution of the application.</p>
<details id="log-filter-channel" class="log-filter">
<summary>
<span class="icon">{{ include('@WebProfiler/Icon/filter.svg') }}</span>
Channel (<span class="filter-active-num">{{ filters.channel|length - 1 }}</span>)
</summary>
<div class="tab-content">
{% if debug_logs is empty %}
<div class="empty">
<p>There are no log messages of this level.</p>
<div class="log-filter-content">
<div class="filter-select-all-or-none">
<button type="button" class="btn btn-link select-all">Select All</button>
<button type="button" class="btn btn-link select-none">Select None</button>
</div>
{% for value in filters.channel %}
<div class="log-filter-option">
<input {{ 'event' != value ? 'checked' }} type="checkbox" id="filter-log-channel-{{ value }}" name="filter-log-channel-{{ value }}" value="{{ value }}">
<label for="filter-log-channel-{{ value }}">{{ value|title }}</label>
</div>
{% else %}
{{ helper.render_table(debug_logs, 'debug') }}
{% endif %}
{% endfor %}
</div>
</div>
<div class="tab">
<h3 class="tab-title">PHP Notices <span class="badge">{{ collector.countscreams|default(0) }}</span></h3>
<p class="text-muted">Log messages generated by PHP notices silenced with the @ operator.</p>
<div class="tab-content">
{% if silenced_logs is empty %}
<div class="empty">
<p>There are no log messages of this level.</p>
</div>
{% else %}
{{ helper.render_table(silenced_logs, 'silenced') }}
{% endif %}
</div>
</div>
{% set compilerLogTotal = 0 %}
{% for logs in collector.compilerLogs %}
{% set compilerLogTotal = compilerLogTotal + logs|length %}
{% endfor %}
<div class="tab">
<h3 class="tab-title">Container <span class="badge">{{ compilerLogTotal }}</span></h3>
<p class="text-muted">Log messages generated during the compilation of the service container.</p>
<div class="tab-content">
{% if collector.compilerLogs is empty %}
<div class="empty">
<p>There are no compiler log messages.</p>
</div>
{% else %}
<table class="logs">
<thead>
<tr>
<th class="full-width">Class</th>
<th>Messages</th>
</tr>
</thead>
<tbody>
{% for class, logs in collector.compilerLogs %}
<tr class="">
<td class="font-normal">
{% set context_id = 'context-compiler-' ~ loop.index %}
<a class="btn btn-link sf-toggle" data-toggle-selector="#{{ context_id }}" data-toggle-alt-content="{{ class }}">{{ class }}</a>
<div id="{{ context_id }}" class="context sf-toggle-content sf-toggle-hidden">
<ul>
{% for log in logs %}
<li>{{ profiler_dump_log(log.message) }}</li>
{% endfor %}
</ul>
</div>
</td>
<td class="font-normal text-right">{{ logs|length }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
</div>
</div>
</details>
</div>
{% endif %}
{% endblock %}
{% macro render_table(logs, category = '', show_level = false, is_deprecation = false) %}
{% import _self as helper %}
{% set channel_is_defined = (logs|first).channel is defined %}
<table class="logs">
<colgroup>
<col width="140px">
<col>
</colgroup>
<table class="logs">
<thead>
<tr>
<th>{{ show_level ? 'Level' : 'Time' }}</th>
{% if channel_is_defined %}<th>Channel</th>{% endif %}
<th class="full-width">Message</th>
</tr>
</thead>
<thead>
<th>Time</th>
<th>Message</th>
</thead>
<tbody>
{% for log in logs %}
{% set css_class = is_deprecation ? ''
: log.priorityName in ['CRITICAL', 'ERROR', 'ALERT', 'EMERGENCY'] ? 'status-error'
: log.priorityName == 'WARNING' ? 'status-warning'
%}
<tr class="{{ css_class }}">
<td class="font-normal text-small" nowrap>
{% if show_level %}
<span class="colored text-bold">{{ log.priorityName }}</span>
{% endif %}
<span class="text-muted newline">{{ log.timestamp|date('H:i:s') }}</span>
</td>
<tbody>
{% for log in collector.processedLogs %}
{% set css_class = 'error' == log.type ? 'error'
: (log.priorityName == 'WARNING' or 'deprecation' == log.type) ? 'warning'
: 'silenced' == log.type ? 'silenced'
%}
<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') }}">
{{ log.timestamp|date('H:i:s.v') }}
</time>
{% if channel_is_defined %}
<td class="font-normal text-small text-bold" nowrap>
{{ log.channel }}
{% if log.errorCount is defined and log.errorCount > 1 %}
<span class="text-muted">({{ log.errorCount }} times)</span>
{% if log.type in ['error', 'deprecation', 'silenced'] or 'WARNING' == log.priorityName %}
<span class="log-type-badge badge badge-{{ css_class }}">
{% if 'error' == log.type or 'WARNING' == log.priorityName %}
{{ log.priorityName|lower }}
{% else %}
{{ log.type|lower }}
{% endif %}
</span>
{% else %}
<span class="log-type-badge badge badge-{{ css_class }}">
{{ log.priorityName|lower }}
</span>
{% endif %}
</td>
{% endif %}
<td class="font-normal">
{{ helper.render_log_message('debug', loop.index, log) }}
</td>
</tr>
{% endfor %}
</tbody>
</table>
<td class="font-normal">{{ helper.render_log_message(category, loop.index, log) }}</td>
<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 %}
<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>
</summary>
{% if collector.compilerLogs is empty %}
<div class="empty">
<p>There are no compiler log messages.</p>
</div>
{% else %}
<table class="container-logs">
<thead>
<tr>
<th>Messages</th>
<th class="full-width">Class</th>
</tr>
{% endfor %}
</tbody>
</table>
{% endmacro %}
</thead>
<tbody>
{% for class, logs in collector.compilerLogs %}
<tr>
<td class="font-normal text-right">{{ logs|length }}</td>
<td class="font-normal">
{% set context_id = 'context-compiler-' ~ loop.index %}
<button type="button" class="btn btn-link sf-toggle" data-toggle-selector="#{{ context_id }}" data-toggle-alt-content="{{ class }}">{{ class }}</button>
<div id="{{ context_id }}" class="context sf-toggle-content sf-toggle-hidden">
<ul class="break-long-words">
{% for log in logs %}
<li>{{ profiler_dump_log(log.message) }}</li>
{% endfor %}
</ul>
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
</details>
{% endblock %}
{% macro render_log_message(category, log_index, log) %}
{% set has_context = log.context is defined and log.context is not empty %}
@@ -231,26 +234,41 @@
{{ profiler_dump_log(log.message) }}
{% else %}
{{ profiler_dump_log(log.message, log.context) }}
{% endif %}
<div class="text-small font-normal">
<div class="log-metadata">
{% if log.channel %}
<span class="badge">{{ log.channel }}</span>
{% endif %}
{% if log.errorCount is defined and log.errorCount > 1 %}
<span class="log-num-occurrences">{{ log.errorCount }} times</span>
{% endif %}
{% if has_context %}
{% set context_id = 'context-' ~ category ~ '-' ~ log_index %}
<a class="btn btn-link text-small sf-toggle" data-toggle-selector="#{{ context_id }}" data-toggle-alt-content="Hide context">Show context</a>
<span><button type="button" class="btn btn-link text-small sf-toggle" data-toggle-selector="#{{ context_id }}" data-toggle-alt-content="Hide context">Show context</button></span>
{% endif %}
{% if has_trace %}
&nbsp;&nbsp;
{% set trace_id = 'trace-' ~ category ~ '-' ~ log_index %}
<a class="btn btn-link text-small sf-toggle" data-toggle-selector="#{{ trace_id }}" data-toggle-alt-content="Hide trace">Show trace</a>
{% endif %}
</div>
{% if has_trace %}
{% set trace_id = 'trace-' ~ category ~ '-' ~ log_index %}
<span><button type="button" class="btn btn-link text-small sf-toggle" data-toggle-selector="#{{ trace_id }}" data-toggle-alt-content="Hide trace">Show trace</button></span>
<div id="{{ context_id }}" class="context sf-toggle-content sf-toggle-hidden">
{{ profiler_dump(log.context, maxDepth=1) }}
</div>
<div id="{{ trace_id }}" class="context sf-toggle-content sf-toggle-hidden">
{{ profiler_dump(log.context.exception.trace, maxDepth=1) }}
</div>
{% endif %}
{% if has_context %}
<div id="{{ context_id }}" class="context sf-toggle-content sf-toggle-hidden">
{{ profiler_dump(log.context, maxDepth=1) }}
</div>
{% endif %}
{% if has_trace %}
<div id="{{ trace_id }}" class="context sf-toggle-content sf-toggle-hidden">
{{ profiler_dump(log.context.exception.trace, maxDepth=1) }}
</div>
{% endif %}
{% endif %}
</div>
{% endmacro %}

View File

@@ -0,0 +1,217 @@
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
{% block toolbar %}
{% set events = collector.events %}
{% if events.messages|length %}
{% set icon %}
{% include('@WebProfiler/Icon/mailer.svg') %}
<span class="sf-toolbar-value">{{ events.messages|length }}</span>
{% endset %}
{% set text %}
<div class="sf-toolbar-info-piece">
<b>Queued messages</b>
<span class="sf-toolbar-status">{{ events.events|filter(e => e.isQueued())|length }}</span>
</div>
<div class="sf-toolbar-info-piece">
<b>Sent messages</b>
<span class="sf-toolbar-status">{{ events.events|filter(e => not e.isQueued())|length }}</span>
</div>
{% endset %}
{{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { 'link': profiler_url }) }}
{% 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>
<strong>E-mails</strong>
{% if events.messages|length > 0 %}
<span class="count">
<span>{{ events.messages|length }}</span>
</span>
{% endif %}
</span>
{% endblock %}
{% block panel %}
{% set events = collector.events %}
<h2>Emails</h2>
{% if not events.messages|length %}
<div class="empty">
<p>No emails were sent.</p>
</div>
{% endif %}
<div class="metrics">
<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>
{% 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>
<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 %}
{% 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>
</div>
</div>
</div>
{% endif %}
</div>
</div>
</div>
{% endfor %}
</div>
</div>
{% endfor %}
{% endblock %}

View File

@@ -2,7 +2,7 @@
{% block toolbar %}
{% set icon %}
{% set status_color = (collector.memory / 1024 / 1024) > 50 ? 'yellow' : '' %}
{% set status_color = (collector.memory / 1024 / 1024) > 50 ? 'yellow' %}
{{ include('@WebProfiler/Icon/memory.svg') }}
<span class="sf-toolbar-value">{{ '%.1f'|format(collector.memory / 1024 / 1024) }}</span>
<span class="sf-toolbar-label">MiB</span>

View File

@@ -0,0 +1,201 @@
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
{% import _self as helper %}
{% block toolbar %}
{% if collector.messages|length > 0 %}
{% set status_color = collector.exceptionsCount ? 'red' %}
{% set icon %}
{{ include('@WebProfiler/Icon/messenger.svg') }}
<span class="sf-toolbar-value">{{ collector.messages|length }}</span>
{% endset %}
{% set text %}
{% for bus in collector.buses %}
{% set exceptionsCount = collector.exceptionsCount(bus) %}
<div class="sf-toolbar-info-piece">
<b>{{ bus }}</b>
<span
title="{{ exceptionsCount }} message(s) with exceptions"
class="sf-toolbar-status sf-toolbar-status-{{ exceptionsCount ? 'red' }}"
>
{{ collector.messages(bus)|length }}
</span>
</div>
{% endfor %}
{% endset %}
{{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { link: 'messenger', status: status_color }) }}
{% endif %}
{% endblock %}
{% block menu %}
<span class="label{{ collector.exceptionsCount ? ' label-status-error' }}{{ collector.messages is empty ? ' disabled' }}">
<span class="icon">{{ include('@WebProfiler/Icon/messenger.svg') }}</span>
<strong>Messages</strong>
{% if collector.exceptionsCount > 0 %}
<span class="count">
<span>{{ collector.exceptionsCount }}</span>
</span>
{% endif %}
</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">
<p>No messages have been collected.</p>
</div>
{% else %}
<div class="sf-tabs message-bus">
<div class="tab">
{% set messages = collector.messages %}
{% set exceptionsCount = collector.exceptionsCount %}
<h3 class="tab-title">All<span class="badge {{ exceptionsCount ? exceptionsCount == messages|length ? 'status-error' : 'status-some-errors' }}">{{ messages|length }}</span></h3>
<div class="tab-content">
<p class="text-muted">Ordered list of dispatched messages across all your buses</p>
{{ helper.render_bus_messages(messages, true) }}
</div>
</div>
{% for bus in collector.buses %}
<div class="tab message-bus">
{% set messages = collector.messages(bus) %}
{% set exceptionsCount = collector.exceptionsCount(bus) %}
<h3 class="tab-title">{{ bus }}<span class="badge {{ exceptionsCount ? exceptionsCount == messages|length ? 'status-error' : 'status-some-errors' }}">{{ messages|length }}</span></h3>
<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) }}
</div>
</div>
{% endfor %}
</div>
{% endif %}
{% endblock %}
{% macro render_bus_messages(messages, showBus = false) %}
{% set discr = random() %}
{% for dispatchCall in messages %}
<table class="message-item">
<thead>
<tr>
<th colspan="2" class="sf-toggle"
data-toggle-selector="#message-item-{{ discr }}-{{ loop.index0 }}-details"
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>
</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 %}
{% else %}
{{ caller.name }}
{% endif %}
line <a class="text-small sf-toggle" data-toggle-selector="#sf-trace-{{ discr }}-{{ loop.index0 }}">{{ caller.line }}</a>
</span>
<div class="hidden" id="sf-trace-{{ discr }}-{{ loop.index0 }}">
<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>
</td>
</tr>
{% if showBus %}
<tr>
<td class="text-bold">Bus</td>
<td>{{ dispatchCall.bus }}</td>
</tr>
{% endif %}
<tr>
<td class="text-bold">Message</td>
<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>
<td>
{% for item in dispatchCall.stamps %}
{{ profiler_dump(item) }}
{% else %}
<span class="text-muted">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>
<td>
{% for item in dispatchCall.stamps_after_dispatch %}
{{ profiler_dump(item) }}
{% else %}
<span class="text-muted">No items</span>
{% endfor %}
</td>
</tr>
{% endif %}
{% if dispatchCall.exception is defined %}
<tr>
<td class="text-bold">Exception</td>
<td>
{{ profiler_dump(dispatchCall.exception.value, maxDepth=1) }}
</td>
</tr>
{% endif %}
</tbody>
</table>
{% endfor %}
{% endmacro %}

View File

@@ -0,0 +1,168 @@
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
{% block toolbar %}
{% set events = collector.events %}
{% if events.messages|length %}
{% set icon %}
{% include('@WebProfiler/Icon/notifier.svg') %}
<span class="sf-toolbar-value">{{ events.messages|length }}</span>
{% endset %}
{% set text %}
<div class="sf-toolbar-info-piece">
<b>Sent notifications</b>
<span class="sf-toolbar-status">{{ events.messages|length }}</span>
</div>
{% for transport in events.transports %}
<div class="sf-toolbar-info-piece">
<b>{{ transport }}</b>
<span class="sf-toolbar-status">{{ events.messages(transport)|length }}</span>
</div>
{% endfor %}
{% endset %}
{{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { 'link': profiler_url }) }}
{% 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|length ? '' : 'disabled' }}">
<span class="icon">{{ include('@WebProfiler/Icon/notifier.svg') }}</span>
<strong>Notifications</strong>
{% if events.messages|length > 0 %}
<span class="count">
<span>{{ events.messages|length }}</span>
</span>
{% endif %}
</span>
{% endblock %}
{% block panel %}
{% set events = collector.events %}
<h2>Notifications</h2>
{% if not events.messages|length %}
<div class="empty">
<p>No notifications were sent.</p>
</div>
{% endif %}
<div class="metrics">
{% for transport in events.transports %}
<div class="metric">
<span class="value">{{ events.messages(transport)|length }}</span>
<span class="label">{{ transport }}</span>
</div>
{% endfor %}
</div>
{% for transport in events.transports %}
<h3>{{ transport }}</h3>
<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">Message #{{ loop.index }} <small>({{ event.isQueued() ? 'queued' : 'sent' }})</small></h3>
<div class="tab-content">
<div class="card">
<div class="card-block">
<span class="label">Subject</span>
<h2 class="m-t-10">{{ message.getSubject() ?? '(empty)' }}</h2>
</div>
{% if message.getNotification is defined %}
<div class="card-block">
<div class="row">
<div class="col">
<span class="label">Content</span>
<pre class="prewrap">{{ message.getNotification().getContent() ?? '(empty)' }}</pre>
<span class="label">Importance</span>
<pre class="prewrap">{{ message.getNotification().getImportance() }}</pre>
</div>
</div>
</div>
{% endif %}
<div class="card-block">
<div class="sf-tabs sf-tabs-sm">
{% if message.getNotification is defined %}
<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/>
{{- 'ExceptionAsString: ' ~ (notification.getExceptionAsString() is empty ? '(empty)' : notification.getExceptionAsString()) }}
</pre>
</div>
</div>
{% endif %}
<div class="tab">
<h3 class="tab-title">Message Options</h3>
<div class="tab-content">
<pre class="prewrap" style="max-height: 600px">
{%- if message.getOptions() is null %}
{{- '(empty)' }}
{%- else %}
{{- message.getOptions()|json_encode(constant('JSON_PRETTY_PRINT')) }}
{%- endif %}
</pre>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
</div>
{% endfor %}
{% endblock %}

View File

@@ -12,9 +12,10 @@
{% endset %}
{% endif %}
{% if collector.forward|default(false) %}
{% if collector.forwardtoken %}
{% set forward_profile = profile.childByToken(collector.forwardtoken) %}
{% set forward_handler %}
{{ helper.set_handler(collector.forward.controller) }}
{{ helper.set_handler(forward_profile ? forward_profile.collector('request').controller : 'n/a') }}
{% endset %}
{% endif %}
@@ -24,7 +25,7 @@
<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.forward|default(false) %}{{ include('@WebProfiler/Icon/forward.svg') }}{% endif %}
{% if collector.forwardtoken %}{{ include('@WebProfiler/Icon/forward.svg') }}{% 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 %}
@@ -49,13 +50,6 @@
<span>{{ request_handler }}</span>
</div>
{% if collector.controller.class is defined -%}
<div class="sf-toolbar-info-piece">
<b>Controller class</b>
<span>{{ collector.controller.class }}</span>
</div>
{%- endif %}
<div class="sf-toolbar-info-piece">
<b>Route name</b>
<span>{{ collector.route|default('n/a') }}</span>
@@ -65,6 +59,11 @@
<b>Has session</b>
<span>{% if collector.sessionmetadata|length %}yes{% else %}no{% endif %}</span>
</div>
<div class="sf-toolbar-info-piece">
<b>Stateless Check</b>
<span>{% if collector.statelesscheck %}yes{% else %}no{% endif %}</span>
</div>
</div>
{% if redirect_handler is defined -%}
@@ -88,7 +87,7 @@
<b>Forwarded to</b>
<span>
{{ forward_handler }}
(<a href="{{ path('_profiler', { token: collector.forward.token }) }}">{{ collector.forward.token }}</a>)
(<a href="{{ path('_profiler', { token: collector.forwardtoken }) }}">{{ collector.forwardtoken }}</a>)
</span>
</div>
</div>
@@ -137,6 +136,16 @@
{{ include('@WebProfiler/Profiler/bag.html.twig', { bag: collector.requestrequest, maxDepth: 1 }, with_context = false) }}
{% endif %}
<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 %}
<h3>Request Attributes</h3>
{% if collector.requestattributes.all is empty %}
@@ -157,17 +166,33 @@
<p>Request content not available (it was retrieved as a resource).</p>
</div>
{% elseif collector.content %}
<div class="card">
<pre class="break-long-words">{{ collector.content }}</pre>
<div class="sf-tabs">
{% set prettyJson = collector.isJsonRequest ? collector.prettyJson : null %}
{% if prettyJson is not null %}
<div class="tab">
<h3 class="tab-title">Pretty</h3>
<div class="tab-content">
<div class="card" style="max-height: 500px; overflow-y: auto;">
<pre class="break-long-words">{{ prettyJson }}</pre>
</div>
</div>
</div>
{% endif %}
<div class="tab">
<h3 class="tab-title">Raw</h3>
<div class="tab-content">
<div class="card">
<pre class="break-long-words">{{ collector.content }}</pre>
</div>
</div>
</div>
</div>
{% else %}
<div class="empty">
<p>No content</p>
</div>
{% endif %}
<h3>Server Parameters</h3>
{{ include('@WebProfiler/Profiler/bag.html.twig', { bag: collector.requestserver }, with_context = false) }}
</div>
</div>
@@ -208,7 +233,7 @@
</div>
<div class="tab {{ collector.sessionmetadata is empty ? 'disabled' }}">
<h3 class="tab-title">Session</h3>
<h3 class="tab-title">Session{% if collector.sessionusages is not empty %} <span class="badge">{{ collector.sessionusages|length }}</span>{% endif %}</h3>
<div class="tab-content">
<h3>Session Metadata</h3>
@@ -230,6 +255,54 @@
{% else %}
{{ include('@WebProfiler/Profiler/table.html.twig', { data: collector.sessionattributes, labels: ['Attribute', 'Value'] }, with_context = false) }}
{% endif %}
<h3>Session Usage</h3>
<div class="metrics">
<div class="metric">
<span class="value">{{ collector.sessionusages|length }}</span>
<span class="label">Usages</span>
</div>
<div class="metric">
<span class="value">{{ include('@WebProfiler/Icon/' ~ (collector.statelesscheck ? 'yes' : 'no') ~ '.svg') }}</span>
<span class="label">Stateless check enabled</span>
</div>
</div>
{% if collector.sessionusages is empty %}
<div class="empty">
<p>Session not used.</p>
</div>
{% else %}
<table class="session_usages">
<thead>
<tr>
<th class="full-width">Usage</th>
</tr>
</thead>
<tbody>
{% for key, usage in collector.sessionusages %}
<tr>
<td class="font-normal">
{%- set link = usage.file|file_link(usage.line) %}
{%- if link %}<a href="{{ link }}" title="{{ usage.name }}">{% else %}<span title="{{ usage.name }}">{% endif %}
{{ usage.name }}
{%- if link %}</a>{% else %}</span>{% endif %}
<div class="text-small font-normal">
{% set usage_id = 'session-usage-trace-' ~ key %}
<a class="btn btn-link text-small sf-toggle" data-toggle-selector="#{{ usage_id }}" data-toggle-alt-content="Hide trace">Show trace</a>
</div>
<div id="{{ usage_id }}" class="context sf-toggle-content sf-toggle-hidden">
{{ profiler_dump(usage.trace, maxDepth=2) }}
</div>
</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
</div>
</div>
@@ -249,6 +322,22 @@
</div>
</div>
<div class="tab">
<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: collector.dotenvvars }, with_context = false) }}
<h4>Defined as regular env variables</h4>
{% set requestserver = [] %}
{% for key, value in collector.requestserver|filter((_, key) => key not in collector.dotenvvars.keys) %}
{% set requestserver = requestserver|merge({(key): value}) %}
{% endfor %}
{{ include('@WebProfiler/Profiler/table.html.twig', { data: requestserver }, with_context = false) }}
</div>
</div>
{% if profile.parent %}
<div class="tab">
<h3 class="tab-title">Parent Request</h3>
@@ -287,7 +376,7 @@
{% if controller.class is defined -%}
{%- if method|default(false) %}<span class="sf-toolbar-status sf-toolbar-redirection-method">{{ method }}</span>{% endif -%}
{%- set link = controller.file|file_link(controller.line) %}
{%- if link %}<a href="{{ link }}" title="{{ controller.file }}">{% else %}<span>{% endif %}
{%- if link %}<a href="{{ link }}" title="{{ controller.class }}">{% else %}<span title="{{ controller.class }}">{% endif %}
{%- if route|default(false) -%}
@{{ route }}

View File

@@ -10,5 +10,5 @@
{% endblock %}
{% block panel %}
{{ render(path('_profiler_router', { token: token })) }}
{{ render(controller('web_profiler.controller.router::panelAction', { token: token })) }}
{% endblock %}

View File

@@ -0,0 +1,64 @@
/* Legend */
.sf-profiler-timeline .legends .timeline-category {
border: none;
background: none;
border-left: 1em solid transparent;
line-height: 1em;
margin: 0 1em 0 0;
padding: 0 0.5em;
display: none;
opacity: 0.5;
}
.sf-profiler-timeline .legends .timeline-category.active {
opacity: 1;
}
.sf-profiler-timeline .legends .timeline-category.present {
display: inline-block;
}
.timeline-graph {
margin: 1em 0;
width: 100%;
background-color: var(--table-background);
border: 1px solid var(--table-border);
}
/* Typography */
.timeline-graph .timeline-label {
font-family: var(--font-sans-serif);
font-size: 12px;
line-height: 12px;
font-weight: normal;
fill: var(--color-text);
}
.timeline-graph .timeline-label .timeline-sublabel {
margin-left: 1em;
fill: var(--color-muted);
}
.timeline-graph .timeline-subrequest,
.timeline-graph .timeline-border {
fill: none;
stroke: var(--table-border);
stroke-width: 1px;
}
.timeline-graph .timeline-subrequest {
fill: url(#subrequest);
fill-opacity: 0.5;
}
.timeline-subrequest-pattern {
fill: var(--table-border);
}
/* Timeline periods */
.timeline-graph .timeline-period {
stroke-width: 0;
}

View File

@@ -2,23 +2,11 @@
{% import _self as helper %}
{% if colors is not defined %}
{% set colors = {
'default': '#999',
'section': '#444',
'event_listener': '#00B8F5',
'template': '#66CC00',
'doctrine': '#FF6633',
} %}
{% endif %}
{% block toolbar %}
{% set has_time_events = collector.events|length > 0 %}
{% set total_time = has_time_events ? '%.0f'|format(collector.duration) : 'n/a' %}
{% set initialization_time = collector.events|length ? '%.0f'|format(collector.inittime) : 'n/a' %}
{% set status_color = has_time_events and collector.duration > 1000 ? 'yellow' : '' %}
{% set status_color = has_time_events and collector.duration > 1000 ? 'yellow' %}
{% set icon %}
{{ include('@WebProfiler/Icon/time.svg') }}
@@ -101,7 +89,7 @@
</div>
{% elseif collector.events is empty %}
<div class="empty">
<p>No timing events have been recorded. Are you sure that debugging is enabled in the kernel?</p>
<p>No timing events have been recorded. Check that symfony/stopwatch is installed and debugging enabled in the kernel.</p>
</div>
{% else %}
{{ block('panelContent') }}
@@ -112,7 +100,7 @@
<form id="timeline-control" action="" method="get">
<input type="hidden" name="panel" value="time">
<label for="threshold">Threshold</label>
<input type="number" size="3" name="threshold" id="threshold" value="3" min="0"> ms
<input type="number" name="threshold" id="threshold" value="1" min="0" placeholder="1.1"> ms
<span class="help">(timeline only displays events with a duration longer than this threshold)</span>
</form>
@@ -130,7 +118,7 @@
</h3>
{% endif %}
{{ helper.display_timeline('timeline_' ~ token, collector.events, colors) }}
{{ helper.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>
@@ -144,380 +132,34 @@
<small>{{ events.__section__.duration }} ms</small>
</h4>
{{ helper.display_timeline('timeline_' ~ child.token, events, colors) }}
{{ helper.display_timeline(child.token, events, collector.events.__section__.origin) }}
{% endfor %}
{% endif %}
<script>{% autoescape 'js' %}//<![CDATA[
/**
* In-memory key-value cache manager
*/
var cache = new function() {
"use strict";
var dict = {};
this.get = function(key) {
return dict.hasOwnProperty(key)
? dict[key]
: null;
};
this.set = function(key, value) {
dict[key] = value;
return value;
};
};
/**
* Query an element with a CSS selector.
*
* @param {string} selector - a CSS-selector-compatible query string
*
* @return DOMElement|null
*/
function query(selector)
{
"use strict";
var key = 'SELECTOR: ' + selector;
return cache.get(key) || cache.set(key, document.querySelector(selector));
}
/**
* Canvas Manager
*/
function CanvasManager(requests, maxRequestTime) {
"use strict";
var _drawingColors = {{ colors|json_encode|raw }},
_storagePrefix = 'timeline/',
_threshold = 1,
_requests = requests,
_maxRequestTime = maxRequestTime;
/**
* Check whether this event is a child event.
*
* @return true if it is
*/
function isChildEvent(event)
{
return '__section__.child' === event.name;
}
/**
* Check whether this event is categorized in 'section'.
*
* @return true if it is
*/
function isSectionEvent(event)
{
return 'section' === event.category;
}
/**
* Get the width of the container.
*/
function getContainerWidth()
{
return query('#collector-content h2').clientWidth;
}
/**
* Draw one canvas.
*
* @param request the request object
* @param max <subjected for removal>
* @param threshold the threshold (lower bound) of the length of the timeline (in milliseconds)
* @param width the width of the canvas
*/
this.drawOne = function(request, max, threshold, width)
{
"use strict";
var text,
ms,
xc,
drawableEvents,
mainEvents,
elementId = 'timeline_' + request.id,
canvasHeight = 0,
gapPerEvent = 38,
colors = _drawingColors,
space = 10.5,
ratio = (width - space * 2) / max,
h = space,
x = request.left * ratio + space, // position
canvas = cache.get(elementId) || cache.set(elementId, document.getElementById(elementId)),
ctx = canvas.getContext("2d"),
scaleRatio,
devicePixelRatio;
// Filter events whose total time is below the threshold.
drawableEvents = request.events.filter(function(event) {
return event.duration >= threshold;
});
canvasHeight += gapPerEvent * drawableEvents.length;
// For retina displays so text and boxes will be crisp
devicePixelRatio = window.devicePixelRatio == "undefined" ? 1 : window.devicePixelRatio;
scaleRatio = devicePixelRatio / 1;
canvas.width = width * scaleRatio;
canvas.height = canvasHeight * scaleRatio;
canvas.style.width = width + 'px';
canvas.style.height = canvasHeight + 'px';
ctx.scale(scaleRatio, scaleRatio);
ctx.textBaseline = "middle";
ctx.lineWidth = 0;
// For each event, draw a line.
ctx.strokeStyle = "#CCC";
drawableEvents.forEach(function(event) {
event.periods.forEach(function(period) {
var timelineHeadPosition = x + period.start * ratio;
if (isChildEvent(event)) {
/* create a striped background dynamically */
var img = new Image();
img.src = 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKBAMAAAB/HNKOAAAAIVBMVEX////w8PDd7h7d7h7d7h7d7h7w8PDw8PDw8PDw8PDw8PAOi84XAAAAKUlEQVQImWNI71zAwMBQMYuBgY0BxExnADErGEDMTgYQE8hnAKtCZwIAlcMNSR9a1OEAAAAASUVORK5CYII=';
var pattern = ctx.createPattern(img, 'repeat');
ctx.fillStyle = pattern;
ctx.fillRect(timelineHeadPosition, 0, (period.end - period.start) * ratio, canvasHeight);
} else if (isSectionEvent(event)) {
var timelineTailPosition = x + period.end * ratio;
ctx.beginPath();
ctx.moveTo(timelineHeadPosition, 0);
ctx.lineTo(timelineHeadPosition, canvasHeight);
ctx.moveTo(timelineTailPosition, 0);
ctx.lineTo(timelineTailPosition, canvasHeight);
ctx.fill();
ctx.closePath();
ctx.stroke();
}
});
});
// Filter for main events.
mainEvents = drawableEvents.filter(function(event) {
return !isChildEvent(event)
});
// For each main event, draw the visual presentation of timelines.
mainEvents.forEach(function(event) {
h += 8;
// For each sub event, ...
event.periods.forEach(function(period) {
// Set the drawing style.
ctx.fillStyle = colors['default'];
ctx.strokeStyle = colors['default'];
if (colors[event.name]) {
ctx.fillStyle = colors[event.name];
ctx.strokeStyle = colors[event.name];
} else if (colors[event.category]) {
ctx.fillStyle = colors[event.category];
ctx.strokeStyle = colors[event.category];
}
// Draw the timeline
var timelineHeadPosition = x + period.start * ratio;
if (!isSectionEvent(event)) {
ctx.fillRect(timelineHeadPosition, h + 3, 2, 8);
ctx.fillRect(timelineHeadPosition, h, (period.end - period.start) * ratio || 2, 6);
} else {
var timelineTailPosition = x + period.end * ratio;
ctx.beginPath();
ctx.moveTo(timelineHeadPosition, h);
ctx.lineTo(timelineHeadPosition, h + 11);
ctx.lineTo(timelineHeadPosition + 8, h);
ctx.lineTo(timelineHeadPosition, h);
ctx.fill();
ctx.closePath();
ctx.stroke();
ctx.beginPath();
ctx.moveTo(timelineTailPosition, h);
ctx.lineTo(timelineTailPosition, h + 11);
ctx.lineTo(timelineTailPosition - 8, h);
ctx.lineTo(timelineTailPosition, h);
ctx.fill();
ctx.closePath();
ctx.stroke();
ctx.beginPath();
ctx.moveTo(timelineHeadPosition, h);
ctx.lineTo(timelineTailPosition, h);
ctx.lineTo(timelineTailPosition, h + 2);
ctx.lineTo(timelineHeadPosition, h + 2);
ctx.lineTo(timelineHeadPosition, h);
ctx.fill();
ctx.closePath();
ctx.stroke();
}
});
h += 30;
ctx.beginPath();
ctx.strokeStyle = "#E0E0E0";
ctx.moveTo(0, h - 10);
ctx.lineTo(width, h - 10);
ctx.closePath();
ctx.stroke();
});
h = space;
// For each event, draw the label.
mainEvents.forEach(function(event) {
ctx.fillStyle = "#444";
ctx.font = "12px sans-serif";
text = event.name;
ms = " " + (event.duration < 1 ? event.duration : parseInt(event.duration, 10)) + " ms / " + event.memory + " MiB";
if (x + event.starttime * ratio + ctx.measureText(text + ms).width > width) {
ctx.textAlign = "end";
ctx.font = "10px sans-serif";
ctx.fillStyle = "#777";
xc = x + event.endtime * ratio - 1;
ctx.fillText(ms, xc, h);
xc -= ctx.measureText(ms).width;
ctx.font = "12px sans-serif";
ctx.fillStyle = "#222";
ctx.fillText(text, xc, h);
} else {
ctx.textAlign = "start";
ctx.font = "13px sans-serif";
ctx.fillStyle = "#222";
xc = x + event.starttime * ratio + 1;
ctx.fillText(text, xc, h);
xc += ctx.measureText(text).width;
ctx.font = "11px sans-serif";
ctx.fillStyle = "#777";
ctx.fillText(ms, xc, h);
}
h += gapPerEvent;
});
};
this.drawAll = function(width, threshold)
{
"use strict";
width = width || getContainerWidth();
threshold = threshold || this.getThreshold();
var self = this;
_requests.forEach(function(request) {
self.drawOne(request, _maxRequestTime, threshold, width);
});
};
this.getThreshold = function() {
var threshold = Sfjs.getPreference(_storagePrefix + 'threshold');
if (null === threshold) {
return _threshold;
}
_threshold = parseInt(threshold);
return _threshold;
};
this.setThreshold = function(threshold)
{
_threshold = threshold;
Sfjs.setPreference(_storagePrefix + 'threshold', threshold);
return this;
};
}
function canvasAutoUpdateOnResizeAndSubmit(e) {
e.preventDefault();
canvasManager.drawAll();
}
function canvasAutoUpdateOnThresholdChange(e) {
canvasManager
.setThreshold(query('input[name="threshold"]').value)
.drawAll();
}
var requests_data = {
"max": {{ "%F"|format(collector.events.__section__.endtime) }},
"requests": [
{{ helper.dump_request_data(token, profile, collector.events, collector.events.__section__.origin) }}
{% if profile.children|length %}
,
{% for child in profile.children %}
{{ helper.dump_request_data(child.token, child, child.getcollector('time').events, collector.events.__section__.origin) }}{{ loop.last ? '' : ',' }}
{% endfor %}
{% endif %}
]
};
var canvasManager = new CanvasManager(requests_data.requests, requests_data.max);
query('input[name="threshold"]').value = canvasManager.getThreshold();
canvasManager.drawAll();
// Update the colors of legends.
var timelineLegends = document.querySelectorAll('.sf-profiler-timeline > .legends > span[data-color]');
for (var i = 0; i < timelineLegends.length; ++i) {
var timelineLegend = timelineLegends[i];
timelineLegend.style.borderLeftColor = timelineLegend.getAttribute('data-color');
}
// Bind event handlers
var elementTimelineControl = query('#timeline-control'),
elementThresholdControl = query('input[name="threshold"]');
window.onresize = canvasAutoUpdateOnResizeAndSubmit;
elementTimelineControl.onsubmit = canvasAutoUpdateOnResizeAndSubmit;
elementThresholdControl.onclick = canvasAutoUpdateOnThresholdChange;
elementThresholdControl.onchange = canvasAutoUpdateOnThresholdChange;
elementThresholdControl.onkeyup = canvasAutoUpdateOnThresholdChange;
window.setTimeout(function() {
canvasAutoUpdateOnThresholdChange(null);
}, 50);
//]]>{% endautoescape %}</script>
<svg id="timeline-template" width="0" height="0">
<defs>
<pattern id="subrequest" class="timeline-subrequest-pattern" patternUnits="userSpaceOnUse" width="20" height="20" viewBox="0 0 40 40">
<path d="M0 40L40 0H20L0 20M40 40V20L20 40"/>
</pattern>
</defs>
</svg>
<style type="text/css">
{% include '@WebProfiler/Collector/time.css.twig' %}
</style>
<script>
{% include '@WebProfiler/Collector/time.js' %}
</script>
{% endblock %}
{% macro dump_request_data(token, profile, events, origin) %}
{% macro dump_request_data(token, events, origin) %}
{% autoescape 'js' %}
{% from _self import dump_events %}
{
"id": "{{ token }}",
"left": {{ "%F"|format(events.__section__.origin - origin) }},
"events": [
{{ dump_events(events) }}
]
}
{
id: "{{ token }}",
left: {{ "%F"|format(events.__section__.origin - origin) }},
end: "{{ '%F'|format(events.__section__.endtime) }}",
events: [ {{ dump_events(events) }} ],
}
{% endautoescape %}
{% endmacro %}
@@ -525,32 +167,48 @@
{% autoescape 'js' %}
{% for name, event in events %}
{% if '__section__' != name %}
{
"name": "{{ name }}",
"category": "{{ event.category }}",
"origin": {{ "%F"|format(event.origin) }},
"starttime": {{ "%F"|format(event.starttime) }},
"endtime": {{ "%F"|format(event.endtime) }},
"duration": {{ "%F"|format(event.duration) }},
"memory": {{ "%.1F"|format(event.memory / 1024 / 1024) }},
"periods": [
{%- for period in event.periods -%}
{"start": {{ "%F"|format(period.starttime) }}, "end": {{ "%F"|format(period.endtime) }}}{{ loop.last ? '' : ', ' }}
{%- endfor -%}
]
}{{ loop.last ? '' : ',' }}
{
name: "{{ name }}",
category: "{{ event.category }}",
origin: {{ "%F"|format(event.origin) }},
starttime: {{ "%F"|format(event.starttime) }},
endtime: {{ "%F"|format(event.endtime) }},
duration: {{ "%F"|format(event.duration) }},
memory: {{ "%.1F"|format(event.memory / 1024 / 1024) }},
elements: {},
periods: [
{%- for period in event.periods -%}
{
start: {{ "%F"|format(period.starttime) }},
end: {{ "%F"|format(period.endtime) }},
duration: {{ "%F"|format(period.duration) }},
elements: {}
},
{%- endfor -%}
],
},
{% endif %}
{% endfor %}
{% endautoescape %}
{% endmacro %}
{% macro display_timeline(id, events, colors) %}
{% macro display_timeline(token, events, origin) %}
{% import _self as helper %}
<div class="sf-profiler-timeline">
<div class="legends">
{% for category, color in colors %}
<span data-color="{{ color }}">{{ category }}</span>
{% endfor %}
</div>
<canvas width="680" height="" id="{{ id }}" class="timeline"></canvas>
<div id="legend-{{ token }}" class="legends"></div>
<svg id="timeline-{{ token }}" class="timeline-graph"></svg>
<script>{% autoescape 'js' %}
window.addEventListener('load', function onLoad() {
const theme = new Theme();
new TimelineEngine(
theme,
new SvgRenderer(document.getElementById('timeline-{{ token }}')),
new Legend(document.getElementById('legend-{{ token }}'), theme),
document.getElementById('threshold'),
{{ helper.dump_request_data(token, events, origin) }}
);
});
{% endautoescape %}</script>
</div>
{% endmacro %}

View File

@@ -0,0 +1,457 @@
'use strict';
class TimelineEngine {
/**
* @param {Theme} theme
* @param {Renderer} renderer
* @param {Legend} legend
* @param {Element} threshold
* @param {Object} request
* @param {Number} eventHeight
* @param {Number} horizontalMargin
*/
constructor(theme, renderer, legend, threshold, request, eventHeight = 36, horizontalMargin = 10) {
this.theme = theme;
this.renderer = renderer;
this.legend = legend;
this.threshold = threshold;
this.request = request;
this.scale = renderer.width / request.end;
this.eventHeight = eventHeight;
this.horizontalMargin = horizontalMargin;
this.labelY = Math.round(this.eventHeight * 0.48);
this.periodY = Math.round(this.eventHeight * 0.66);
this.FqcnMatcher = /\\([^\\]+)$/i;
this.origin = null;
this.createEventElements = this.createEventElements.bind(this);
this.createBackground = this.createBackground.bind(this);
this.createPeriod = this.createPeriod.bind(this);
this.render = this.render.bind(this);
this.renderEvent = this.renderEvent.bind(this);
this.renderPeriod = this.renderPeriod.bind(this);
this.onResize = this.onResize.bind(this);
this.isActive = this.isActive.bind(this);
this.threshold.addEventListener('change', this.render);
this.legend.addEventListener('change', this.render);
window.addEventListener('resize', this.onResize);
this.createElements();
this.render();
}
onResize() {
this.renderer.measure();
this.setScale(this.renderer.width / this.request.end);
}
setScale(scale) {
if (scale !== this.scale) {
this.scale = scale;
this.render();
}
}
createElements() {
this.origin = this.renderer.setFullVerticalLine(this.createBorder(), 0);
this.renderer.add(this.origin);
this.request.events
.filter(event => event.category === 'section')
.map(this.createBackground)
.forEach(this.renderer.add);
this.request.events
.map(this.createEventElements)
.forEach(this.renderer.add);
}
createBackground(event) {
const subrequest = event.name === '__section__.child';
const background = this.renderer.create('rect', subrequest ? 'timeline-subrequest' : 'timeline-border');
event.elements = Object.assign(event.elements || {}, { background });
return background;
}
createEventElements(event) {
const { name, category, duration, memory, periods } = event;
const border = this.renderer.setFullHorizontalLine(this.createBorder(), 0);
const lines = periods.map(period => this.createPeriod(period, category));
const label = this.createLabel(this.getShortName(name), duration, memory, periods[0]);
const title = this.renderer.createTitle(name);
const group = this.renderer.group([title, border, label].concat(lines), this.theme.getCategoryColor(event.category));
event.elements = Object.assign(event.elements || {}, { group, label, border });
this.legend.add(event.category)
return group;
}
createLabel(name, duration, memory, period) {
const label = this.renderer.createText(name, period.start * this.scale, this.labelY, 'timeline-label');
const sublabel = this.renderer.createTspan(` ${duration} ms / ${memory} MiB`, 'timeline-sublabel');
label.appendChild(sublabel);
return label;
}
createPeriod(period, category) {
const timeline = this.renderer.createPath(null, 'timeline-period', this.theme.getCategoryColor(category));
period.draw = category === 'section' ? this.renderer.setSectionLine : this.renderer.setPeriodLine;
period.elements = Object.assign(period.elements || {}, { timeline });
return timeline;
}
createBorder() {
return this.renderer.createPath(null, 'timeline-border');
}
isActive(event) {
const { duration, category } = event;
return duration >= this.threshold.value && this.legend.isActive(category);
}
render() {
const events = this.request.events.filter(this.isActive);
const width = this.renderer.width + this.horizontalMargin * 2;
const height = this.eventHeight * events.length;
// Set view box
this.renderer.setViewBox(-this.horizontalMargin, 0, width, height);
// Show 0ms origin
this.renderer.setFullVerticalLine(this.origin, 0);
// Render all events
this.request.events.forEach(event => this.renderEvent(event, events.indexOf(event)));
}
renderEvent(event, index) {
const { name, category, duration, memory, periods, elements } = event;
const { group, label, border, background } = elements;
const visible = index >= 0;
group.setAttribute('visibility', visible ? 'visible' : 'hidden');
if (background) {
background.setAttribute('visibility', visible ? 'visible' : 'hidden');
if (visible) {
const [min, max] = this.getEventLimits(event);
this.renderer.setFullRectangle(background, min * this.scale, max * this.scale);
}
}
if (visible) {
// Position the group
group.setAttribute('transform', `translate(0, ${index * this.eventHeight})`);
// Update top border
this.renderer.setFullHorizontalLine(border, 0);
// render label and ensure it doesn't escape the viewport
this.renderLabel(label, event);
// Update periods
periods.forEach(this.renderPeriod);
}
}
renderLabel(label, event) {
const width = this.getLabelWidth(label);
const [min, max] = this.getEventLimits(event);
const alignLeft = (min * this.scale) + width <= this.renderer.width;
label.setAttribute('x', (alignLeft ? min : max) * this.scale);
label.setAttribute('text-anchor', alignLeft ? 'start' : 'end');
}
renderPeriod(period) {
const { elements, start, duration } = period;
period.draw(elements.timeline, start * this.scale, this.periodY, Math.max(duration * this.scale, 1));
}
getLabelWidth(label) {
if (typeof label.width === 'undefined') {
label.width = label.getBBox().width;
}
return label.width;
}
getEventLimits(event) {
if (typeof event.limits === 'undefined') {
const { periods } = event;
event.limits = [
periods[0].start,
periods[periods.length - 1].end
];
}
return event.limits;
}
getShortName(name) {
const matches = this.FqcnMatcher.exec(name);
if (matches) {
return matches[1];
}
return name;
}
}
class Legend {
constructor(element, theme) {
this.element = element;
this.theme = theme;
this.toggle = this.toggle.bind(this);
this.createCategory = this.createCategory.bind(this);
this.categories = [];
this.theme.getDefaultCategories().forEach(this.createCategory);
}
add(category) {
this.get(category).classList.add('present');
}
createCategory(category) {
const element = document.createElement('button');
element.className = `timeline-category active`;
element.style.borderColor = this.theme.getCategoryColor(category);
element.innerText = category;
element.value = category;
element.type = 'button';
element.addEventListener('click', this.toggle);
this.element.appendChild(element);
this.categories.push(element);
return element;
}
toggle(event) {
event.target.classList.toggle('active');
this.emit('change');
}
isActive(category) {
return this.get(category).classList.contains('active');
}
get(category) {
return this.categories.find(element => element.value === category) || this.createCategory(category);
}
emit(name) {
this.element.dispatchEvent(new Event(name));
}
addEventListener(name, callback) {
this.element.addEventListener(name, callback);
}
removeEventListener(name, callback) {
this.element.removeEventListener(name, callback);
}
}
class SvgRenderer {
/**
* @param {SVGElement} element
*/
constructor(element) {
this.ns = 'http://www.w3.org/2000/svg';
this.width = null;
this.viewBox = {};
this.element = element;
this.add = this.add.bind(this);
this.setViewBox(0, 0, 0, 0);
this.measure();
}
setViewBox(x, y, width, height) {
this.viewBox = { x, y, width, height };
this.element.setAttribute('viewBox', `${x} ${y} ${width} ${height}`);
}
measure() {
this.width = this.element.getBoundingClientRect().width;
}
add(element) {
this.element.appendChild(element);
}
group(elements, className) {
const group = this.create('g', className);
elements.forEach(element => group.appendChild(element));
return group;
}
setHorizontalLine(element, x, y, width) {
element.setAttribute('d', `M${x},${y} h${width}`);
return element;
}
setVerticalLine(element, x, y, height) {
element.setAttribute('d', `M${x},${y} v${height}`);
return element;
}
setFullHorizontalLine(element, y) {
return this.setHorizontalLine(element, this.viewBox.x, y, this.viewBox.width);
}
setFullVerticalLine(element, x) {
return this.setVerticalLine(element, x, this.viewBox.y, this.viewBox.height);
}
setFullRectangle(element, min, max) {
element.setAttribute('x', min);
element.setAttribute('y', this.viewBox.y);
element.setAttribute('width', max - min);
element.setAttribute('height', this.viewBox.height);
}
setSectionLine(element, x, y, width, height = 4, markerSize = 6) {
const totalHeight = height + markerSize;
const maxMarkerWidth = Math.min(markerSize, width / 2);
const widthWithoutMarker = Math.max(0, width - (maxMarkerWidth * 2));
element.setAttribute('d', `M${x},${y + totalHeight} v${-totalHeight} h${width} v${totalHeight} l${-maxMarkerWidth} ${-markerSize} h${-widthWithoutMarker} Z`);
}
setPeriodLine(element, x, y, width, height = 4, markerWidth = 2, markerHeight = 4) {
const totalHeight = height + markerHeight;
const maxMarkerWidth = Math.min(markerWidth, width);
element.setAttribute('d', `M${x + maxMarkerWidth},${y + totalHeight} h${-maxMarkerWidth} v${-totalHeight} h${width} v${height} h${maxMarkerWidth-width}Z`);
}
createText(content, x, y, className) {
const element = this.create('text', className);
element.setAttribute('x', x);
element.setAttribute('y', y);
element.textContent = content;
return element;
}
createTspan(content, className) {
const element = this.create('tspan', className);
element.textContent = content;
return element;
}
createTitle(content) {
const element = this.create('title');
element.textContent = content;
return element;
}
createPath(path = null, className = null, color = null) {
const element = this.create('path', className);
if (path) {
element.setAttribute('d', path);
}
if (color) {
element.setAttribute('fill', color);
}
return element;
}
create(name, className = null) {
const element = document.createElementNS(this.ns, name);
if (className) {
element.setAttribute('class', className);
}
return element;
}
}
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',
};
this.customCategoryColors = [
'#dbab09', // dark yellow
'#ea4aaa', // pink
'#964b00', // brown
'#22863a', // dark green
'#0366d6', // dark blue
'#17a2b8', // teal
];
this.getCategoryColor = this.getCategoryColor.bind(this);
this.getDefaultCategories = this.getDefaultCategories.bind(this);
}
getDefaultCategories() {
return Object.keys(this.reservedCategoryColors);
}
getCategoryColor(category) {
return this.reservedCategoryColors[category] || this.getRandomColor(category);
}
getRandomColor(category) {
// instead of pure randomness, colors are assigned deterministically based on the
// category name, to ensure that each custom category always displays the same color
return this.customCategoryColors[this.hash(category) % this.customCategoryColors.length];
}
// copied from https://github.com/darkskyapp/string-hash
hash(string) {
var hash = 5381;
var i = string.length;
while(i) {
hash = (hash * 33) ^ string.charCodeAt(--i);
}
return hash >>> 0;
}
}

View File

@@ -56,19 +56,7 @@
{% endblock %}
{% block panel %}
{% if collector.messages is empty %}
<h2>Translations</h2>
<div class="empty">
<p>No translations have been called.</p>
</div>
{% else %}
{{ block('panelContent') }}
{% endif %}
{% endblock %}
{% block panelContent %}
<h2>Translation Locales</h2>
<h2>Translation</h2>
<div class="metrics">
<div class="metric">
@@ -77,123 +65,112 @@
</div>
<div class="metric">
<span class="value">{{ collector.fallbackLocales|join(', ')|default('-') }}</span>
<span class="label">Fallback locales</span>
<span class="label">Fallback locale{{ collector.fallbackLocales|length != 1 ? 's' }}</span>
</div>
</div>
<h2>Translation Metrics</h2>
<h2>Messages</h2>
<div class="metrics">
<div class="metric">
<span class="value">{{ collector.countDefines }}</span>
<span class="label">Defined messages</span>
{% if collector.messages is empty %}
<div class="empty">
<p>No translations have been called.</p>
</div>
{% else %}
{% block messages %}
<div class="metric">
<span class="value">{{ collector.countFallbacks }}</span>
<span class="label">Fallback messages</span>
</div>
{# sort translation messages in groups #}
{% set messages_defined, messages_missing, messages_fallback = [], [], [] %}
{% for message in collector.messages %}
{% if message.state == constant('Symfony\\Component\\Translation\\DataCollectorTranslator::MESSAGE_DEFINED') %}
{% set messages_defined = messages_defined|merge([message]) %}
{% elseif message.state == constant('Symfony\\Component\\Translation\\DataCollectorTranslator::MESSAGE_MISSING') %}
{% set messages_missing = messages_missing|merge([message]) %}
{% elseif message.state == constant('Symfony\\Component\\Translation\\DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK') %}
{% set messages_fallback = messages_fallback|merge([message]) %}
{% endif %}
{% endfor %}
<div class="metric">
<span class="value">{{ collector.countMissings }}</span>
<span class="label">Missing messages</span>
</div>
</div>
<div class="sf-tabs">
<div class="tab {{ collector.countMissings == 0 ? 'active' }}">
<h3 class="tab-title">Defined <span class="badge">{{ collector.countDefines }}</span></h3>
<h2>Translation Messages</h2>
<div class="tab-content">
<p class="help">
These messages are correctly translated into the given locale.
</p>
{% block messages %}
{% if messages_defined is empty %}
<div class="empty">
<p>None of the used translation messages are defined for the given locale.</p>
</div>
{% else %}
{% block defined_messages %}
{{ helper.render_table(messages_defined) }}
{% endblock %}
{% endif %}
</div>
</div>
{# sort translation messages in groups #}
{% set messages_defined, messages_missing, messages_fallback = [], [], [] %}
{% for message in collector.messages %}
{% if message.state == constant('Symfony\\Component\\Translation\\DataCollectorTranslator::MESSAGE_DEFINED') %}
{% set messages_defined = messages_defined|merge([message]) %}
{% elseif message.state == constant('Symfony\\Component\\Translation\\DataCollectorTranslator::MESSAGE_MISSING') %}
{% set messages_missing = messages_missing|merge([message]) %}
{% elseif message.state == constant('Symfony\\Component\\Translation\\DataCollectorTranslator::MESSAGE_EQUALS_FALLBACK') %}
{% set messages_fallback = messages_fallback|merge([message]) %}
{% endif %}
{% endfor %}
<div class="tab">
<h3 class="tab-title">Fallback <span class="badge {{ collector.countFallbacks ? 'status-warning' }}">{{ collector.countFallbacks }}</span></h3>
<div class="sf-tabs">
<div class="tab">
<h3 class="tab-title">Defined <span class="badge">{{ collector.countDefines }}</span></h3>
<div class="tab-content">
<p class="help">
These messages are not available for the given locale
but Symfony found them in the fallback locale catalog.
</p>
<div class="tab-content">
<p class="help">
These messages are correctly translated into the given locale.
</p>
{% if messages_fallback is empty %}
<div class="empty">
<p>No fallback translation messages were used.</p>
</div>
{% else %}
{% block fallback_messages %}
{{ helper.render_table(messages_fallback, true) }}
{% endblock %}
{% endif %}
</div>
</div>
{% if messages_defined is empty %}
<div class="empty">
<p>None of the used translation messages are defined for the given locale.</p>
</div>
{% else %}
{% block defined_messages %}
{{ helper.render_table(messages_defined) }}
{% endblock %}
{% endif %}
<div class="tab {{ collector.countMissings > 0 ? 'active' }}">
<h3 class="tab-title">Missing <span class="badge {{ collector.countMissings ? 'status-error' }}">{{ collector.countMissings }}</span></h3>
<div class="tab-content">
<p class="help">
These messages are not available for the given locale and cannot
be found in the fallback locales. Add them to the translation
catalogue to avoid Symfony outputting untranslated contents.
</p>
{% if messages_missing is empty %}
<div class="empty">
<p>There are no messages of this category.</p>
</div>
{% else %}
{% block missing_messages %}
{{ helper.render_table(messages_missing) }}
{% endblock %}
{% endif %}
</div>
</div>
</div>
<div class="tab">
<h3 class="tab-title">Fallback <span class="badge {{ collector.countFallbacks ? 'status-warning' }}">{{ collector.countFallbacks }}</span></h3>
<script>Sfjs.createFilters();</script>
<div class="tab-content">
<p class="help">
These messages are not available for the given locale
but Symfony found them in the fallback locale catalog.
</p>
{% if messages_fallback is empty %}
<div class="empty">
<p>No fallback translation messages were used.</p>
</div>
{% else %}
{% block fallback_messages %}
{{ helper.render_table(messages_fallback, true) }}
{% endblock %}
{% endif %}
</div>
</div>
<div class="tab">
<h3 class="tab-title">Missing <span class="badge {{ collector.countMissings ? 'status-error' }}">{{ collector.countMissings }}</span></h3>
<div class="tab-content">
<p class="help">
These messages are not available for the given locale and cannot
be found in the fallback locales. Add them to the translation
catalogue to avoid Symfony outputting untranslated contents.
</p>
{% if messages_missing is empty %}
<div class="empty">
<p>There are no messages of this category.</p>
</div>
{% else %}
{% block missing_messages %}
{{ helper.render_table(messages_missing) }}
{% endblock %}
{% endif %}
</div>
</div>
</div>
{% endblock messages %}
{% endblock messages %}
{% endif %}
{% endblock %}
{% macro render_table(messages, is_fallback) %}
<table>
<table data-filters>
<thead>
<tr>
<th>Locale</th>
<th data-filter="locale">Locale</th>
{% if is_fallback %}
<th>Fallback locale</th>
{% endif %}
<th>Domain</th>
<th data-filter="domain">Domain</th>
<th>Times used</th>
<th>Message ID</th>
<th>Message Preview</th>
@@ -201,7 +178,7 @@
</thead>
<tbody>
{% for message in messages %}
<tr>
<tr data-filter-locale="{{ message.locale }}" data-filter-domain="{{ message.domain }}">
<td class="font-normal text-small nowrap">{{ message.locale }}</td>
{% if is_fallback %}
<td class="font-normal text-small nowrap">{{ message.fallbackLocale|default('-') }}</td>

View File

@@ -75,20 +75,32 @@
<h2>Rendered Templates</h2>
<table>
<table id="twig-table">
<thead>
<tr>
<th scope="col">Template Name</th>
<th scope="col">Render Count</th>
</tr>
<tr>
<th scope="col">Template Name &amp; Path</th>
<th class="num-col" scope="col">Render Count</th>
</tr>
</thead>
<tbody>
{% for template, count in collector.templates %}
<tr>
{%- set file = collector.templatePaths[template]|default(false) -%}
{%- set link = file ? file|file_link(1) : false -%}
<td>{% if link %}<a href="{{ link }}" title="{{ file }}">{{ template }}</a>{% else %}{{ template }}{% endif %}</td>
<td class="font-normal">{{ count }}</td>
<td>
<span class="sf-icon icon-twig">{{ include('@WebProfiler/Icon/twig.svg') }}</span>
{% 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>
{% else %}
{{ template }}
{% endif %}
</td>
<td class="font-normal num-col">{{ count }}</td>
</tr>
{% endfor %}
</tbody>

View File

@@ -2,7 +2,7 @@
{% block toolbar %}
{% if collector.violationsCount > 0 or collector.calls|length %}
{% set status_color = collector.violationsCount ? 'red' : '' %}
{% set status_color = collector.violationsCount ? 'red' %}
{% set icon %}
{{ include('@WebProfiler/Icon/validator.svg') }}
<span class="sf-toolbar-value">
@@ -59,7 +59,12 @@
<div class="sf-validator-compact hidden" id="sf-trace-{{ loop.index0 }}">
<div class="trace">
{{ caller.file|file_excerpt(caller.line) }}
{{ 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>

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24"><path d="M0 0h24v24H0V0z" fill="none"/><path d="M10 18h4v-2h-4v2zM3 6v2h18V6H3zm3 7h12v-2H6v2z" fill="currentColor"/></svg>

After

Width:  |  Height:  |  Size: 207 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#AAA" d="M20.4 12c-1 0-1.8.6-2.2 1.4l-2.6-.9c.1-.3.1-.5.1-.8 0-1.2-.6-2.2-1.5-2.9l1.5-2.6c.3.1.6.2 1 .2 1.4 0 2.5-1.1 2.5-2.5s-1.1-2.5-2.5-2.5-2.5 1.1-2.5 2.5c0 .8.4 1.5.9 1.9l-1.5 2.6c-.5-.3-1-.4-1.6-.4-.9 0-1.7.3-2.3.9L7.4 6.6c.3-.4.5-.9.5-1.5 0-1.4-1.1-2.5-2.5-2.5S2.7 3.7 2.7 5.1s1.1 2.5 2.5 2.5c.6 0 1.1-.2 1.5-.5L9 9.4c-.5.6-.8 1.4-.8 2.3 0 .7.2 1.4.6 2l-3.9 3.8c-.4-.3-.9-.5-1.5-.5C2 17 .9 18.1.9 19.5S2.2 22 3.6 22s2.5-1.1 2.5-2.5c0-.5-.2-1-.5-1.5l3.8-3.7c.7.7 1.6 1.1 2.6 1.1h.2l.4 2.4c-1 .3-1.7 1.3-1.7 2.4 0 1.4 1.1 2.5 2.5 2.5s2.5-1.1 2.5-2.5-1.1-2.5-2.5-2.5l-.4-2.5c1-.3 1.9-1 2.3-2l2.6.9v.4c0 1.4 1.1 2.5 2.5 2.5s2.5-1.1 2.5-2.5c.1-1.4-1.1-2.5-2.5-2.5z"/></svg>

After

Width:  |  Height:  |  Size: 771 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#AAAAAA" d="M22,4.9C22,3.9,21.1,3,20.1,3H3.9C2.9,3,2,3.9,2,4.9v13.1C2,19.1,2.9,20,3.9,20h16.1c1.1,0,1.9-0.9,1.9-1.9V4.9z M8.3,14.1l-3.1,3.1c-0.2,0.2-0.5,0.3-0.7,0.3S4,17.4,3.8,17.2c-0.4-0.4-0.4-1,0-1.4l3.1-3.1c0.4-0.4,1-0.4,1.4,0S8.7,13.7,8.3,14.1z M20.4,17.2c-0.2,0.2-0.5,0.3-0.7,0.3s-0.5-0.1-0.7-0.3l-3.1-3.1c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l3.1,3.1C20.8,16.2,20.8,16.8,20.4,17.2z M20.4,7.2l-7.6,7.6c-0.2,0.2-0.5,0.3-0.7,0.3s-0.5-0.1-0.7-0.3L3.8,7.2c-0.4-0.4-0.4-1,0-1.4s1-0.4,1.4,0l6.9,6.9L19,5.8c0.4-0.4,1-0.4,1.4,0S20.8,6.8,20.4,7.2z"/></svg>

After

Width:  |  Height:  |  Size: 643 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#aaa" d="M16 9a2 2 0 0 0 2 2h4a2 2 0 0 0 2-2V7a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2h-3V4a1 1 0 0 0-1-1H8a2 2 0 0 0-2-2H2a2 2 0 0 0-2 2v2a2 2 0 0 0 2 2h4a2 2 0 0 0 2-2h3v6H8a2 2 0 0 0-2-2H2a2 2 0 0 0-2 2v2a2 2 0 0 0 2 2h4a2 2 0 0 0 2-2h3v9a1 1 0 0 0 2 0v-5h3a2 2 0 0 0 2 2h4a2 2 0 0 0 2-2v-2a2 2 0 0 0-2-2h-4a2 2 0 0 0-2 2h-3V9zm2.52-2.5h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1 0-1zm0 1.63h3a.5.5 0 0 1 .5.5.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5.5.5 0 0 1 .5-.52zm-13-2.82h-3a.5.5 0 0 1-.5-.5.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 .5.5.5.5 0 0 1-.54.48zm0-1.62h-3a.5.5 0 0 1-.5-.5.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 .5.5.5.5 0 0 1-.54.48zm0 9.62h-3a.5.5 0 0 1-.5-.5.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 .5.5.5.5 0 0 1-.54.48zm0-1.62h-3a.5.5 0 0 1-.5-.5.5.5 0 0 1 .5-.5h3a.5.5 0 0 1 .5.5.5.5 0 0 1-.54.48zm13 2.81h3a.5.5 0 0 1 0 1h-3a.5.5 0 0 1 0-1zm0 1.63h3a.5.5 0 0 1 .5.5.5.5 0 0 1-.5.5h-3a.5.5 0 0 1-.5-.5.5.5 0 0 1 .5-.52z"/></svg>

After

Width:  |  Height:  |  Size: 989 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 12 12"><path fill="#999" d="M10.4 8.4L8 6l2.4-2.4c.8-.8.7-1.6.2-2.2-.6-.5-1.4-.6-2.2.2L6 4 3.6 1.6C2.8.8 2 .9 1.4 1.4c-.5.6-.6 1.4.2 2.2L4 6 1.6 8.4c-.8.8-.7 1.6-.2 2.2.6.6 1.4.6 2.2-.2L6 8l2.4 2.4c.8.8 1.6.7 2.2.2.5-.6.6-1.4-.2-2.2z"/></svg>

After

Width:  |  Height:  |  Size: 319 B

View File

@@ -0,0 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#AAA" d="M11.23,4.8c-3,.83-5.07,3.18-5.07,5.9H7.39c0-2.21,1.77-4,4.17-4.72ZM7.49,0A12.22,12.22,0,0,0,0,11.59H2.07A10.14,10.14,0,0,1,8.23,2Zm8.24,2a10.14,10.14,0,0,1,6.16,9.64H24A12.24,12.24,0,0,0,16.47,0ZM4.41,15.64V10.7a7.57,7.57,0,0,1,15.14,0v4.94l3.3,4.4H1.11Zm4.45,5.3A3.06,3.06,0,0,0,11.92,24H12a3.07,3.07,0,0,0,3.07-3.06Z"/></svg>

After

Width:  |  Height:  |  Size: 432 B

View File

@@ -1 +1 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#AAA" d="M20.1 1H3.9A2.9 2.9 0 0 0 1 3.9v16.3C1 21.7 2.3 23 3.9 23h16.3c1.6 0 2.9-1.3 2.9-2.9V3.9a3 3 0 0 0-3-2.9zm.9 19.1c0 .5-.4.9-.9.9H3.9a.9.9 0 0 1-.9-.9V3.9c0-.5.4-.9.9-.9h16.3c.4 0 .8.4.8.9v16.2zM5 5h14v3H5V5zm0 5h3v9H5v-9zm5 0h9v9h-9v-9z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#AAA" d="M8.932 22.492c.016-6.448-.971-11.295-5.995-11.619 4.69-.352 7.113 2.633 9.298 6.907C12.205 6.354 9.882 1.553 4.8 1.297c7.433.07 10.028 5.9 11.508 14.293 1.171-2.282 3.56-5.553 5.347-1.361-1.594-2.04-3.607-1.617-3.978 8.262H8.933z"/></svg>

Before

Width:  |  Height:  |  Size: 350 B

After

Width:  |  Height:  |  Size: 343 B

View File

@@ -4,16 +4,31 @@
<meta charset="{{ _charset }}" />
<meta name="robots" content="noindex,nofollow" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<title>Symfony Profiler</title>
<title>{% block title %}Symfony Profiler{% endblock %}</title>
<link rel="icon" type="image/x-icon" sizes="16x16" href="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAFEUlEQVR4AZVXA4wm2RMf27bXDM/+3/+sYBGfrbVtezc6BWtzfPbYXtvDL9906t6v0vWl05me7q1JzXuvvu4yXnvZgJ9hH6bwZYXLFR739vauUGuDwhq1L1N4Uv/tRYUhFjwcg49hn6aYr1V4TiGp86CoP9Oh1tV414KnM6t9fHymKUZ3DAI0hW4b1AyK3lE8phh5OxWeoJgUGhi5mLm95YzBwcHuhIQEV1JSEoWGhoKWHxYWFmenhJ/B5W0GwZpDt5Ovry9lZWWRyWOu5ORk7JsUpogsq5gnmISTU+HKQoLFQv/qq6/os88+I+EVFRUlSsRZ5oRiVmwlXMWShQkahUdERJCfnx/vd+3aRTU1NXTixAmqrq6mK1eu0PTp05mnrmD+QK6XhLO0XP2O2FJAQICRjjMU4P1PP/1EfX19NGfOHM8Z0N7ezueQkBBXYGAgSWIaQ5Em2T5QzFNSUig9PV3OHOe4uDjZ87p//34C7Nm7x/NcRUUFAX799Vec8Y7m7+8Pz92SfBDXr7VwPYRbxn/MmDG8Tps2jQBd3V30/PPPe35/6qmnaPXq1TR69Gg+h4eHiwwosdLT4dBkQDSXWmJiIq/vv/8+/fvvv3ThwgWqr6+n/Px8oyCmAerq6jy03Nxc2Yv7ySSjQzrmi4i92fVpaWlYOZ79/f2MW7dtpSlTptDp06epo6ODPvroI850ASiGdyZOnEjXrl2jyspKT4XA9cgjkaPL/D8UWG62HokieyQQoKSkRGiMs2bNotraWmprayOBNWvWyO+scGdnp5zF/WYvLEb8TwpRykp1MV7feust6uzqJMD169fpueeeY/rDDz/MKzzgdrsJoGkaffvtt/TFF19wQsIDmzZtssojt+6Fo1CgzKiAvAB3DRs2jAULtLS0eErPGB5Ad3c3lZaWUnFxMfeAd955h5+JjY3FaqXAPwhBnRCNySK4b98+Aoilv/z6i/zGggSk1g0opWupAMvGP91yt96zadWqVdTc3Ezz58/31LOAy+US6zgHBP766y+mDR8+HBUgFWSnQI2EAFnqlpcaGxsJIFkMN8L9AnPnzmX6jRs3SACeAi0vL888JwYPgTEJpauhnADo6/LSgQMHCHD37l2Cp15//XXq7eslgKb+Fi1exM9lZmbaCDclIcpQQhATE4OVsrOzuamg+cyePZuzG64Hrlu3jp9ZuWolCdy+fZueeOIJpkdHR1sLHqgM0Yh0bTRz1m7fvp2KiopYkYKCApo8ebLZIwzlFeXSOXEnsLPe2Ij+p5DbYYdOdOtDQ0rNjFya5sTcsGGDcTDZoXTcNoVBMoxWyzDS2yXmOyeUtGSskmDjx4/nRgPAfBDmMpZtUIbRcsi2GsfSD2QYyd2OcdmyZSSwdu1apuXk5GB16v4bak0yX0imyIUEgwNovFTglhMZGcm0srIy43zAVUxuTLbW4xn17Fci23wly9dngUummrTaixcvMpOtW7fiiBwQpqKYU9efHuxDJE5hC9wvL9TW1RLg+PHjPGTQ8wsLC4WpDC5Y5UR4k5qKMSLT6lqeAiX0nuAaMmSI9sMPP9CZM2foyJEj9O677wpTVIuTjidNp0HibvttoH9E5OMqbWKkSaNSlojldoLF7TEP+nUEmKI62y1kOBINbVaNarcI0PuGGUlHyfYvLHg7/jhFSFYqZh0P8KHSptd5ksOPU3tvqAEUot/hFmOIYJLp87wGe9Dwm95eg5xa/R8G6d8U5EcFhwAAAABJRU5ErkJggg==">
{% block head %}
<style>
<style{% if csp_style_nonce is defined and csp_style_nonce %} nonce="{{ csp_style_nonce }}"{% endif %}>
{{ include('@WebProfiler/Profiler/profiler.css.twig') }}
</style>
{% endblock %}
</head>
<body>
<script{% if csp_script_nonce is defined and csp_script_nonce %} nonce="{{ csp_script_nonce }}"{% endif %}>
if (null === localStorage.getItem('symfony/profiler/theme') || 'theme-auto' === localStorage.getItem('symfony/profiler/theme')) {
document.body.classList.add((matchMedia('(prefers-color-scheme: dark)').matches ? 'theme-dark' : 'theme-light'));
// needed to respond dynamically to OS changes without having to refresh the page
window.matchMedia('(prefers-color-scheme: dark)').addEventListener('change', (e) => {
document.body.classList.remove('theme-light', 'theme-dark');
document.body.classList.add(e.matches ? 'theme-dark' : 'theme-light');
});
} else {
document.body.classList.add(localStorage.getItem('symfony/profiler/theme'));
}
document.body.classList.add(localStorage.getItem('symfony/profiler/width') || 'width-normal');
</script>
{% block body '' %}
</body>
</html>

View File

@@ -1,10 +1,11 @@
{# This file is partially duplicated in TwigBundle/Resources/views/base_js.html.twig. If you
make any change in this file, verify the same change is needed in the other file. #}
{# This file is partially duplicated in src/Symfony/Component/ErrorHandler/Resources/assets/js/exception.js.
If you make any change in this file, verify the same change is needed in the other file. #}
<script{% if csp_script_nonce is defined and csp_script_nonce %} nonce="{{ csp_script_nonce }}"{% endif %}>/*<![CDATA[*/
{# Caution: the contents of this file are processed by Twig before loading
them as JavaScript source code. Always use '/*' comments instead
of '//' comments to avoid impossible-to-debug side-effects #}
if (typeof Sfjs === 'undefined' || typeof Sfjs.loadToolbar === 'undefined') {
Sfjs = (function() {
"use strict";
@@ -24,10 +25,36 @@
var profilerStorageKey = 'symfony/profiler/';
var request = function(url, onSuccess, onError, payload, options) {
var addEventListener;
var el = document.createElement('div');
if (!('addEventListener' in el)) {
addEventListener = function (element, eventName, callback) {
element.attachEvent('on' + eventName, callback);
};
} else {
addEventListener = function (element, eventName, callback) {
element.addEventListener(eventName, callback, false);
};
}
if (navigator.clipboard) {
document.querySelectorAll('[data-clipboard-text]').forEach(function(element) {
removeClass(element, 'hidden');
element.addEventListener('click', function() {
navigator.clipboard.writeText(element.getAttribute('data-clipboard-text'));
})
});
}
var request = function(url, onSuccess, onError, payload, options, tries) {
var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');
options = options || {};
options.maxTries = options.maxTries || 0;
options.retry = options.retry || false;
tries = tries || 1;
/* this delays for 125, 375, 625, 875, and 1000, ... */
var delay = tries < 5 ? (tries - 0.5) * 250 : 1000;
xhr.open(options.method || 'GET', url, true);
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
xhr.onreadystatechange = function(state) {
@@ -35,11 +62,13 @@
return null;
}
if (xhr.status == 404 && options.maxTries > 1) {
setTimeout(function(){
options.maxTries--;
request(url, onSuccess, onError, payload, options);
}, 1000);
if (xhr.status == 404 && options.retry && !options.stop) {
setTimeout(function() {
if (options.stop) {
return;
}
request(url, onSuccess, onError, payload, options, tries + 1);
}, delay);
return null;
}
@@ -50,6 +79,11 @@
(onError || noop)(xhr);
}
};
if (options.onSend) {
options.onSend(tries);
}
xhr.send(payload || '');
};
@@ -83,6 +117,10 @@
if (ret = allHeaders.match(/^x-debug-token-link:\s+(.*)$/im)) {
stackElement.profilerUrl = ret[1];
}
if (ret = allHeaders.match(/^Symfony-Debug-Toolbar-Replace:\s+(.*)$/im)) {
stackElement.toolbarReplaceFinished = false;
stackElement.toolbarReplace = '1' === ret[1];
}
};
var successStreak = 4;
@@ -124,7 +162,7 @@
var nbOfAjaxRequest = tbody.rows.length;
if (nbOfAjaxRequest >= 100) {
tbody.deleteRow(nbOfAjaxRequest - 1);
tbody.deleteRow(0);
}
var request = requestStack[index];
@@ -132,6 +170,14 @@
var row = document.createElement('tr');
request.DOMNode = row;
var requestNumberCell = document.createElement('td');
requestNumberCell.textContent = index + 1;
row.appendChild(requestNumberCell);
var profilerCell = document.createElement('td');
profilerCell.textContent = 'n/a';
row.appendChild(profilerCell);
var methodCell = document.createElement('td');
methodCell.textContent = request.method;
row.appendChild(methodCell);
@@ -164,29 +210,47 @@
durationCell.textContent = 'n/a';
row.appendChild(durationCell);
var profilerCell = document.createElement('td');
profilerCell.textContent = 'n/a';
row.appendChild(profilerCell);
request.liveDurationHandle = setInterval(function() {
durationCell.textContent = (new Date() - request.start) + ' ms';
}, 100);
row.className = 'sf-ajax-request sf-ajax-request-loading';
tbody.insertBefore(row, tbody.firstChild);
tbody.insertBefore(row, null);
var toolbarInfo = document.querySelector('.sf-toolbar-block-ajax .sf-toolbar-info');
toolbarInfo.scrollTop = toolbarInfo.scrollHeight;
renderAjaxRequests();
};
var finishAjaxRequest = function(index) {
var request = requestStack[index];
clearInterval(request.liveDurationHandle);
if (!request.DOMNode) {
return;
}
if (request.toolbarReplace && !request.toolbarReplaceFinished && request.profile) {
/* Flag as complete because finishAjaxRequest can be called multiple times. */
request.toolbarReplaceFinished = true;
/* Search up through the DOM to find the toolbar's container ID. */
for (var elem = request.DOMNode; elem && elem !== document; elem = elem.parentNode) {
if (elem.id.match(/^sfwdt/)) {
Sfjs.loadToolbar(elem.id.replace(/^sfwdt/, ''), request.profile);
break;
}
}
}
pendingRequests--;
var row = request.DOMNode;
/* Unpack the children from the row */
var methodCell = row.children[0];
var statusCodeCell = row.children[2];
var profilerCell = row.children[1];
var methodCell = row.children[2];
var statusCodeCell = row.children[4];
var statusCodeElem = statusCodeCell.children[0];
var durationCell = row.children[4];
var profilerCell = row.children[5];
var durationCell = row.children[6];
if (request.error) {
row.className = 'sf-ajax-request sf-ajax-request-error';
@@ -211,7 +275,7 @@
}
if (request.duration) {
durationCell.textContent = request.duration + 'ms';
durationCell.textContent = request.duration + ' ms';
}
if (request.profilerUrl) {
@@ -225,19 +289,6 @@
renderAjaxRequests();
};
var addEventListener;
var el = document.createElement('div');
if (!('addEventListener' in el)) {
addEventListener = function (element, eventName, callback) {
element.attachEvent('on' + eventName, callback);
};
} else {
addEventListener = function (element, eventName, callback) {
element.addEventListener(eventName, callback, false);
};
}
{% if excluded_ajax_paths is defined %}
if (window.fetch && window.fetch.polyfill === undefined) {
var oldFetch = window.fetch;
@@ -279,6 +330,8 @@
stackElement.statusCode = r.status;
stackElement.profile = r.headers.get('x-debug-token');
stackElement.profilerUrl = r.headers.get('x-debug-token-link');
stackElement.toolbarReplaceFinished = false;
stackElement.toolbarReplace = '1' === r.headers.get('Symfony-Debug-Toolbar-Replace');
finishAjaxRequest(idx);
}, function (e){
stackElement.error = true;
@@ -298,13 +351,13 @@
/* prevent logging AJAX calls to static and inline files, like templates */
var path = url;
if (url.substr(0, 1) === '/') {
if (url.slice(0, 1) === '/') {
if (0 === url.indexOf('{{ request.basePath|e('js') }}')) {
path = url.substr({{ request.basePath|length }});
path = url.slice({{ request.basePath|length }});
}
}
else if (0 === url.indexOf('{{ (request.schemeAndHttpHost ~ request.basePath)|e('js') }}')) {
path = url.substr({{ (request.schemeAndHttpHost ~ request.basePath)|length }});
path = url.slice({{ (request.schemeAndHttpHost ~ request.basePath)|length }});
}
if (!path.match(new RegExp({{ excluded_ajax_paths|json_encode|raw }}))) {
@@ -356,6 +409,14 @@
renderAjaxRequests: renderAjaxRequests,
getSfwdt: function(token) {
if (!this.sfwdt) {
this.sfwdt = document.getElementById('sfwdt' + token);
}
return this.sfwdt;
},
load: function(selector, url, onSuccess, onError, options) {
var el = document.getElementById(selector);
@@ -366,12 +427,15 @@
el.innerHTML = xhr.responseText;
el.setAttribute('data-sfurl', url);
removeClass(el, 'loading');
var pending = pendingRequests;
for (var i = 0; i < requestStack.length; i++) {
startAjaxRequest(i);
if (requestStack[i].duration) {
finishAjaxRequest(i);
}
}
/* Revert the pending state in case there was a start called without a finish above. */
pendingRequests = pending;
(onSuccess || noop)(xhr, el);
},
function(xhr) { (onError || noop)(xhr, el); },
@@ -383,6 +447,189 @@
return this;
},
showToolbar: function(token) {
var sfwdt = this.getSfwdt(token);
removeClass(sfwdt, 'sf-display-none');
if (getPreference('toolbar/displayState') == 'none') {
document.getElementById('sfToolbarMainContent-' + token).style.display = 'none';
document.getElementById('sfToolbarClearer-' + token).style.display = 'none';
document.getElementById('sfMiniToolbar-' + token).style.display = 'block';
} else {
document.getElementById('sfToolbarMainContent-' + token).style.display = 'block';
document.getElementById('sfToolbarClearer-' + token).style.display = 'block';
document.getElementById('sfMiniToolbar-' + token).style.display = 'none';
}
},
hideToolbar: function(token) {
var sfwdt = this.getSfwdt(token);
addClass(sfwdt, 'sf-display-none');
},
initToolbar: function(token) {
this.showToolbar(token);
var hideButton = document.getElementById('sfToolbarHideButton-' + token);
var hideButtonSvg = hideButton.querySelector('svg');
hideButtonSvg.setAttribute('aria-hidden', 'true');
hideButtonSvg.setAttribute('focusable', 'false');
addEventListener(hideButton, 'click', function (event) {
event.preventDefault();
var p = this.parentNode;
p.style.display = 'none';
(p.previousElementSibling || p.previousSibling).style.display = 'none';
document.getElementById('sfMiniToolbar-' + token).style.display = 'block';
setPreference('toolbar/displayState', 'none');
});
var showButton = document.getElementById('sfToolbarMiniToggler-' + token);
var showButtonSvg = showButton.querySelector('svg');
showButtonSvg.setAttribute('aria-hidden', 'true');
showButtonSvg.setAttribute('focusable', 'false');
addEventListener(showButton, 'click', function (event) {
event.preventDefault();
var elem = this.parentNode;
if (elem.style.display == 'none') {
document.getElementById('sfToolbarMainContent-' + token).style.display = 'none';
document.getElementById('sfToolbarClearer-' + token).style.display = 'none';
elem.style.display = 'block';
} else {
document.getElementById('sfToolbarMainContent-' + token).style.display = 'block';
document.getElementById('sfToolbarClearer-' + token).style.display = 'block';
elem.style.display = 'none'
}
setPreference('toolbar/displayState', 'block');
});
},
loadToolbar: function(token, newToken) {
var that = this;
var triesCounter = document.getElementById('sfLoadCounter-' + token);
var options = {
retry: true,
onSend: function (count) {
if (count === 3) {
that.initToolbar(token);
}
if (triesCounter) {
triesCounter.textContent = count;
}
},
};
var cancelButton = document.getElementById('sfLoadCancel-' + token);
if (cancelButton) {
addEventListener(cancelButton, 'click', function (event) {
event.preventDefault();
options.stop = true;
that.hideToolbar(token);
});
}
newToken = (newToken || token);
this.load(
'sfwdt' + token,
'{{ url("_wdt", { "token": "xxxxxx" })|escape('js') }}'.replace(/xxxxxx/, newToken),
function(xhr, el) {
/* Do nothing in the edge case where the toolbar has already been replaced with a new one */
if (!document.getElementById('sfToolbarMainContent-' + newToken)) {
return;
}
/* Evaluate in global scope scripts embedded inside the toolbar */
var i, scripts = [].slice.call(el.querySelectorAll('script'));
for (i = 0; i < scripts.length; ++i) {
eval.call({}, scripts[i].firstChild.nodeValue);
}
el.style.display = -1 !== xhr.responseText.indexOf('sf-toolbarreset') ? 'block' : 'none';
if (el.style.display == 'none') {
return;
}
that.initToolbar(newToken);
/* Handle toolbar-info position */
var toolbarBlocks = [].slice.call(el.querySelectorAll('.sf-toolbar-block'));
for (i = 0; i < toolbarBlocks.length; ++i) {
toolbarBlocks[i].onmouseover = function () {
var toolbarInfo = this.querySelectorAll('.sf-toolbar-info')[0];
var pageWidth = document.body.clientWidth;
var elementWidth = toolbarInfo.offsetWidth;
var leftValue = (elementWidth + this.offsetLeft) - pageWidth;
var rightValue = (elementWidth + (pageWidth - this.offsetLeft)) - pageWidth;
/* Reset right and left value, useful on window resize */
toolbarInfo.style.right = '';
toolbarInfo.style.left = '';
if (elementWidth > pageWidth) {
toolbarInfo.style.left = 0;
}
else if (leftValue > 0 && rightValue > 0) {
toolbarInfo.style.right = (rightValue * -1) + 'px';
} else if (leftValue < 0) {
toolbarInfo.style.left = 0;
} else {
toolbarInfo.style.right = '0px';
}
};
}
renderAjaxRequests();
addEventListener(document.querySelector('.sf-toolbar-ajax-clear'), 'click', function() {
requestStack = [];
renderAjaxRequests();
successStreak = 4;
document.querySelector('.sf-toolbar-ajax-request-list').innerHTML = '';
});
addEventListener(document.querySelector('.sf-toolbar-block-ajax'), 'mouseenter', function (event) {
var elem = document.querySelector('.sf-toolbar-block-ajax .sf-toolbar-info');
elem.scrollTop = elem.scrollHeight;
});
addEventListener(document.querySelector('.sf-toolbar-block-ajax > .sf-toolbar-icon'), 'click', function (event) {
event.preventDefault();
toggleClass(this.parentNode, 'hover');
});
var dumpInfo = document.querySelector('.sf-toolbar-block-dump .sf-toolbar-info');
if (null !== dumpInfo) {
addEventListener(dumpInfo, 'sfbeforedumpcollapse', function () {
dumpInfo.style.minHeight = dumpInfo.getBoundingClientRect().height+'px';
});
addEventListener(dumpInfo, 'mouseleave', function () {
dumpInfo.style.minHeight = '';
});
}
},
function(xhr) {
if (xhr.status !== 0 && !options.stop) {
var sfwdt = that.getSfwdt(token);
sfwdt.innerHTML = '\
<div class="sf-toolbarreset">\
<div class="sf-toolbar-icon"><svg width="26" height="28" xmlns="http://www.w3.org/2000/svg" version="1.1" x="0px" y="0px" viewBox="0 0 26 28" enable-background="new 0 0 26 28" xml:space="preserve"><path fill="#FFFFFF" d="M13 0C5.8 0 0 5.8 0 13c0 7.2 5.8 13 13 13c7.2 0 13-5.8 13-13C26 5.8 20.2 0 13 0z M20 7.5 c-0.6 0-1-0.3-1-0.9c0-0.2 0-0.4 0.2-0.6c0.1-0.3 0.2-0.3 0.2-0.4c0-0.3-0.5-0.4-0.7-0.4c-2 0.1-2.5 2.7-2.9 4.8l-0.2 1.1 c1.1 0.2 1.9 0 2.4-0.3c0.6-0.4-0.2-0.8-0.1-1.3C18 9.2 18.4 9 18.7 8.9c0.5 0 0.8 0.5 0.8 1c0 0.8-1.1 2-3.3 1.9 c-0.3 0-0.5 0-0.7-0.1L15 14.1c-0.4 1.7-0.9 4.1-2.6 6.2c-1.5 1.8-3.1 2.1-3.8 2.1c-1.3 0-2.1-0.6-2.2-1.6c0-0.9 0.8-1.4 1.3-1.4 c0.7 0 1.2 0.5 1.2 1.1c0 0.5-0.2 0.6-0.4 0.7c-0.1 0.1-0.3 0.2-0.3 0.4c0 0.1 0.1 0.3 0.4 0.3c0.5 0 0.9-0.3 1.2-0.5 c1.3-1 1.7-2.9 2.4-6.2l0.1-0.8c0.2-1.1 0.5-2.3 0.8-3.5c-0.9-0.7-1.4-1.5-2.6-1.8c-0.8-0.2-1.3 0-1.7 0.4C8.4 10 8.6 10.7 9 11.1 l0.7 0.7c0.8 0.9 1.3 1.7 1.1 2.7c-0.3 1.6-2.1 2.8-4.3 2.1c-1.9-0.6-2.2-1.9-2-2.7c0.2-0.6 0.7-0.8 1.2-0.6 c0.5 0.2 0.7 0.8 0.6 1.3c0 0.1 0 0.1-0.1 0.3C6 15 5.9 15.2 5.9 15.3c-0.1 0.4 0.4 0.7 0.8 0.8c0.8 0.3 1.7-0.2 1.9-0.9 c0.2-0.6-0.2-1.1-0.4-1.2l-0.8-0.9c-0.4-0.4-1.2-1.5-0.8-2.8c0.2-0.5 0.5-1 0.9-1.4c1-0.7 2-0.8 3-0.6c1.3 0.4 1.9 1.2 2.8 1.9 c0.5-1.3 1.1-2.6 2-3.8c0.9-1 2-1.7 3.3-1.8C20 4.8 21 5.4 21 6.3C21 6.7 20.8 7.5 20 7.5z"/></svg></div>\
An error occurred while loading the web debug toolbar. <a href="{{ url("_profiler_home")|escape('js') }}' + newToken + '>Open the web profiler.</a>\
</div>\
';
sfwdt.setAttribute('class', 'sf-toolbar sf-error-toolbar');
}
},
options
);
return this;
},
toggle: function(selector, elOn, elOff) {
var tmp = elOn.style.display,
el = document.getElementById(selector);
@@ -402,17 +649,18 @@
/* create the tab navigation for each group of tabs */
for (var i = 0; i < tabGroups.length; i++) {
var tabs = tabGroups[i].querySelectorAll('.tab');
var tabs = tabGroups[i].querySelectorAll(':scope > .tab');
var tabNavigation = document.createElement('ul');
tabNavigation.className = 'tab-navigation';
var selectedTabId = 'tab-' + i + '-0'; /* select the first tab by default */
for (var j = 0; j < tabs.length; j++) {
var tabId = 'tab-' + i + '-' + j;
var tabTitle = tabs[j].querySelector('.tab-title').innerHTML;
var tabNavigationItem = document.createElement('li');
tabNavigationItem.setAttribute('data-tab-id', tabId);
if (j == 0) { addClass(tabNavigationItem, 'active'); }
if (hasClass(tabs[j], 'active')) { selectedTabId = tabId; }
if (hasClass(tabs[j], 'disabled')) { addClass(tabNavigationItem, 'disabled'); }
tabNavigationItem.innerHTML = tabTitle;
tabNavigation.appendChild(tabNavigationItem);
@@ -422,11 +670,12 @@
}
tabGroups[i].insertBefore(tabNavigation, tabGroups[i].firstChild);
addClass(document.querySelector('[data-tab-id="' + selectedTabId + '"]'), 'active');
}
/* display the active tab and add the 'click' event listeners */
for (i = 0; i < tabGroups.length; i++) {
tabNavigation = tabGroups[i].querySelectorAll('.tab-navigation li');
tabNavigation = tabGroups[i].querySelectorAll(':scope > .tab-navigation li');
for (j = 0; j < tabNavigation.length; j++) {
tabId = tabNavigation[j].getAttribute('data-tab-id');
@@ -528,9 +777,92 @@
});
}
/* Prevents from disallowing clicks on "copy to clipboard" elements inside toggles */
var copyToClipboardElements = toggles[i].querySelectorAll('span[data-clipboard-text]');
for (var k = 0; k < copyToClipboardElements.length; k++) {
addEventListener(copyToClipboardElements[k], 'click', function(e) {
e.stopPropagation();
});
}
toggles[i].setAttribute('data-processed', 'true');
}
}
},
initializeLogsTable: function() {
Sfjs.updateLogsTable();
document.querySelectorAll('.log-filter input').forEach((input) => {
input.addEventListener('change', () => { Sfjs.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;
});
Sfjs.updateLogsTable();
});
});
document.body.addEventListener('click', (event) => {
document.querySelectorAll('details.log-filter').forEach((filterElement) => {
if (!filterElement.contains(event.target) && filterElement.open) {
filterElement.open = false;
}
});
});
},
updateLogsTable: function() {
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);
const logs = document.querySelector('table.logs');
if (null === logs) {
return;
}
/* 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;
/* update the currently selected "log type" tab */
document.querySelectorAll('#log-filter-type li').forEach((tab) => tab.classList.remove('active'));
document.querySelector(`#log-filter-type input[value="${selectedType}"]`).parentElement.classList.add('active');
},
};
})();
@@ -538,5 +870,5 @@
Sfjs.createTabs();
Sfjs.createToggles();
});
}
/*]]>*/</script>

View File

@@ -0,0 +1,25 @@
{% block toolbar %}
{% set icon %}
{{ include('@WebProfiler/Icon/symfony.svg') }}
<span class="sf-toolbar-value sf-toolbar-ajax-request-counter">
Loading&hellip;
</span>
{% endset %}
{% set text %}
<div class="sf-toolbar-info-piece">
<b>Loading the web debug toolbar&hellip;</b>
</div>
<div class="sf-toolbar-info-piece">
Attempt #<span id="sfLoadCounter-{{ token }}"></span>
</div>
<div class="sf-toolbar-info-piece">
<b>
<button class="sf-cancel-button" type="button" id="sfLoadCancel-{{ token }}" title="Cancel loading">Cancel</button>
</b>
</div>
{% endset %}
{{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { link: profiler_url }) }}
{% endblock %}

View File

@@ -6,7 +6,8 @@
<div id="summary">
{% block summary %}
{% if profile is defined %}
{% set status_code = ('request' in profile.collectors|keys) ? profile.getcollector('request').statuscode|default(0) : 0 %}
{% set request_collector = profile.collectors.request|default(false) %}
{% set status_code = request_collector ? request_collector.statuscode|default(0) : 0 %}
{% set css_class = status_code > 399 ? 'status-error' : status_code > 299 ? 'status-warning' : 'status-success' %}
<div class="status {{ css_class }}">
@@ -16,10 +17,13 @@
<a href="{{ profile.url }}">{{ profile.url }}</a>
{% else %}
{{ profile.url }}
{% set referer = request_collector ? request_collector.requestheaders.get('referer') : null %}
{% if referer %}
<a href="{{ referer }}" class="referer">Return to referer URL</a>
{% endif %}
{% endif %}
</h2>
{% set request_collector = profile.collectors.request|default(false) %}
{% if request_collector and request_collector.redirect -%}
{%- set redirect = request_collector.redirect -%}
{%- set controller = redirect.controller -%}
@@ -44,18 +48,22 @@
</dl>
{%- endif %}
{% if request_collector and request_collector.forward|default(false) and request_collector.forward.controller.class is defined -%}
{%- set forward = request_collector.forward -%}
{%- set controller = forward.controller -%}
{% if request_collector and request_collector.forwardtoken -%}
{% set forward_profile = profile.childByToken(request_collector.forwardtoken) %}
{% set controller = forward_profile ? forward_profile.collector('request').controller : 'n/a' %}
<dl class="metadata">
<dt>Forwarded to</dt>
<dd>
{% set link = controller.file|file_link(controller.line) -%}
{% set link = controller.file is defined ? controller.file|file_link(controller.line) : null -%}
{%- if link %}<a href="{{ link }}" title="{{ controller.file }}">{% endif -%}
{{- controller.class|abbr_class|striptags -}}
{{- controller.method ? ' :: ' ~ controller.method }}
{% if controller.class is defined %}
{{- controller.class|abbr_class|striptags -}}
{{- controller.method ? ' :: ' ~ controller.method -}}
{% else %}
{{- controller -}}
{% endif %}
{%- if link %}</a>{% endif %}
(<a href="{{ path('_profiler', { token: forward.token }) }}">{{ forward.token }}</a>)
(<a href="{{ path('_profiler', { token: request_collector.forwardtoken }) }}">{{ request_collector.forwardtoken }}</a>)
</dd>
</dl>
{%- endif %}
@@ -73,7 +81,7 @@
</dd>
<dt>Profiled on</dt>
<dd>{{ profile.time|date('r') }}</dd>
<dd><time datetime="{{ profile.time|date('c') }}">{{ profile.time|date('r') }}</time></dd>
<dt>Token</dt>
<dd>{{ profile.token }}</dd>
@@ -100,7 +108,7 @@
{{ include('@WebProfiler/Icon/search.svg') }} <span class="hidden-small">Search</span>
</a>
{{ render(path('_profiler_search_bar', request.query.all)) }}
{{ render(controller('web_profiler.controller.profiler::searchBarAction', request.query.all)) }}
</div>
</div>
@@ -115,13 +123,15 @@
{%- endif -%}
{%- endset %}
{% if menu is not empty %}
<li class="{{ name }} {{ name == panel ? 'selected' : '' }}">
<li class="{{ name }} {{ name == panel ? 'selected' }}">
<a href="{{ path('_profiler', { token: token, panel: name }) }}">{{ menu|raw }}</a>
</li>
{% endif %}
{% endfor %}
</ul>
{% endif %}
{{ include('@WebProfiler/Profiler/settings.html.twig') }}
</div>
<div id="collector-wrapper">
@@ -138,6 +148,6 @@
event.preventDefault();
Sfjs.toggleClass(document.getElementById('sidebar'), 'expanded');
})
}())
}());
</script>
{% endblock %}

View File

@@ -1,5 +1,13 @@
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
{% macro profile_search_filter(request, result, property) %}
{%- if request.hasSession -%}
<a href="{{ path('_profiler_search_results', request.query.all|merge({token: result.token})|merge({ (property): result[property] })) }}" title="Search"><span title="Search" class="sf-icon sf-search">{{ include('@WebProfiler/Icon/search.svg') }}</span></a>
{%- endif -%}
{% endmacro %}
{% import _self as helper %}
{% block summary %}
<div class="status">
<div class="container">
@@ -32,28 +40,14 @@
<span class="label {{ css_class }}">{{ result.status_code|default('n/a') }}</span>
</td>
<td>
<span class="nowrap">{{ result.ip }}</span>
{% if request.session is not null %}
<a href="{{ path('_profiler_search_results', request.query.all|merge({'ip': result.ip, 'token': result.token})) }}" title="Search">
<span title="Search" class="sf-icon sf-search">{{ include('@WebProfiler/Icon/search.svg') }}</span>
</a>
{% endif %}
<span class="nowrap">{{ result.ip }} {{ helper.profile_search_filter(request, result, 'ip') }}</span>
</td>
<td>
{{ result.method }}
{% if request.session is not null %}
<a href="{{ path('_profiler_search_results', request.query.all|merge({'method': result.method, 'token': result.token})) }}" title="Search">
<span title="Search" class="sf-icon sf-search">{{ include('@WebProfiler/Icon/search.svg') }}</span>
</a>
{% endif %}
<span class="nowrap">{{ result.method }} {{ helper.profile_search_filter(request, result, 'method') }}</span>
</td>
<td class="break-long-words">
{{ result.url }}
{% if request.session is not null %}
<a href="{{ path('_profiler_search_results', request.query.all|merge({'url': result.url, 'token': result.token})) }}" title="Search">
<span title="Search" class="sf-icon sf-search">{{ include('@WebProfiler/Icon/search.svg') }}</span>
</a>
{% endif %}
{{ helper.profile_search_filter(request, result, 'url') }}
</td>
<td class="text-small">
<span class="nowrap">{{ result.time|date('d-M-Y') }}</span>

View File

@@ -17,7 +17,7 @@
<div class="form-group">
<label for="status_code">Status</label>
<input type="number" name="status_code" id="status_code" value="{{ status_code }}">
<input type="number" name="status_code" id="status_code" min="100" max="599" value="{{ status_code }}">
</div>
<div class="form-group">

View File

@@ -0,0 +1,193 @@
<style>
#open-settings {
color: var(--color-muted);
display: block;
margin: 15px 15px 5px;
}
.modal-wrap {
-webkit-transition: all 0.3s ease-in-out;
align-items: center;
background: rgba(0, 0, 0, 0.8);
display: flex;
height: 100%;
justify-content: center;
left: 0;
opacity: 1;
overflow: auto;
position: fixed;
top: 0;
transition: all 0.3s ease-in-out;
visibility: hidden;
width: 100%;
z-index: 100000;
}
.modal-wrap.visible {
opacity: 1;
visibility: visible;
}
.modal-wrap .modal-container {
box-shadow: 5px 5px 10px 0px rgba(0, 0, 0, 0.5);
color: var(--base-6);
margin: 1em;
max-width: 94%;
width: 600px;
}
.modal-wrap .modal-header {
align-items: center;
background: var(--table-header);
display: flex;
justify-content: space-between;
padding: 15px 30px;
}
.modal-wrap .modal-header h3 {
color: var(--base-6);
margin: 0;
}
.modal-wrap .modal-header .close-modal {
background: transparent;
border: 0;
color: var(--base-4);
cursor: pointer;
font-size: 28px;
line-height: 1;
}
.modal-wrap .modal-header .close-modal:hover { opacity: 1; }
.modal-wrap .modal-content {
background: var(--base-1);
margin: 0;
padding: 15px 30px;
width: 100%;
z-index: 100000;
}
.modal-content h4 {
border-bottom: var(--border);
margin: 0 0 15px;
padding: 0 0 5px;
}
.modal-content input, .modal-content .form-help {
margin-left: 15px;
}
.modal-content label {
cursor: pointer;
font-size: 16px;
margin-left: 3px;
}
.modal-content .form-help {
color: var(--color-muted);
font-size: 14px;
margin: 5px 0 15px 33px;
}
.modal-content .form-help + h4 {
margin-top: 45px;
}
@media (max-width: 768px) {
#open-settings {
color: transparent;
}
#sidebar:hover #open-settings, #sidebar.expanded #open-settings {
color: var(--color-muted);
}
#open-settings:before {
content: '\2699';
font-weight: bold;
font-size: 25px;
color: var(--color-muted);
}
#sidebar:hover #open-settings:before, #sidebar.expanded #open-settings:before {
content: '';
}
}
</style>
<a href="#" id="open-settings">Settings</a>
<div class="modal-wrap" id="profiler-settings">
<div class="modal-container">
<div class="modal-header">
<h3>Configuration Settings</h3>
<button class="close-modal">&times;</button>
</div>
<div class="modal-content">
<h4>Theme</h4>
<input class="config-option" type="radio" name="theme" value="auto" id="settings-theme-auto">
<label for="settings-theme-auto">Auto</label>
<p class="form-help"><strong>Default theme</strong>. It switches between Light and Dark automatically to match the operating system theme.</p>
<input class="config-option" type="radio" name="theme" value="light" id="settings-theme-light">
<label for="settings-theme-light">Light</label>
<p class="form-help">Provides greatest readability, but requires a well-lit environment.</p>
<input class="config-option" type="radio" name="theme" value="dark" id="settings-theme-dark">
<label for="settings-theme-dark">Dark</label>
<p class="form-help">Reduces eye fatigue. Ideal for low light environments.</p>
<h4>Page Width</h4>
<input class="config-option" type="radio" name="width" value="normal" id="settings-width-normal">
<label for="settings-width-normal">Normal</label>
<p class="form-help">Fixed page width. Improves readability.</p>
<input class="config-option" type="radio" name="width" value="full" id="settings-width-full">
<label for="settings-width-full">Full-page</label>
<p class="form-help">Dynamic page width. As wide as the browser window.</p>
</div>
</div>
</div>
<script>
(function() {
const configOptions = document.querySelectorAll('.config-option');
const allSettingValues = ['theme-auto', 'theme-ligh', 'theme-dark', 'width-normal', 'width-full'];
[...configOptions].forEach(option => {
option.addEventListener('change', function (event) {
const optionName = option.name;
const optionValue = option.value;
const settingName = 'symfony/profiler/' + optionName;
const settingValue = optionName + '-' + optionValue;
localStorage.setItem(settingName, settingValue);
document.body.classList.forEach((cssClass) => {
if (cssClass.startsWith(optionName)) {
document.body.classList.remove(cssClass);
}
});
if ('theme-auto' === settingValue) {
document.body.classList.add((matchMedia('(prefers-color-scheme: dark)').matches ? 'theme-dark' : 'theme-light'));
} else {
document.body.classList.add(settingValue);
}
});
});
const openModalButton = document.getElementById('open-settings');
const modalWindow = document.getElementById('profiler-settings');
const closeModalButton = document.getElementsByClassName('close-modal')[0];
const modalWrapper = document.getElementsByClassName('modal-wrap')[0]
openModalButton.addEventListener('click', function(event) {
document.getElementById('settings-' + (localStorage.getItem('symfony/profiler/theme') || 'theme-auto')).checked = 'checked';
document.getElementById('settings-' + (localStorage.getItem('symfony/profiler/width') || 'width-normal')).checked = 'checked';
modalWindow.classList.toggle('visible');
event.preventDefault();
});
closeModalButton.addEventListener('click', function() {
modalWindow.classList.remove('visible');
});
modalWrapper.addEventListener('click', function(event) {
if (event.target == event.currentTarget) {
modalWindow.classList.remove('visible');
}
});
})();
</script>

View File

@@ -14,8 +14,10 @@
z-index: 99999;
}
.sf-minitoolbar a {
display: block;
.sf-minitoolbar button {
background-color: transparent;
padding: 0;
border: none;
}
.sf-minitoolbar svg,
.sf-minitoolbar img {
@@ -71,6 +73,10 @@
display: inline-block;
}
.sf-toolbarreset .sf-cancel-button {
color: #444;
}
.sf-toolbarreset .hide-button {
background: #444;
display: block;
@@ -81,10 +87,13 @@
height: 36px;
cursor: pointer;
text-align: center;
border: none;
margin: 0;
padding: 0;
}
.sf-toolbarreset .hide-button svg {
max-height: 18px;
margin-top: 10px;
margin-top: 1px;
}
.sf-toolbar-block {
@@ -94,11 +103,14 @@
height: 36px;
margin-right: 0;
white-space: nowrap;
max-width: 15%;
}
.sf-toolbar-block > a,
.sf-toolbar-block > a:hover {
display: block;
text-decoration: none;
background-color: transparent;
color: inherit;
}
.sf-toolbar-block span {
@@ -236,6 +248,7 @@ div.sf-toolbar .sf-toolbar-block a:hover {
padding: 0 10px;
}
.sf-toolbar-block-request .sf-toolbar-info-piece a {
background-color: transparent;
text-decoration: none;
}
.sf-toolbar-block-request .sf-toolbar-info-piece a:hover {
@@ -279,6 +292,8 @@ div.sf-toolbar .sf-toolbar-block a:hover {
display: block;
height: 36px;
padding: 0 7px;
overflow: hidden;
text-overflow: ellipsis;
}
.sf-toolbar-block-request .sf-toolbar-icon {
padding-left: 0;
@@ -407,34 +422,6 @@ div.sf-toolbar .sf-toolbar-block a:hover {
display: none;
}
/* Override the setting when the toolbar is on the top */
{% if position == 'top' %}
.sf-minitoolbar {
border-bottom-left-radius: 4px;
border-top-left-radius: 0;
bottom: auto;
right: 0;
top: 0;
}
.sf-toolbarreset {
bottom: auto;
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2);
top: 0;
}
.sf-toolbar-block .sf-toolbar-info {
bottom: auto;
top: 36px;
}
{% endif %}
{% if not floatable %}
.sf-toolbarreset {
position: static;
}
{% endif %}
/* Responsive Design */
.sf-toolbar-icon .sf-toolbar-label,
.sf-toolbar-icon .sf-toolbar-value {
@@ -523,7 +510,7 @@ div.sf-toolbar .sf-toolbar-block a:hover {
@media (min-width: 1024px) {
.sf-toolbar-block .sf-toolbar-info-piece-additional,
.sf-toolbar-block .sf-toolbar-info-piece-additional-detail {
display: inline-block;
display: inline;
}
.sf-toolbar-block .sf-toolbar-info-piece-additional:empty,
@@ -562,6 +549,11 @@ div.sf-toolbar .sf-toolbar-block a:hover {
margin-right: 10px;
}
.sf-full-stack {
left: 0px;
font-size: 12px;
}
/***** Media query print: Do not print the Toolbar. *****/
@media print {
.sf-toolbar {

View File

@@ -1,8 +1,8 @@
<!-- START of Symfony Web Debug Toolbar -->
<div id="sfMiniToolbar-{{ token }}" class="sf-minitoolbar" data-no-turbolink>
<a href="#" title="Show Symfony toolbar" tabindex="-1" id="sfToolbarMiniToggler-{{ token }}" accesskey="D">
<button type="button" title="Show Symfony toolbar" id="sfToolbarMiniToggler-{{ token }}" accesskey="D" aria-expanded="false" aria-controls="sfToolbarMainContent-{{ token }}">
{{ include('@WebProfiler/Icon/symfony.svg') }}
</a>
</button>
</div>
<div id="sfToolbarClearer-{{ token }}" class="sf-toolbar-clearer"></div>
@@ -10,9 +10,9 @@
{% for name, template in templates %}
{% if block('toolbar', template) is defined %}
{% with {
collector: profile.getcollector(name),
collector: profile ? profile.getcollector(name) : null,
profiler_url: profiler_url,
token: profile.token,
token: token ?? (profile ? profile.token : null),
name: name,
profiler_markup_version: profiler_markup_version,
csp_script_nonce: csp_script_nonce,
@@ -22,9 +22,25 @@
{% endwith %}
{% endif %}
{% endfor %}
{% if full_stack %}
<div class="sf-full-stack sf-toolbar-block sf-toolbar-block-full-stack sf-toolbar-status-red sf-toolbar-block-right">
<div class="sf-toolbar-icon">
<span class="sf-toolbar-value">Using symfony/symfony is NOT supported</span>
</div>
<div class="sf-toolbar-info sf-toolbar-status-red">
<p>This project is using Symfony via the "symfony/symfony" package.</p>
<p>This is NOT supported anymore since Symfony 4.0.</p>
<p>Even if it seems to work well, it has some important limitations with no workarounds.</p>
<p>Using this package also makes your project slower.</p>
<a class="hide-button" id="sfToolbarHideButton-{{ token }}" title="Close Toolbar" tabindex="-1" accesskey="D">
<strong>Please, stop using this package and replace it with individual packages instead.</strong>
</div>
<div></div>
</div>
{% endif %}
<button class="hide-button" type="button" id="sfToolbarHideButton-{{ token }}" title="Close Toolbar" accesskey="D" aria-expanded="true" aria-controls="sfToolbarMainContent-{{ token }}">
{{ include('@WebProfiler/Icon/close.svg') }}
</a>
</button>
</div>
<!-- END of Symfony Web Debug Toolbar -->

View File

@@ -1,127 +1,21 @@
<div id="sfwdt{{ token }}" class="sf-toolbar sf-display-none" role="region" aria-label="Symfony Web Debug Toolbar"></div>
<div id="sfwdt{{ token }}" class="sf-toolbar sf-display-none" role="region" aria-label="Symfony Web Debug Toolbar">
{% include('@WebProfiler/Profiler/toolbar.html.twig') with {
templates: {
'request': '@WebProfiler/Profiler/cancel.html.twig'
},
profile: null,
profiler_url: url('_profiler', {token: token}),
profiler_markup_version: 2,
} %}
</div>
{{ include('@WebProfiler/Profiler/base_js.html.twig') }}
<style{% if csp_style_nonce %} nonce="{{ csp_style_nonce }}"{% endif %}>
{{ include('@WebProfiler/Profiler/toolbar.css.twig', { 'position': position, 'floatable': true }) }}
{{ include('@WebProfiler/Profiler/toolbar.css.twig') }}
</style>
<script{% if csp_script_nonce %} nonce="{{ csp_script_nonce }}"{% endif %}>/*<![CDATA[*/
(function () {
{% if 'top' == position %}
var sfwdt = document.getElementById('sfwdt{{ token }}');
document.body.insertBefore(
document.body.removeChild(sfwdt),
document.body.firstChild
);
{% endif %}
Sfjs.load(
'sfwdt{{ token }}',
'{{ path("_wdt", { "token": token })|escape('js') }}',
function(xhr, el) {
/* Evaluate embedded scripts inside the toolbar */
var i, scripts = [].slice.call(el.querySelectorAll('script'));
for (i = 0; i < scripts.length; ++i) {
eval(scripts[i].firstChild.nodeValue);
}
el.style.display = -1 !== xhr.responseText.indexOf('sf-toolbarreset') ? 'block' : 'none';
if (el.style.display == 'none') {
return;
}
if (Sfjs.getPreference('toolbar/displayState') == 'none') {
document.getElementById('sfToolbarMainContent-{{ token }}').style.display = 'none';
document.getElementById('sfToolbarClearer-{{ token }}').style.display = 'none';
document.getElementById('sfMiniToolbar-{{ token }}').style.display = 'block';
} else {
document.getElementById('sfToolbarMainContent-{{ token }}').style.display = 'block';
document.getElementById('sfToolbarClearer-{{ token }}').style.display = 'block';
document.getElementById('sfMiniToolbar-{{ token }}').style.display = 'none';
}
/* Handle toolbar-info position */
var toolbarBlocks = [].slice.call(el.querySelectorAll('.sf-toolbar-block'));
for (i = 0; i < toolbarBlocks.length; ++i) {
toolbarBlocks[i].onmouseover = function () {
var toolbarInfo = this.querySelectorAll('.sf-toolbar-info')[0];
var pageWidth = document.body.clientWidth;
var elementWidth = toolbarInfo.offsetWidth;
var leftValue = (elementWidth + this.offsetLeft) - pageWidth;
var rightValue = (elementWidth + (pageWidth - this.offsetLeft)) - pageWidth;
/* Reset right and left value, useful on window resize */
toolbarInfo.style.right = '';
toolbarInfo.style.left = '';
if (elementWidth > pageWidth) {
toolbarInfo.style.left = 0;
}
else if (leftValue > 0 && rightValue > 0) {
toolbarInfo.style.right = (rightValue * -1) + 'px';
} else if (leftValue < 0) {
toolbarInfo.style.left = 0;
} else {
toolbarInfo.style.right = '0px';
}
};
}
Sfjs.addEventListener(document.getElementById('sfToolbarHideButton-{{ token }}'), 'click', function (event) {
event.preventDefault();
var p = this.parentNode;
p.style.display = 'none';
(p.previousElementSibling || p.previousSibling).style.display = 'none';
document.getElementById('sfMiniToolbar-{{ token }}').style.display = 'block';
Sfjs.setPreference('toolbar/displayState', 'none');
});
Sfjs.addEventListener(document.getElementById('sfToolbarMiniToggler-{{ token }}'), 'click', function (event) {
event.preventDefault();
var elem = this.parentNode;
if (elem.style.display == 'none') {
document.getElementById('sfToolbarMainContent-{{ token }}').style.display = 'none';
document.getElementById('sfToolbarClearer-{{ token }}').style.display = 'none';
elem.style.display = 'block';
} else {
document.getElementById('sfToolbarMainContent-{{ token }}').style.display = 'block';
document.getElementById('sfToolbarClearer-{{ token }}').style.display = 'block';
elem.style.display = 'none'
}
Sfjs.setPreference('toolbar/displayState', 'block');
});
Sfjs.renderAjaxRequests();
Sfjs.addEventListener(document.querySelector('.sf-toolbar-block-ajax > .sf-toolbar-icon'), 'click', function (event) {
event.preventDefault();
Sfjs.toggleClass(this.parentNode, 'hover');
});
var dumpInfo = document.querySelector('.sf-toolbar-block-dump .sf-toolbar-info');
if (null !== dumpInfo) {
Sfjs.addEventListener(dumpInfo, 'sfbeforedumpcollapse', function () {
dumpInfo.style.minHeight = dumpInfo.getBoundingClientRect().height+'px';
});
Sfjs.addEventListener(dumpInfo, 'mouseleave', function () {
dumpInfo.style.minHeight = '';
});
}
},
function(xhr) {
if (xhr.status !== 0) {
var sfwdt = document.getElementById('sfwdt{{ token }}');
sfwdt.innerHTML = '\
<div class="sf-toolbarreset">\
<div class="sf-toolbar-icon"><svg width="26" height="28" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 26 28"><path fill="#FFF" d="M13 0a13 13 0 1 0 0 26 13 13 0 1 0 0-26zm7 7.5c-.6 0-1-.3-1-.9 0-.2 0-.4.2-.6l.2-.4c0-.3-.5-.4-.7-.4-2 .1-2.5 2.7-2.9 4.8l-.2 1.1c1.1.2 1.9 0 2.4-.3.6-.4-.2-.8-.1-1.3.1-.3.5-.5.8-.6.5 0 .8.5.8 1 0 .8-1.1 2-3.3 1.9l-.7-.1-.5 2.4c-.4 1.7-.9 4.1-2.6 6.2-1.5 1.8-3.1 2.1-3.8 2.1-1.3 0-2.1-.6-2.2-1.6 0-.9.8-1.4 1.3-1.4.7 0 1.2.5 1.2 1.1 0 .5-.2.6-.4.7-.1.1-.3.2-.3.4 0 .1.1.3.4.3.5 0 .9-.3 1.2-.5 1.3-1 1.7-2.9 2.4-6.2l.1-.8.8-3.5c-.9-.7-1.4-1.5-2.6-1.8-.8-.2-1.3 0-1.7.4-.4.5-.2 1.2.2 1.6l.7.7c.8.9 1.3 1.7 1.1 2.7-.3 1.6-2.1 2.8-4.3 2.1-1.9-.6-2.2-1.9-2-2.7.2-.6.7-.8 1.2-.6.5.2.7.8.6 1.3l-.1.3c-.2.1-.3.3-.3.4-.1.4.4.7.8.8.8.3 1.7-.2 1.9-.9.2-.6-.2-1.1-.4-1.2l-.8-.9c-.4-.4-1.2-1.5-.8-2.8.2-.5.5-1 .9-1.4 1-.7 2-.8 3-.6 1.3.4 1.9 1.2 2.8 1.9.5-1.3 1.1-2.6 2-3.8a5 5 0 0 1 3.3-1.8c1.4.2 2.4.8 2.4 1.7 0 .4-.2 1.2-1 1.2z"/></svg></div>\
An error occurred while loading the web debug toolbar. <a href="{{ path("_profiler", { "token": token }) }}">Open the web profiler.</a>\
</div>\
';
sfwdt.setAttribute('class', 'sf-toolbar sf-error-toolbar');
}
},
{ maxTries: 5 }
);
Sfjs.loadToolbar('{{ token }}');
})();
/*]]>*/</script>

View File

@@ -1,4 +1,4 @@
{% extends '@Twig/layout.html.twig' %}
{% extends '@WebProfiler/Profiler/base.html.twig' %}
{% block title 'Redirection Intercepted' %}

View File

@@ -5,13 +5,6 @@
<span class="value">{{ request.route ?: '(none)' }}</span>
<span class="label">Matched route</span>
</div>
{% if request.route %}
<div class="metric">
<span class="value">{{ traces|length }}</span>
<span class="label">Tested routes before match</span>
</div>
{% endif %}
</div>
{% if request.route %}

View File

@@ -0,0 +1 @@
<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M1408 960V832q0-26-19-45t-45-19H448q-26 0-45 19t-19 45v128q0 26 19 45t45 19h896q26 0 45-19t19-45zm256-544v960q0 119-84.5 203.5T1376 1664H416q-119 0-203.5-84.5T128 1376V416q0-119 84.5-203.5T416 128h960q119 0 203.5 84.5T1664 416z"/></svg>

After

Width:  |  Height:  |  Size: 337 B

View File

@@ -0,0 +1 @@
<svg width="1792" height="1792" viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M1408 960V832q0-26-19-45t-45-19h-320V448q0-26-19-45t-45-19H832q-26 0-45 19t-19 45v320H448q-26 0-45 19t-19 45v128q0 26 19 45t45 19h320v320q0 26 19 45t45 19h128q26 0 45-19t19-45v-320h320q26 0 45-19t19-45zm256-544v960q0 119-84.5 203.5T1376 1664H416q-119 0-203.5-84.5T128 1376V416q0-119 84.5-203.5T416 128h960q119 0 203.5 84.5T1664 416z"/></svg>

After

Width:  |  Height:  |  Size: 442 B