N°6934 - Symfony 6.4 - upgrade Symfony bundles to 6.4 (#580)

* Update Symfony lib to version ~6.4.0
* Update code missing return type
* Add an iTop general configuration entry to store application secret (Symfony mandatory parameter)
* Use dependency injection in ExceptionListener & UserProvider classes
This commit is contained in:
bdalsass
2023-12-05 13:56:56 +01:00
committed by GitHub
parent 863ab4560c
commit 27ce51ab07
1392 changed files with 44869 additions and 27799 deletions

View File

@@ -2,7 +2,7 @@
{% block toolbar %}
{% set icon %}
{{ include('@WebProfiler/Icon/ajax.svg') }}
{{ source('@WebProfiler/Icon/ajax.svg') }}
<span class="sf-toolbar-value sf-toolbar-ajax-request-counter">0</span>
{% endset %}

View File

@@ -3,7 +3,7 @@
{% block toolbar %}
{% if collector.totals.calls > 0 %}
{% set icon %}
{{ include('@WebProfiler/Icon/cache.svg') }}
{{ source('@WebProfiler/Icon/cache.svg') }}
<span class="sf-toolbar-value">{{ collector.totals.calls }}</span>
<span class="sf-toolbar-info-piece-additional-detail">
<span class="sf-toolbar-label">in</span>
@@ -36,92 +36,47 @@
{% block menu %}
<span class="label {{ collector.totals.calls == 0 ? 'disabled' }}">
<span class="icon">
{{ include('@WebProfiler/Icon/cache.svg') }}
<span class="icon">
{{ source('@WebProfiler/Icon/cache.svg') }}
</span>
<strong>Cache</strong>
</span>
<strong>Cache</strong>
</span>
{% endblock %}
{% block panel %}
<h2>Cache</h2>
{% if collector.totals.calls == 0 %}
<div class="empty">
<div class="empty empty-panel">
<p>No cache calls were made.</p>
</div>
{% else %}
<div class="metrics">
<div class="metric">
<span class="value">{{ collector.totals.calls }}</span>
<span class="label">Total calls</span>
</div>
<div class="metric">
<span class="value">{{ '%0.2f'|format(collector.totals.time * 1000) }} <span class="unit">ms</span></span>
<span class="label">Total time</span>
</div>
<div class="metric-divider"></div>
<div class="metric">
<span class="value">{{ collector.totals.reads }}</span>
<span class="label">Total reads</span>
</div>
<div class="metric">
<span class="value">{{ collector.totals.writes }}</span>
<span class="label">Total writes</span>
</div>
<div class="metric">
<span class="value">{{ collector.totals.deletes }}</span>
<span class="label">Total deletes</span>
</div>
<div class="metric-divider"></div>
<div class="metric">
<span class="value">{{ collector.totals.hits }}</span>
<span class="label">Total hits</span>
</div>
<div class="metric">
<span class="value">{{ collector.totals.misses }}</span>
<span class="label">Total misses</span>
</div>
<div class="metric">
<span class="value">
{{ collector.totals.hit_read_ratio ?? 0 }} <span class="unit">%</span>
</span>
<span class="label">Hits/reads</span>
</div>
</div>
{{ _self.render_metrics(collector.totals, true) }}
<h2>Pools</h2>
<div class="sf-tabs">
{% for name, calls in collector.calls %}
{# the empty merge is needed to turn the iterator into an array #}
{% set cache_pools_with_calls = collector.calls|filter(calls => calls|length > 0)|merge([]) %}
{% for name, calls in cache_pools_with_calls %}
<div class="tab {{ calls|length == 0 ? 'disabled' }}">
<h3 class="tab-title">{{ name }} <span class="badge">{{ collector.statistics[name].calls }}</span></h3>
<div class="tab-content">
<h4>Adapter</h4>
<div class="card">
{% if collector.adapters[name] is defined %}
<code>{{ collector.adapters[name] }}</code>
{% else %}
<span class="text-muted">Unable to get the adapter class.</span>
{% endif %}
</div>
{% if calls|length == 0 %}
<div class="empty">
<p>No calls were made for {{ name }} pool.</p>
</div>
{% else %}
<h4>Metrics</h4>
<div class="metrics">
{% for key, value in collector.statistics[name] %}
<div class="metric">
<span class="value">
{% if key == 'time' %}
{{ '%0.2f'|format(1000 * value) }} <span class="unit">ms</span>
{% elseif key == 'hit_read_ratio' %}
{{ value ?? 0 }} <span class="unit">%</span>
{% else %}
{{ value }}
{% endif %}
</span>
<span class="label">{{ key == 'hit_read_ratio' ? 'Hits/reads' : key|capitalize }}</span>
</div>
{% if key == 'time' or key == 'deletes' %}
<div class="metric-divider"></div>
{% endif %}
{% endfor %}
</div>
{{ _self.render_metrics(collector.statistics[name]) }}
<h4>Calls</h4>
<table>
@@ -147,7 +102,77 @@
{% endif %}
</div>
</div>
{% if loop.last %}
<div class="tab">
<h3 class="tab-title">Pools without calls <span class="badge">{{ collector.calls|filter(calls => 0 == calls|length)|length }}</span></h3>
<div class="tab-content">
<table>
<thead>
<tr>
<th>Cache pools that received no calls</th>
</tr>
</thead>
<tbody>
{% for cache_pool in collector.calls|filter(calls => 0 == calls|length)|keys|sort %}
<tr><td>{{ cache_pool }}</td></tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
{% endif %}
{% endfor %}
</div>
{% endif %}
{% endblock %}
{% macro render_metrics(pool, is_total = false) %}
<div class="metrics">
<div class="metric">
<span class="value">{{ pool.calls }}</span>
<span class="label">{{ is_total ? 'Total calls' : 'Calls' }}</span>
</div>
<div class="metric">
<span class="value">{{ '%0.2f'|format(pool.time * 1000) }} <span class="unit">ms</span></span>
<span class="label">{{ is_total ? 'Total time' : 'Time' }}</span>
</div>
<div class="metric-divider"></div>
<div class="metric-group">
<div class="metric">
<span class="value">{{ pool.reads }}</span>
<span class="label">{{ is_total ? 'Total reads' : 'Reads' }}</span>
</div>
<div class="metric">
<span class="value">{{ pool.writes }}</span>
<span class="label">{{ is_total ? 'Total writes' : 'Writes' }}</span>
</div>
<div class="metric">
<span class="value">{{ pool.deletes }}</span>
<span class="label">{{ is_total ? 'Total deletes' : 'Deletes' }}</span>
</div>
</div>
<div class="metric-divider"></div>
<div class="metric-group">
<div class="metric">
<span class="value">{{ pool.hits }}</span>
<span class="label">{{ is_total ? 'Total hits' : 'Hits' }}</span>
</div>
<div class="metric">
<span class="value">{{ pool.misses }}</span>
<span class="label">{{ is_total ? 'Total misses' : 'Misses' }}</span>
</div>
<div class="metric">
<span class="value">
{{ pool.hit_read_ratio ?? 0 }} <span class="unit">%</span>
</span>
<span class="label">Hits/reads</span>
</div>
</div>
</div>
{% endmacro %}

View File

@@ -0,0 +1,249 @@
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
{% block menu %}
<span class="label">
<span class="icon">{{ source('@WebProfiler/Icon/command.svg') }}</span>
<strong>Console Command</strong>
</span>
{% endblock %}
{% block panel %}
<h2>
{% set command = collector.command %}
<a href="{{ command.file|file_link(command.line) }}">
{% if command.executor is defined %}
{{ command.executor|abbr_method }}
{% else %}
{{ command.class|abbr_class }}
{% endif %}
</a>
</h2>
<div class="sf-tabs">
<div class="tab">
<h3 class="tab-title">Command</h3>
<div class="tab-content">
<div class="metrics">
<div class="metric">
<span class="value">{{ collector.duration }}</span>
<span class="label">Duration</span>
</div>
<div class="metric">
<span class="value">{{ collector.maxMemoryUsage }}</span>
<span class="label">Peak Memory Usage</span>
</div>
<div class="metric">
<span class="value">{{ collector.verbosityLevel }}</span>
<span class="label">Verbosity Level</span>
</div>
</div>
<div class="metrics">
<div class="metric">
<span class="value">{{ source('@WebProfiler/Icon/' ~ (collector.signalable is not empty ? 'yes' : 'no') ~ '.svg') }}</span>
<span class="label">Signalable</span>
</div>
<div class="metric">
<span class="value">{{ source('@WebProfiler/Icon/' ~ (collector.interactive ? 'yes' : 'no') ~ '.svg') }}</span>
<span class="label">Interactive</span>
</div>
<div class="metric">
<span class="value">{{ source('@WebProfiler/Icon/' ~ (collector.validateInput ? 'yes' : 'no') ~ '.svg') }}</span>
<span class="label">Validate Input</span>
</div>
<div class="metric">
<span class="value">{{ source('@WebProfiler/Icon/' ~ (collector.enabled ? 'yes' : 'no') ~ '.svg') }}</span>
<span class="label">Enabled</span>
</div>
<div class="metric">
<span class="value">{{ source('@WebProfiler/Icon/' ~ (collector.visible ? 'yes' : 'no') ~ '.svg') }}</span>
<span class="label">Visible</span>
</div>
</div>
<h3>Arguments</h3>
{% if collector.arguments is empty %}
<div class="empty">
<p>No arguments were set</p>
</div>
{% else %}
{{ include('@WebProfiler/Profiler/table.html.twig', { data: collector.arguments, labels: ['Argument', 'Value'], maxDepth: 2 }, with_context=false) }}
{% endif %}
<h3>Options</h3>
{% if collector.options is empty %}
<div class="empty">
<p>No options were set</p>
</div>
{% else %}
{{ include('@WebProfiler/Profiler/table.html.twig', { data: collector.options, labels: ['Option', 'Value'], maxDepth: 2 }, with_context=false) }}
{% endif %}
{% if collector.interactive %}
<h3>Interactive Inputs</h3>
<p class="help">
The values which have been set interactively.
</p>
{% if collector.interactiveInputs is empty %}
<div class="empty">
<p>No inputs were set</p>
</div>
{% else %}
{{ include('@WebProfiler/Profiler/table.html.twig', { data: collector.interactiveInputs, labels: ['Input', 'Value'], maxDepth: 2 }, with_context=false) }}
{% endif %}
{% endif %}
<h3>Application inputs</h3>
{% if collector.applicationInputs is empty %}
<div class="empty">
<p>No application inputs are set</p>
</div>
{% else %}
{{ include('@WebProfiler/Profiler/table.html.twig', { data: collector.applicationInputs, labels: ['Input', 'Value'], maxDepth: 2 }, with_context=false) }}
{% endif %}
</div>
</div>
<div class="tab">
<h3 class="tab-title">Input / Output</h3>
<div class="tab-content">
<table>
<tr>
<td class="font-normal">Input</td>
<td class="font-normal">{{ profiler_dump(collector.input) }}</td>
</tr>
<tr>
<td class="font-normal">Output</td>
<td class="font-normal">{{ profiler_dump(collector.output) }}</td>
</tr>
</table>
</div>
</div>
<div class="tab">
<h3 class="tab-title">Helper Set</h3>
<div class="tab-content">
{% if collector.helperSet is empty %}
<div class="empty">
<p>No helpers</p>
</div>
{% else %}
<table class="{{ class|default('') }}">
<thead>
<tr>
<th scope="col">Helpers</th>
</tr>
</thead>
<tbody>
{% for helper in collector.helperSet|sort %}
<tr>
<td>{{ profiler_dump(helper) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
</div>
</div>
<div class="tab">
{% set request_collector = profile.collectors.request %}
<h3 class="tab-title">Server Parameters</h3>
<div class="tab-content">
<h3>Server Parameters</h3>
<h4>Defined in .env</h4>
{{ include('@WebProfiler/Profiler/bag.html.twig', { bag: request_collector.dotenvvars }, with_context = false) }}
<h4>Defined as regular env variables</h4>
{% set requestserver = [] %}
{% for key, value in request_collector.requestserver|filter((_, key) => key not in request_collector.dotenvvars.keys) %}
{% set requestserver = requestserver|merge({(key): value}) %}
{% endfor %}
{{ include('@WebProfiler/Profiler/table.html.twig', { data: requestserver }, with_context = false) }}
</div>
</div>
{% if collector.signalable is not empty %}
<div class="tab">
<h3 class="tab-title">Signals</h3>
<div class="tab-content">
<h3>Subscribed signals</h3>
{{ collector.signalable|join(', ') }}
<h3>Handled signals</h3>
{% if collector.handledSignals is empty %}
<div class="empty">
<p>No signals handled</p>
</div>
{% else %}
<table>
<thead>
<tr>
<th>Signal</th>
<th>Times handled</th>
<th>Total execution time</th>
<th>Memory peak</th>
</tr>
</thead>
<tbody>
{% for signal, data in collector.handledSignals %}
<tr>
<td>{{ signal }}</td>
<td>{{ data.handled }}</td>
<td>{{ data.duration }} ms</td>
<td>{{ data.memory }} MiB</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
</div>
</div>
{% endif %}
{% if profile.parent %}
<div class="tab">
<h3 class="tab-title">Parent Command</h3>
<div class="tab-content">
<h3>
<a href="{{ path('_profiler', { token: profile.parent.token }) }}">Return to parent command</a>
<small>(token = {{ profile.parent.token }})</small>
</h3>
{{ profile.parent.url }}
</div>
</div>
{% endif %}
{% if profile.children|length %}
<div class="tab">
<h3 class="tab-title">Sub Commands <span class="badge">{{ profile.children|length }}</span></h3>
<div class="tab-content">
{% for child in profile.children %}
<h3>
{{ child.url }}
<small>(token = <a href="{{ path('_profiler', { token: child.token }) }}">{{ child.token }}</a>)</small>
</h3>
{% endfor %}
</div>
</div>
{% endif %}
</div>
{% endblock %}

View File

@@ -1,5 +1,51 @@
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
{% block head %}
{{ parent() }}
<style>
.config-symfony-version-lts {
border: 0;
color: var(--color-muted);
font-size: 21px;
line-height: 33px;
}
.config-symfony-version-lts[title] {
text-decoration: none;
}
.config-symfony-version-status-badge {
background-color: var(--badge-background);
border-radius: 4px;
color: var(--badge-color);
display: inline-block;
font-size: 15px;
font-weight: bold;
margin: 10px 0 5px;
padding: 3px 7px;
white-space: nowrap;
}
.config-symfony-version-status-badge.status-success {
background-color: var(--badge-success-background);
color: var(--badge-success-color);
}
.config-symfony-version-status-badge.status-warning {
background-color: var(--badge-warning-background);
color: var(--badge-warning-color);
}
.config-symfony-version-status-badge.status-error {
background-color: var(--badge-danger-background);
color: var(--badge-danger-color);
}
.config-symfony-version-roadmap-link {
display: inline-block;
margin: 10px 5px 5px;
}
.config-symfony-eol {
margin-top: 5px;
}
</style>
{% endblock %}
{% block toolbar %}
{% if 'unknown' == collector.symfonyState %}
{% set block_status = '' %}
@@ -20,7 +66,7 @@
{% set icon %}
<span class="sf-toolbar-label">
{{ include('@WebProfiler/Icon/symfony.svg') }}
{{ source('@WebProfiler/Icon/symfony.svg') }}
</span>
<span class="sf-toolbar-value">{{ collector.symfonyState is defined ? collector.symfonyversion : 'n/a' }}</span>
{% endset %}
@@ -64,7 +110,13 @@
<div class="sf-toolbar-info-piece sf-toolbar-info-php-ext">
<b>PHP Extensions</b>
<span class="sf-toolbar-status sf-toolbar-status-{{ collector.hasxdebug ? 'green' : 'gray' }}">xdebug {{ collector.hasxdebug ? '✓' : '✗' }}</span>
{% if collector.hasXdebugInfo %}
<a href="{{ path('_profiler_xdebug') }}" title="View xdebug_info()">
{% endif %}
<span class="sf-toolbar-status sf-toolbar-status-{{ collector.hasXdebug ? 'green' : 'gray' }}">Xdebug {{ collector.hasXdebug ? '✓' : '✗' }}</span>
{% if collector.hasXdebugInfo %}
</a>
{% endif %}
<span class="sf-toolbar-status sf-toolbar-status-{{ collector.hasapcu ? 'green' : 'gray' }}">APCu {{ collector.hasapcu ? '✓' : '✗' }}</span>
<span class="sf-toolbar-status sf-toolbar-status-{{ collector.haszendopcache ? 'green' : 'red' }}">OPcache {{ collector.haszendopcache ? '✓' : '✗' }}</span>
</div>
@@ -102,7 +154,7 @@
{% block menu %}
<span class="label label-status-{{ collector.symfonyState == 'eol' ? 'red' : collector.symfonyState in ['eom', 'dev'] ? 'yellow' }}">
<span class="icon">{{ include('@WebProfiler/Icon/config.svg') }}</span>
<span class="icon">{{ source('@WebProfiler/Icon/config.svg') }}</span>
<strong>Configuration</strong>
</span>
{% endblock %}
@@ -112,7 +164,13 @@
<div class="metrics">
<div class="metric">
<span class="value">{{ collector.symfonyversion }}</span>
<span class="value">
{{ collector.symfonyversion }}
{% if collector.symfonylts %}
<abbr class="config-symfony-version-lts" title="This is a Long-Term Support version">(LTS)</abbr>
{% endif %}
</span>
<span class="label">Symfony version</span>
</div>
@@ -131,33 +189,39 @@
{% endif %}
</div>
{% set symfony_status = { dev: 'Unstable Version', stable: 'Stable Version', eom: 'Maintenance Ended', eol: 'Version Expired' } %}
{% set symfony_status = { dev: 'In Development', stable: 'Maintained', eom: 'Security Fixes Only', eol: 'Unmaintained' } %}
{% set symfony_status_class = { dev: 'warning', stable: 'success', eom: 'warning', eol: 'error' } %}
<table>
<thead class="small">
<tr>
<th>Symfony Status</th>
<th>Bugs {{ collector.symfonystate in ['eom', 'eol'] ? 'were' : 'are' }} fixed until</th>
<th>Security issues {{ collector.symfonystate == 'eol' ? 'were' : 'are' }} fixed until</th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td class="font-normal">
<span class="label status-{{ symfony_status_class[collector.symfonystate] }}">{{ symfony_status[collector.symfonystate]|upper }}</span>
{% if collector.symfonylts %}
&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>
<div class="metrics">
<div class="metric-group">
<div class="metric">
<span class="value">
<span class="config-symfony-version-status-badge status-{{ symfony_status_class[collector.symfonystate] }}">{{ symfony_status[collector.symfonystate]|upper }}</span>
</span>
<span class="label">Your Symfony version status</span>
</div>
{% if collector.symfonylts %}
<div class="metric">
<span class="value config-symfony-eol">
{{ collector.symfonyeom }}
</span>
<span class="label">Bug fixes {{ collector.symfonystate in ['eom', 'eol'] ? 'ended on' : 'until' }}</span>
</div>
{% endif %}
<div class="metric">
<span class="value config-symfony-eol">
{{ collector.symfonyeol }}
</span>
<span class="label">
{{ collector.symfonylts ? 'Security fixes' : 'Bug fixes and security fixes' }}
{{ 'eol' == collector.symfonystate ? 'ended on' : 'until' }}</span>
</div>
</div>
</div>
<a class="config-symfony-version-roadmap-link" href="https://symfony.com/releases/{{ collector.symfonyminorversion }}">View Symfony {{ collector.symfonyversion }} release details</a>
<h2>PHP Configuration</h2>
@@ -184,19 +248,21 @@
</div>
<div class="metrics">
<div class="metric">
<span class="value">{{ include('@WebProfiler/Icon/' ~ (collector.haszendopcache ? 'yes' : 'no') ~ '.svg') }}</span>
<span class="label">OPcache</span>
</div>
<div class="metric-group">
<div class="metric">
<span class="value value-is-icon {{ not collector.haszendopcache ? 'value-shows-no-color' }}">{{ source('@WebProfiler/Icon/' ~ (collector.haszendopcache ? 'yes' : 'no') ~ '.svg') }}</span>
<span class="label">OPcache</span>
</div>
<div class="metric">
<span class="value">{{ include('@WebProfiler/Icon/' ~ (collector.hasapcu ? 'yes' : 'no-gray') ~ '.svg') }}</span>
<span class="label">APCu</span>
</div>
<div class="metric">
<span class="value value-is-icon {{ not collector.hasapcu ? 'value-shows-no-color' }}">{{ source('@WebProfiler/Icon/' ~ (collector.hasapcu ? 'yes' : 'no') ~ '.svg') }}</span>
<span class="label">APCu</span>
</div>
<div class="metric">
<span class="value">{{ include('@WebProfiler/Icon/' ~ (collector.hasxdebug ? 'yes' : 'no-gray') ~ '.svg') }}</span>
<span class="label">Xdebug</span>
<div class="metric">
<span class="value value-is-icon {{ not collector.hasxdebug ? 'value-shows-no-color' }}">{{ source('@WebProfiler/Icon/' ~ (collector.hasxdebug ? 'yes' : 'no') ~ '.svg') }}</span>
<span class="label">Xdebug</span>
</div>
</div>
</div>

View File

@@ -1,85 +1,92 @@
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
{% import _self as helper %}
{% block menu %}
<span class="label">
<span class="icon">{{ include('@WebProfiler/Icon/event.svg') }}</span>
<span class="icon">{{ source('@WebProfiler/Icon/event.svg') }}</span>
<strong>Events</strong>
</span>
{% endblock %}
{% block panel %}
<h2>Event Dispatcher</h2>
<h2>Dispatched Events</h2>
{% if collector.calledlisteners is empty %}
<div class="empty">
<p>No events have been recorded. Check that debugging is enabled in the kernel.</p>
</div>
{% else %}
<div class="sf-tabs">
<div class="sf-tabs">
{% for dispatcherName, dispatcherData in collector.data %}
<div class="tab">
<h3 class="tab-title">Called Listeners <span class="badge">{{ collector.calledlisteners|length }}</span></h3>
<h3 class="tab-title">{{ dispatcherName }}</h3>
<div class="tab-content">
{{ helper.render_table(collector.calledlisteners) }}
</div>
</div>
<div class="tab">
<h3 class="tab-title">Not Called Listeners <span class="badge">{{ collector.notcalledlisteners|length }}</span></h3>
<div class="tab-content">
{% if collector.notcalledlisteners is empty %}
<div class="empty">
<p>
<strong>There are no uncalled listeners</strong>.
</p>
<p>
All listeners were called for this request or an error occurred
when trying to collect uncalled listeners (in which case check the
logs to get more information).
</p>
{% if dispatcherData['called_listeners'] is empty %}
<div class="empty empty-panel">
<p>No events have been recorded. Check that debugging is enabled in the kernel.</p>
</div>
{% else %}
{{ helper.render_table(collector.notcalledlisteners) }}
<div class="sf-tabs">
<div class="tab">
<h3 class="tab-title">Called Listeners <span class="badge">{{ dispatcherData['called_listeners']|length }}</span></h3>
<div class="tab-content">
{{ _self.render_table(dispatcherData['called_listeners']) }}
</div>
</div>
<div class="tab">
<h3 class="tab-title">Not Called Listeners <span class="badge">{{ dispatcherData['not_called_listeners']|length }}</span></h3>
<div class="tab-content">
{% if dispatcherData['not_called_listeners'] is empty %}
<div class="empty">
<p>
<strong>There are no uncalled listeners</strong>.
</p>
<p>
All listeners were called or an error occurred
when trying to collect uncalled listeners (in which case check the
logs to get more information).
</p>
</div>
{% else %}
{{ _self.render_table(dispatcherData['not_called_listeners']) }}
{% endif %}
</div>
</div>
<div class="tab">
<h3 class="tab-title">Orphaned Events <span class="badge">{{ dispatcherData['orphaned_events']|length }}</span></h3>
<div class="tab-content">
{% if dispatcherData['orphaned_events'] is empty %}
<div class="empty">
<p>
<strong>There are no orphaned events</strong>.
</p>
<p>
All dispatched events were handled or an error occurred
when trying to collect orphaned events (in which case check the
logs to get more information).
</p>
</div>
{% else %}
<table>
<thead>
<tr>
<th>Event</th>
</tr>
</thead>
<tbody>
{% for event in dispatcherData['orphaned_events'] %}
<tr>
<td class="font-normal">{{ event }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
</div>
</div>
</div>
{% endif %}
</div>
</div>
<div class="tab">
<h3 class="tab-title">Orphaned Events <span class="badge">{{ collector.orphanedEvents|length }}</span></h3>
<div class="tab-content">
{% if collector.orphanedEvents is empty %}
<div class="empty">
<p>
<strong>There are no orphaned events</strong>.
</p>
<p>
All dispatched events were handled or an error occurred
when trying to collect orphaned events (in which case check the
logs to get more information).
</p>
</div>
{% else %}
<table>
<thead>
<tr>
<th>Event</th>
</tr>
</thead>
<tbody>
{% for event in collector.orphanedEvents %}
<tr>
<td class="font-normal">{{ event }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
</div>
</div>
</div>
{% endif %}
{% endfor %}
</div>
{% endblock %}
{% macro render_table(listeners) %}

View File

@@ -12,7 +12,7 @@
{% block menu %}
<span class="label {{ collector.hasexception ? 'label-status-error' : 'disabled' }}">
<span class="icon">{{ include('@WebProfiler/Icon/exception.svg') }}</span>
<span class="icon">{{ source('@WebProfiler/Icon/exception.svg') }}</span>
<strong>Exception</strong>
{% if collector.hasexception %}
<span class="count">
@@ -23,11 +23,19 @@
{% endblock %}
{% block panel %}
{# these styles are needed to override some styles from Exception page, which wasn't
updated yet to the new style of the Symfony Profiler #}
<style>
.tab-navigation li { background: none; border: 0; font-size: 14px; }
.tab-navigation li.active { border-radius: 6px; }
.tab-navigation li.active .badge { background-color: var(--selected-badge-background); color: var(--selected-badge-color); }
</style>
<h2>Exceptions</h2>
{% if not collector.hasexception %}
<div class="empty">
<p>No exception was thrown and caught during the request.</p>
<div class="empty empty-panel">
<p>No exception was thrown and caught.</p>
</div>
{% else %}
<div class="sf-reset">

View File

@@ -1,9 +1,45 @@
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
{% block head %}
{{ parent() }}
<style>
.sf-profiler-httpclient-requests thead th {
vertical-align: top;
}
.sf-profiler-httpclient-requests .http-method {
border: 1px solid var(--header-status-request-method-color);
border-radius: 5px;
color: var(--header-status-request-method-color);
display: inline-block;
font-weight: 500;
line-height: 1;
margin-right: 6px;
padding: 2px 4px;
text-align: center;
white-space: nowrap;
}
.sf-profiler-httpclient-requests .status-response-status-code {
background: var(--gray-600);
border-radius: 4px;
color: var(--white);
display: inline-block;
font-size: 12px;
font-weight: bold;
margin-bottom: 2px;
padding: 1px 3px;
}
.sf-profiler-httpclient-requests .status-response-status-code.status-success { background: var(--header-success-status-code-background); color: var(--header-success-status-code-color); }
.sf-profiler-httpclient-requests .status-response-status-code.status-warning { background: var(--header-warning-status-code-background); color: var(--header-warning-status-code-color); }
.sf-profiler-httpclient-requests .status-response-status-code.status-error { background: var(--header-error-status-code-background); color: var(--header-error-status-code-color); }
</style>
{% endblock %}
{% block toolbar %}
{% if collector.requestCount %}
{% set icon %}
{{ include('@WebProfiler/Icon/http-client.svg') }}
{{ source('@WebProfiler/Icon/http-client.svg') }}
{% set status_color = '' %}
<span class="sf-toolbar-value">{{ collector.requestCount }}</span>
{% endset %}
@@ -25,7 +61,7 @@
{% block menu %}
<span class="label {{ collector.requestCount == 0 ? 'disabled' }}">
<span class="icon">{{ include('@WebProfiler/Icon/http-client.svg') }}</span>
<span class="icon">{{ source('@WebProfiler/Icon/http-client.svg') }}</span>
<strong>HTTP Client</strong>
{% if collector.requestCount %}
<span class="count">
@@ -38,7 +74,7 @@
{% block panel %}
<h2>HTTP Client</h2>
{% if collector.requestCount == 0 %}
<div class="empty">
<div class="empty empty-panel">
<p>No HTTP requests were made.</p>
</div>
{% else %}
@@ -77,28 +113,37 @@
{% endif %}
{% endfor %}
{% endif %}
<table>
<table class="sf-profiler-httpclient-requests">
<thead>
<tr>
<th>
<span class="label">{{ trace.method }}</span>
<span class="http-method">{{ trace.method }}</span>
</th>
<th class="full-width">
{{ trace.url }}
{% if trace.options is not empty %}
{{ profiler_dump(trace.options, maxDepth=1) }}
{% endif %}
</th>
{% if profiler_token and profiler_link %}
<th>
Profile
</th>
{% endif %}
{% if trace.curlCommand is defined and trace.curlCommand %}
<th>
<button class="btn btn-sm hidden" title="Copy as cURL" data-clipboard-text="{{ trace.curlCommand }}">Copy as cURL</button>
</th>
{% endif %}
</tr>
</thead>
<tbody>
{% if trace.options is not empty %}
<tr>
<th class="font-normal">Request options</th>
<td>{{ profiler_dump(trace.options, maxDepth=1) }}</td>
</tr>
{% endif %}
<tr>
<th>
<th class="font-normal">Response</th>
<td{% if trace.curlCommand is defined and trace.curlCommand %} colspan="2"{% endif %}>
{% if trace.http_code >= 500 %}
{% set responseStatus = 'error' %}
{% elseif trace.http_code >= 400 %}
@@ -106,11 +151,10 @@
{% else %}
{% set responseStatus = 'success' %}
{% endif %}
<span class="label status-{{ responseStatus }}">
<span class="font-normal status-response-status-code status-{{ responseStatus }}">
{{ trace.http_code }}
</span>
</th>
<td>
{{ profiler_dump(trace.info, maxDepth=1) }}
</td>
{% if profiler_token and profiler_link %}

View File

@@ -1,12 +1,330 @@
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
{% import _self as helper %}
{% block stylesheets %}
{{ parent() }}
<style>
:root {
--log-filter-active-num-color: #2563EB;
--log-timestamp-color: #555;
}
.theme-dark {
--log-filter-active-num-color: #2563EB;
--log-timestamp-color: #ccc;
}
.log-filters {
display: flex;
flex-wrap: wrap;
}
.log-filters .log-filter {
flex-shrink: 0;
margin-right: 15px;
position: relative;
}
.log-filters .log-filter summary {
align-items: center;
background: var(--button-background);
border-radius: 6px;
border: 1px solid var(--button-border-color);
box-shadow: var(--button-box-shadow);
color: var(--button-color);
cursor: pointer;
display: flex;
font-size: 13px;
font-weight: 500;
padding: 4px 8px;
white-space: nowrap;
}
.log-filters .log-filter summary:active {
box-shadow: none;
transform: translateY(1px);
}
.log-filters .log-filter summary .icon {
height: 18px;
width: 18px;
margin: 0 7px 0 0;
}
.log-filters .log-filter summary svg {
height: 18px;
width: 18px;
opacity: 0.7;
}
.log-filters .log-filter summary svg {
stroke-width: 2;
}
.log-filters .log-filter summary .filter-active-num {
color: var(--log-filter-active-num-color);
font-weight: bold;
padding: 0 1px;
}
.log-filter .tab-navigation {
position: relative;
}
.log-filter .tab-navigation input[type="radio"] {
position: absolute;
pointer-events: none;
opacity: 0;
}
.tab-navigation input[type="radio"]:checked + .tab-control {
background-color: var(--tab-active-background);
border-radius: 6px;
box-shadow: inset 0 0 0 1.5px var(--tab-active-border-color);
color: var(--tab-active-color);
position: relative;
z-index: 1;
}
.theme-dark .tab-navigation input[type="radio"]:checked + .tab-control {
box-shadow: inset 0 0 0 1px var(--tab-border-color);
}
.tab-navigation input[type="radio"]:focus-visible + .tab-control {
outline: 1px solid var(--color-link);
}
.tab-navigation input[type="radio"]:checked + .tab-control + input[type="radio"] + .tab-control:before{
width: 0;
}
.log-filters .log-filter .log-filter-content {
background: var(--base-0);
border: 1px solid var(--table-border-color);
border-radius: 6px;
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
padding: 15px;
position: absolute;
left: 0;
top: 32px;
max-width: 400px;
min-width: 200px;
z-index: 9999;
}
.log-filters .log-filter .log-filter-content .log-filter-option {
align-items: center;
display: flex;
}
.log-filter .filter-select-all-or-none {
margin-bottom: 10px;
}
.log-filter .filter-select-all-or-none button + button {
margin-left: 15px;
}
.log-filters .log-filter .log-filter-content .log-filter-option + .log-filter-option {
margin: 7px 0 0;
}
.log-filters .log-filter .log-filter-content .log-filter-option label {
cursor: pointer;
flex: 1;
padding-left: 10px;
}
table.logs {
border-bottom-width: 0;
border-collapse: collapse;
}
table.logs tr + tr td {
border-width: 1px 0 0;
}
table.logs .metadata {
display: block;
font-size: 12px;
}
.theme-dark tr.status-error td,
.theme-dark tr.status-warning td { border-bottom: unset; border-top: unset; }
table.logs .log-timestamp {
color: var(--log-timestamp-color);
}
table.logs .log-metadata {
margin: 8px 0 0;
}
table.logs .log-metadata > span {
display: inline-block;
}
table.logs .log-metadata > span + span {
margin-left: 10px;
}
table.logs .log-metadata .log-channel {
color: var(--base-1);
font-size: 13px;
font-weight: bold;
}
table.logs .log-metadata .badge {
background: var(--badge-light-background);
color: var(--badge-light-color);
}
table.logs .log-metadata .log-num-occurrences {
color: var(--color-muted);
font-size: 13px;
}
table.logs .log-metadata .context {
background: var(--code-block-background);
border-radius: 4px;
padding: 5px;
}
table.logs .log-metadata .context + .context {
margin-top: 10px;
}
.log-type-badge {
background: var(--badge-light-background);
box-shadow: none;
color: var(--badge-light-color);
display: inline-block;
font-family: var(--font-family-system);
margin-top: 5px;
}
.log-type-badge.badge-deprecation,
.log-type-badge.badge-warning {
background: var(--badge-warning-background);
color: var(--badge-warning-color);
}
.log-type-badge.badge-error {
background: var(--badge-danger-background);
color: var(--badge-danger-color);
}
.log-type-badge.badge-silenced {
background: #EDE9FE;
color: #6D28D9;
}
.theme-dark .log-type-badge.badge-silenced {
background: #5B21B6;
color: #EDE9FE;
}
tr.log-status-warning > td:first-child,
tr.log-status-error > td:first-child,
tr.log-status-silenced > td:first-child {
position: relative;
}
tr.log-status-warning > td:first-child:before,
tr.log-status-error > td:first-child:before,
tr.log-status-silenced > td:first-child:before {
background: transparent;
border-radius: 0;
content: '';
position: absolute;
top: 0;
left: 0;
width: 4px;
height: 100%;
}
tr.log-status-warning > td:first-child:before {
background: var(--yellow-400);
}
tr.log-status-error > td:first-child:before {
background: var(--red-400);
}
tr.log-status-silenced > td:first-child:before {
background: #a78bfa;
}
.container-compilation-logs {
background: var(--table-background);
border: 1px solid var(--base-2);
border-radius: 6px;
margin-top: 30px;
padding: 15px;
}
.container-compilation-logs summary {
cursor: pointer;
}
.container-compilation-logs summary h4 {
margin: 0 0 5px;
}
.container-compilation-logs summary p {
margin: 0;
}
</style>
{% endblock %}
{% block javascripts %}
<script>
window.addEventListener('DOMContentLoaded', () => {
new SymfonyProfilerLoggerPanel();
});
class SymfonyProfilerLoggerPanel {
constructor() {
this.#initializeLogsTable();
}
#initializeLogsTable() {
this.#updateLogsTable();
document.querySelectorAll('.log-filter input').forEach((input) => {
input.addEventListener('change', () => { this.#updateLogsTable(); });
});
document.querySelectorAll('.filter-select-all-or-none button').forEach((link) => {
link.addEventListener('click', () => {
const selectAll = link.classList.contains('select-all');
link.closest('.log-filter-content').querySelectorAll('input').forEach((input) => {
input.checked = selectAll;
});
this.#updateLogsTable();
});
});
document.body.addEventListener('click', (event) => {
document.querySelectorAll('details.log-filter').forEach((filterElement) => {
if (!filterElement.contains(event.target) && filterElement.open) {
filterElement.open = false;
}
});
});
}
#updateLogsTable() {
const logs = document.querySelector('table.logs');
if (null === logs) {
return;
}
const selectedType = document.querySelector('#log-filter-type input:checked').value;
const priorities = document.querySelectorAll('#log-filter-priority input');
const allPriorities = Array.from(priorities).map((input) => input.value);
const selectedPriorities = Array.from(priorities).filter((input) => input.checked).map((input) => input.value);
const channels = document.querySelectorAll('#log-filter-channel input');
const selectedChannels = Array.from(channels).filter((input) => input.checked).map((input) => input.value);
/* hide rows that don't match the current filters */
let numVisibleRows = 0;
logs.querySelectorAll('tbody tr').forEach((row) => {
if ('all' !== selectedType && selectedType !== row.getAttribute('data-type')) {
row.style.display = 'none';
return;
}
const priority = row.getAttribute('data-priority');
if (false === selectedPriorities.includes(priority) && true === allPriorities.includes(priority)) {
row.style.display = 'none';
return;
}
if ('' !== row.getAttribute('data-channel') && false === selectedChannels.includes(row.getAttribute('data-channel'))) {
row.style.display = 'none';
return;
}
row.style.display = 'table-row';
numVisibleRows++;
});
document.querySelector('table.logs').style.display = 0 === numVisibleRows ? 'none' : 'table';
document.querySelector('.no-logs-message').style.display = 0 === numVisibleRows ? 'block' : 'none';
/* update the selected totals of all filters */
document.querySelector('#log-filter-priority .filter-active-num').innerText = (priorities.length === selectedPriorities.length) ? 'All' : selectedPriorities.length;
document.querySelector('#log-filter-channel .filter-active-num').innerText = (channels.length === selectedChannels.length) ? 'All' : selectedChannels.length;
}
}
</script>
{% endblock %}
{% block toolbar %}
{% if collector.counterrors or collector.countdeprecations or collector.countwarnings %}
{% set icon %}
{% set status_color = collector.counterrors ? 'red' : collector.countwarnings ? 'yellow' : 'none' %}
{{ include('@WebProfiler/Icon/logger.svg') }}
{{ source('@WebProfiler/Icon/logger.svg') }}
<span class="sf-toolbar-value">{{ collector.counterrors ?: (collector.countdeprecations + collector.countwarnings) }}</span>
{% endset %}
@@ -33,7 +351,7 @@
{% block menu %}
<span class="label label-status-{{ collector.counterrors ? 'error' : collector.countwarnings ? 'warning' : 'none' }} {{ collector.logs is empty ? 'disabled' }}">
<span class="icon">{{ include('@WebProfiler/Icon/logger.svg') }}</span>
<span class="icon">{{ source('@WebProfiler/Icon/logger.svg') }}</span>
<strong>Logs</strong>
{% if collector.counterrors or collector.countdeprecations or collector.countwarnings %}
<span class="count">
@@ -47,7 +365,7 @@
<h2>Log Messages</h2>
{% if collector.processedLogs is empty %}
<div class="empty">
<div class="empty empty-panel">
<p>No log messages available.</p>
</div>
{% else %}
@@ -57,33 +375,30 @@
{% set filters = collector.filters %}
<div class="log-filters">
<div id="log-filter-type" class="log-filter">
<ul class="tab-navigation">
<li class="{{ not has_error_logs and not has_deprecation_logs ? 'active' }}">
<input {{ not has_error_logs and not has_deprecation_logs ? 'checked' }} type="radio" id="filter-log-type-all" name="filter-log-type" value="all">
<label for="filter-log-type-all">All messages</label>
</li>
<div class="tab-navigation">
{% set initially_active_tab = has_error_logs ? 'error' : has_deprecation_logs ? 'deprecation' : 'all' %}
<input type="radio" id="filter-log-type-all" name="filter-log-type" value="all" {{ 'all' == initially_active_tab ? 'checked' }}>
<label for="filter-log-type-all" class="tab-control">
All messages
</label>
<li class="{{ has_error_logs ? 'active' }}">
<input {{ has_error_logs ? 'checked' }} type="radio" id="filter-log-type-error" name="filter-log-type" value="error">
<label for="filter-log-type-error">
Errors
<span class="badge status-{{ collector.counterrors ? 'error' }}">{{ collector.counterrors|default(0) }}</span>
</label>
</li>
<input type="radio" id="filter-log-type-error" name="filter-log-type" value="error" {{ 'error' == initially_active_tab ? 'checked' }}>
<label for="filter-log-type-error" class="tab-control">
Errors
<span class="badge status-{{ collector.counterrors ? 'error' }}">{{ collector.counterrors|default(0) }}</span>
</label>
<li class="{{ not has_error_logs and has_deprecation_logs ? 'active' }}">
<input {{ not has_error_logs and has_deprecation_logs ? 'checked' }} type="radio" id="filter-log-type-deprecation" name="filter-log-type" value="deprecation">
<label for="filter-log-type-deprecation">
Deprecations
<span class="badge status-{{ collector.countdeprecations ? 'warning' }}">{{ collector.countdeprecations|default(0) }}</span>
</label>
</li>
</ul>
<input type="radio" id="filter-log-type-deprecation" name="filter-log-type" value="deprecation" {{ 'deprecation' == initially_active_tab ? 'checked' }}>
<label for="filter-log-type-deprecation" class="tab-control">
Deprecations
<span class="badge status-{{ collector.countdeprecations ? 'warning' }}">{{ collector.countdeprecations|default(0) }}</span>
</label>
</div>
</div>
<details id="log-filter-priority" class="log-filter">
<summary>
<span class="icon">{{ include('@WebProfiler/Icon/filter.svg') }}</span>
<span class="icon">{{ source('@WebProfiler/Icon/filter.svg') }}</span>
Level (<span class="filter-active-num">{{ filters.priority|length - 1 }}</span>)
</summary>
@@ -104,7 +419,7 @@
<details id="log-filter-channel" class="log-filter">
<summary>
<span class="icon">{{ include('@WebProfiler/Icon/filter.svg') }}</span>
<span class="icon">{{ source('@WebProfiler/Icon/filter.svg') }}</span>
Channel (<span class="filter-active-num">{{ filters.channel|length - 1 }}</span>)
</summary>
@@ -126,13 +441,15 @@
<table class="logs">
<colgroup>
<col width="140px">
<col style="width: 140px">
<col>
</colgroup>
<thead>
<th>Time</th>
<th>Message</th>
<tr>
<th>Time</th>
<th>Message</th>
</tr>
</thead>
<tbody>
@@ -143,7 +460,7 @@
%}
<tr class="log-status-{{ css_class }}" data-type="{{ log.type }}" data-priority="{{ log.priority }}" data-channel="{{ log.channel }}" style="{{ 'event' == log.channel or 'DEBUG' == log.priorityName ? 'display: none' }}">
<td class="log-timestamp">
<time title="{{ log.timestamp|date('r') }}" datetime="{{ log.timestamp|date('c') }}">
<time class="newline" title="{{ log.timestamp|date('r') }}" datetime="{{ log.timestamp|date(constant('DateTimeInterface::RFC3339_EXTENDED')) }}" data-convert-to-user-timezone data-render-as-time data-render-with-millisecond-precision>
{{ log.timestamp|date('H:i:s.v') }}
</time>
@@ -163,7 +480,7 @@
</td>
<td class="font-normal">
{{ helper.render_log_message('debug', loop.index, log) }}
{{ _self.render_log_message('debug', loop.index, log) }}
</td>
</tr>
{% endfor %}
@@ -173,19 +490,13 @@
<div class="no-logs-message empty">
<p>There are no log messages.</p>
</div>
<script>Sfjs.initializeLogsTable();</script>
{% endif %}
{% set compilerLogTotal = 0 %}
{% for logs in collector.compilerLogs %}
{% set compilerLogTotal = compilerLogTotal + logs|length %}
{% endfor %}
{% set compilerLogTotal = collector.compilerLogs|reduce((total, logs) => total + logs|length, 0) %}
<details class="container-compilation-logs">
<summary>
<h4>Container Compilation Logs <span class="text-muted">({{ compilerLogTotal }})</span></h4>
<p class="text-muted">Log messages generated during the compilation of the service container.</p>
<span class="text-muted">Log messages generated during the compilation of the service container.</span>
</summary>
{% if collector.compilerLogs is empty %}

View File

@@ -1,11 +1,199 @@
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
{% block stylesheets %}
{{ parent() }}
<style>
:root {
--mailer-email-table-wrapper-background: var(--gray-100);
--mailer-email-table-active-row-background: #dbeafe;
--mailer-email-table-active-row-color: var(--color-text);
}
.theme-dark {
--mailer-email-table-wrapper-background: var(--gray-900);
--mailer-email-table-active-row-background: var(--gray-300);
--mailer-email-table-active-row-color: var(--gray-800);
}
.mailer-email-summary-table-wrapper {
background: var(--mailer-email-table-wrapper-background);
border-bottom: 4px double var(--table-border-color);
border-radius: inherit;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
margin: 0 -9px 10px -9px;
padding-bottom: 10px;
transform: translateY(-9px);
max-height: 265px;
overflow-y: auto;
}
.mailer-email-summary-table,
.mailer-email-summary-table tr,
.mailer-email-summary-table td {
border: 0;
border-radius: inherit;
border-bottom-left-radius: 0;
border-bottom-right-radius: 0;
box-shadow: none;
}
.mailer-email-summary-table th {
color: var(--color-muted);
font-size: 13px;
padding: 4px 10px;
}
.mailer-email-summary-table tr td,
.mailer-email-summary-table tr:last-of-type td {
border: solid var(--table-border-color);
border-width: 1px 0;
}
.mailer-email-summary-table-row {
margin: 5px 0;
}
.mailer-email-summary-table-row:hover {
cursor: pointer;
}
.mailer-email-summary-table-row.active {
background: var(--mailer-email-table-active-row-background);
color: var(--mailer-email-table-active-row-color);
}
.mailer-email-summary-table-row td {
font-family: var(--font-family-system);
font-size: inherit;
}
.mailer-email-details {
display: none;
}
.mailer-email-details.active {
display: block;
}
.mailer-transport-information {
border-bottom: 1px solid var(--form-input-border-color);
padding-bottom: 5px;
font-size: var(--font-size-body);
margin: 5px 0 10px 5px;
}
.mailer-transport-information .badge {
font-size: inherit;
font-weight: inherit;
}
.mailer-message-subject {
font-size: 21px;
font-weight: bold;
margin: 5px;
}
.mailer-message-headers {
margin-bottom: 10px;
}
.mailer-message-headers p {
font-size: var(--font-size-body);
margin: 2px 5px;
}
.mailer-message-header-secondary {
color: var(--color-muted);
}
.mailer-message-attachments-title {
align-items: center;
display: flex;
font-size: var(--font-size-body);
font-weight: 600;
margin-bottom: 10px;
}
.mailer-message-attachments-title svg {
color: var(--color-muted);
margin-right: 5px;
height: 18px;
width: 18px;
}
.mailer-message-attachments-title span {
font-weight: normal;
margin-left: 4px;
}
.mailer-message-attachments-list {
list-style: none;
margin: 0 0 5px 20px;
padding: 0;
}
.mailer-message-attachments-list li {
align-items: center;
display: flex;
}
.mailer-message-attachments-list li svg {
margin-right: 5px;
height: 18px;
width: 18px;
}
.mailer-message-attachments-list li a {
margin-left: 5px;
}
.mailer-email-body {
margin: 0;
padding: 6px 8px;
}
.mailer-empty-email-body {
background-image: url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' stroke='%23e5e5e5' stroke-width='4' stroke-dasharray='6%2c 14' stroke-dashoffset='0' stroke-linecap='square'/%3e%3c/svg%3e");
border-radius: 6px;
color: var(--color-muted);
margin: 1em 0 0;
padding: .5em 1em;
}
.theme-dark .mailer-empty-email-body {
background-image: url("data:image/svg+xml,%3csvg width='100%25' height='100%25' xmlns='http://www.w3.org/2000/svg'%3e%3crect width='100%25' height='100%25' fill='none' stroke='%23737373' stroke-width='4' stroke-dasharray='6%2c 14' stroke-dashoffset='0' stroke-linecap='square'/%3e%3c/svg%3e");
}
.mailer-empty-email-body p {
font-size: var(--font-size-body);
margin: 0;
padding: 0.5em 0;
}
.mailer-message-download-raw {
align-items: center;
display: flex;
padding: 5px 0 0 5px;
}
.mailer-message-download-raw svg {
height: 18px;
width: 18px;
margin-right: 3px;
}
</style>
{% endblock %}
{% block javascripts %}
{{ parent() }}
<script>
window.addEventListener('DOMContentLoaded', () => {
new SymfonyProfilerMailerPanel();
});
class SymfonyProfilerMailerPanel {
constructor() {
this.#initializeEmailsTable();
}
#initializeEmailsTable() {
const emailRows = document.querySelectorAll('.mailer-email-summary-table-row');
emailRows.forEach((emailRow) => {
emailRow.addEventListener('click', () => {
emailRows.forEach((row) => row.classList.remove('active'));
emailRow.classList.add('active');
document.querySelectorAll('.mailer-email-details').forEach((emailDetails) => emailDetails.style.display = 'none');
document.querySelector(emailRow.getAttribute('data-target')).style.display = 'block';
});
});
}
}
</script>
{% endblock %}
{% block toolbar %}
{% set events = collector.events %}
{% if events.messages|length %}
{% set icon %}
{% include('@WebProfiler/Icon/mailer.svg') %}
{{ source('@WebProfiler/Icon/mailer.svg') }}
<span class="sf-toolbar-value">{{ events.messages|length }}</span>
{% endset %}
@@ -24,50 +212,13 @@
{% endif %}
{% endblock %}
{% block head %}
{{ parent() }}
<style type="text/css">
/* utility classes */
.m-t-0 { margin-top: 0 !important; }
.m-t-10 { margin-top: 10px !important; }
/* basic grid */
.row {
display: flex;
flex-wrap: wrap;
margin-right: -15px;
margin-left: -15px;
}
.col {
flex-basis: 0;
flex-grow: 1;
max-width: 100%;
position: relative;
width: 100%;
min-height: 1px;
padding-right: 15px;
padding-left: 15px;
}
.col-4 {
flex: 0 0 33.333333%;
max-width: 33.333333%;
}
/* small tabs */
.sf-tabs-sm .tab-navigation li {
font-size: 14px;
padding: .3em .5em;
}
</style>
{% endblock %}
{% block menu %}
{% set events = collector.events %}
<span class="label {{ events.messages is empty ? 'disabled' }}">
<span class="icon">{{ include('@WebProfiler/Icon/mailer.svg') }}</span>
<span class="icon">{{ source('@WebProfiler/Icon/mailer.svg') }}</span>
<strong>E-mails</strong>
<strong>Emails</strong>
{% if events.messages|length > 0 %}
<span class="count">
<span>{{ events.messages|length }}</span>
@@ -78,140 +229,225 @@
{% block panel %}
{% set events = collector.events %}
<h2>Emails</h2>
{% if not events.messages|length %}
<div class="empty">
<div class="empty empty-panel">
<p>No emails were sent.</p>
</div>
{% else %}
<div class="metrics">
<div class="metric-group">
<div class="metric">
<span class="value">{{ events.events|filter(e => e.isQueued())|length }}</span>
<span class="label">Queued</span>
</div>
<div class="metric">
<span class="value">{{ events.events|filter(e => not e.isQueued())|length }}</span>
<span class="label">Sent</span>
</div>
</div>
</div>
{% endif %}
<div class="metrics">
<div class="metric">
<span class="value">{{ events.events|filter(e => e.isQueued())|length }}</span>
<span class="label">Queued</span>
{% if events.transports|length > 1 %}
{% for transport in events.transports %}
<h2><code>{{ transport }}</code> transport</h2>
{{ _self.render_transport_details(collector, transport) }}
{% endfor %}
{% elseif events.transports is not empty %}
{{ _self.render_transport_details(collector, events.transports|first, true) }}
{% endif %}
{% macro render_transport_details(collector, transport, show_transport_name = false) %}
<div class="card">
{% set num_emails = collector.events.events(transport)|length %}
{% if num_emails > 1 %}
<div class="mailer-email-summary-table-wrapper">
<table class="mailer-email-summary-table">
<thead>
<tr>
<th>#</th>
<th>Subject</th>
<th>To</th>
<th class="visually-hidden">Actions</th>
</tr>
</thead>
<tbody>
{% for event in collector.events.events(transport) %}
<tr class="mailer-email-summary-table-row {{ loop.first ? 'active' }}" data-target="#email-{{ loop.index }}">
<td>{{ loop.index }}</td>
<td>{{ event.message.getSubject() ?? '(No subject)' }}</td>
<td>{{ event.message.getTo()|map(addr => addr.toString())|join(', ')|default('(empty)') }}</td>
<td class="visually-hidden"><button class="mailer-email-summary-table-row-button" data-target="#email-{{ loop.index }}">View email details</button></td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
{% for event in collector.events.events(transport) %}
<div class="mailer-email-details {{ loop.first ? 'active' }}" id="email-{{ loop.index }}">
{{ _self.render_email_details(collector, transport, event.message, event.isQueued, show_transport_name) }}
</div>
{% endfor %}
{% else %}
{% set event = (collector.events.events(transport)|first) %}
{{ _self.render_email_details(collector, transport, event.message, event.isQueued, show_transport_name) }}
{% endif %}
</div>
{% endmacro %}
<div class="metric">
<span class="value">{{ events.events|filter(e => not e.isQueued())|length }}</span>
<span class="label">Sent</span>
</div>
</div>
{% macro render_email_details(collector, transport, message, message_is_queued, show_transport_name = false) %}
{% if show_transport_name %}
<p class="mailer-transport-information">
<strong>Status:</strong> <span class="badge badge-{{ message_is_queued ? 'warning' : 'success' }}">{{ message_is_queued ? 'Queued' : 'Sent' }}</span>
&bull;
<strong>Transport:</strong> <code>{{ transport }}</code>
</p>
{% endif %}
{% for transport in events.transports %}
<div class="card-block">
<div class="sf-tabs sf-tabs-sm">
{% for event in events.events(transport) %}
{% set message = event.message %}
<div class="tab">
<h3 class="tab-title">Email {{ event.isQueued() ? 'queued' : 'sent via ' ~ transport }}</h3>
<div class="tab-content">
<div class="card">
{% if message.headers is not defined %}
{# RawMessage instance #}
<div class="card-block">
<pre class="prewrap" style="max-height: 600px">{{ message.toString() }}</pre>
</div>
{% else %}
{# Message instance #}
<div class="card-block">
<div class="sf-tabs sf-tabs-sm">
<div class="tab">
<h3 class="tab-title">Headers</h3>
<div class="tab-content">
<span class="label">Subject</span>
<h2 class="m-t-10">{{ message.headers.get('subject').bodyAsString() ?? '(empty)' }}</h2>
<div class="row">
<div class="col col-4">
<span class="label">From</span>
<pre class="prewrap">{{ (message.headers.get('from').bodyAsString() ?? '(empty)')|replace({'From:': ''}) }}</pre>
{% if message.headers is not defined %}
{# render the raw message contents #}
<a class="mailer-message-download-raw" href="data:application/octet-stream;base64,{{ collector.base64Encode(message.toString()) }}" download="email.eml">
{{ source('@WebProfiler/Icon/download.svg') }}
Download as EML file
</a>
<span class="label">To</span>
<pre class="prewrap">{{ (message.headers.get('to').bodyAsString() ?? '(empty)')|replace({'To:': ''}) }}</pre>
</div>
<div class="col">
<span class="label">Headers</span>
<pre class="prewrap">{% for header in message.headers.all|filter(header => (header.name ?? '') not in ['Subject', 'From', 'To']) %}
{{- header.toString }}
{%~ endfor %}</pre>
</div>
</div>
</div>
</div>
{% if message.htmlBody is defined %}
{# Email instance #}
{% set htmlBody = message.htmlBody() %}
{% if htmlBody is not null %}
<div class="tab">
<h3 class="tab-title">HTML Preview</h3>
<div class="tab-content">
<pre class="prewrap" style="max-height: 600px">
<iframe
src="data:text/html;charset=utf-8;base64,{{ collector.base64Encode(htmlBody) }}"
style="height: 80vh;width: 100%;"
>
</iframe>
</pre>
</div>
</div>
<div class="tab">
<h3 class="tab-title">HTML Content</h3>
<div class="tab-content">
<pre class="prewrap" style="max-height: 600px">
{%- if message.htmlCharset() %}
{{- htmlBody|convert_encoding('UTF-8', message.htmlCharset()) }}
{%- else %}
{{- htmlBody }}
{%- endif -%}
</pre>
</div>
</div>
{% endif %}
{% set textBody = message.textBody() %}
{% if textBody is not null %}
<div class="tab">
<h3 class="tab-title">Text Content</h3>
<div class="tab-content">
<pre class="prewrap" style="max-height: 600px">
{%- if message.textCharset() %}
{{- textBody|convert_encoding('UTF-8', message.textCharset()) }}
{%- else %}
{{- textBody }}
{%- endif -%}
</pre>
</div>
</div>
{% endif %}
{% for attachment in message.attachments %}
<div class="tab">
<h3 class="tab-title">Attachment #{{ loop.index }}</h3>
<div class="tab-content">
<pre class="prewrap" style="max-height: 600px">{{ attachment.toString() }}</pre>
</div>
</div>
{% endfor %}
<pre class="prewrap" style="max-height: 600px; margin-left: 5px">{{ message.toString() }}</pre>
{% else %}
<div class="sf-tabs">
<div class="tab">
<h3 class="tab-title">Email contents</h3>
<div class="tab-content">
<div class="card-block">
<p class="mailer-message-subject">
{{ message.getSubject() ?? '(No subject)' }}
</p>
<div class="mailer-message-headers">
<p><strong>From:</strong> {{ message.getFrom()|map(addr => addr.toString())|join(', ')|default('(empty)') }}</p>
<p><strong>To:</strong> {{ message.getTo()|map(addr => addr.toString())|join(', ')|default('(empty)') }}</p>
{% for header in message.headers.all|filter(header => (header.name ?? '')|lower not in ['subject', 'from', 'to']) %}
<p class="mailer-message-header-secondary">{{ header.toString }}</p>
{% endfor %}
</div>
</div>
{% if message.attachments %}
<div class="card-block">
{% set num_of_attachments = message.attachments|length %}
{% set total_attachments_size_in_bytes = message.attachments|reduce((total_size, attachment) => total_size + attachment.body|length, 0) %}
<p class="mailer-message-attachments-title">
{{ source('@WebProfiler/Icon/attachment.svg') }}
Attachments <span>({{ num_of_attachments }} file{{ num_of_attachments > 1 ? 's' }} / {{ _self.render_file_size_humanized(total_attachments_size_in_bytes) }})</span>
</p>
<ul class="mailer-message-attachments-list">
{% for attachment in message.attachments %}
<li>
{{ source('@WebProfiler/Icon/file.svg') }}
{% if attachment.filename|default %}
{{ attachment.filename }}
{% else %}
<em>(no filename)</em>
{% endif %}
<div class="tab">
<h3 class="tab-title">Parts Hierarchy</h3>
<div class="tab-content">
<pre class="prewrap" style="max-height: 600px">{{ message.body().asDebugString() }}</pre>
</div>
</div>
<div class="tab">
<h3 class="tab-title">Raw</h3>
<div class="tab-content">
<pre class="prewrap" style="max-height: 600px">{{ message.toString() }}</pre>
</div>
({{ _self.render_file_size_humanized(attachment.body|length) }})
<a href="data:{{ attachment.contentType|default('application/octet-stream') }};base64,{{ collector.base64Encode(attachment.body) }}" download="{{ attachment.filename|default('attachment') }}">Download</a>
</li>
{% endfor %}
</ul>
</div>
{% endif %}
<div class="card-block">
{% set textBody = message.textBody %}
{% set htmlBody = message.htmlBody %}
<div class="sf-tabs sf-tabs-sm">
<div class="tab {{ not textBody ? 'disabled' }} {{ textBody ? 'active' }}">
<h3 class="tab-title">Text content</h3>
<div class="tab-content">
{% if textBody %}
<pre class="mailer-email-body prewrap" style="max-height: 600px">
{%- if message.textCharset() %}
{{- textBody|convert_encoding('UTF-8', message.textCharset()) }}
{%- else %}
{{- textBody }}
{%- endif -%}
</pre>
{% else %}
<div class="mailer-empty-email-body">
<p>The text body is empty.</p>
</div>
{% endif %}
</div>
</div>
{% if htmlBody %}
<div class="tab">
<h3 class="tab-title">HTML preview</h3>
<div class="tab-content">
<pre class="prewrap" style="max-height: 600px"><iframe src="data:text/html;charset=utf-8;base64,{{ collector.base64Encode(htmlBody) }}" style="height: 80vh;width: 100%;"></iframe>
</pre>
</div>
</div>
{% endif %}
<div class="tab {{ not htmlBody ? 'disabled' }} {{ not textBody and htmlBody ? 'active' }}">
<h3 class="tab-title">HTML content</h3>
<div class="tab-content">
{% if htmlBody %}
<pre class="mailer-email-body prewrap" style="max-height: 600px">
{%- if message.htmlCharset() %}
{{- htmlBody|convert_encoding('UTF-8', message.htmlCharset()) }}
{%- else %}
{{- htmlBody }}
{%- endif -%}
</pre>
{% else %}
<div class="mailer-empty-email-body">
<p>The HTML body is empty.</p>
</div>
{% endif %}
</div>
</div>
</div>
</div>
</div>
{% endfor %}
</div>
<div class="tab">
<h3 class="tab-title">MIME parts</h3>
<div class="tab-content">
<pre class="prewrap" style="max-height: 600px; margin-left: 5px">{{ message.body().asDebugString() }}</pre>
</div>
</div>
<div class="tab">
<h3 class="tab-title">Raw Message</h3>
<div class="tab-content">
<a class="mailer-message-download-raw" href="data:application/octet-stream;base64,{{ collector.base64Encode(message.toString()) }}" download="email.eml">
{{ source('@WebProfiler/Icon/download.svg') }}
Download as EML file
</a>
<pre class="prewrap" style="max-height: 600px; margin-left: 5px">{{ message.toString() }}</pre>
</div>
</div>
</div>
</div>
{% endfor %}
{% endif %}
{% endmacro %}
{% macro render_file_size_humanized(bytes) %}
{%- if bytes < 1000 -%}
{{- bytes ~ ' bytes' -}}
{%- elseif bytes < 1000 ** 2 -%}
{{- (bytes / 1000)|number_format(2) ~ ' kB' -}}
{%- else -%}
{{- (bytes / 1000 ** 2)|number_format(2) ~ ' MB' -}}
{%- endif -%}
{% endmacro %}
{% endblock %}

View File

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

View File

@@ -1,12 +1,49 @@
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
{% import _self as helper %}
{% block head %}
{{ parent() }}
<style>
.message-item thead th { position: relative; cursor: pointer; user-select: none; padding-right: 35px; }
.message-item tbody tr td:first-child { width: 170px; }
.message-item .label { float: right; padding: 1px 5px; opacity: .75; margin-left: 5px; }
.message-item .toggle-button { position: absolute; right: 6px; top: 6px; opacity: .5; pointer-events: none; color: inherit; }
.message-item .icon svg { height: 24px; width: 24px; }
.message-item .icon-close svg { transform: rotate(180deg); }
.message-item .sf-toggle-off .icon-close, .sf-toggle-on .icon-open { display: none; }
.message-item .sf-toggle-off .icon-open, .sf-toggle-on .icon-close { display: block; }
.message-bus .badge.status-some-errors { line-height: 16px; border-bottom: 2px solid #B0413E; }
.message-item tbody.sf-toggle-content.sf-toggle-visible { display: table-row-group; }
#collector-content .message-bus .trace {
border: var(--border);
background: var(--base-0);
padding: 10px;
margin: 0.5em 0;
overflow: auto;
}
#collector-content .message-bus .trace {
font-size: 12px;
}
#collector-content .message-bus .trace li {
margin-bottom: 0;
padding: 0;
}
#collector-content .message-bus .trace li.selected {
background: var(--highlight-selected-line);
}
</style>
{% endblock %}
{% block toolbar %}
{% if collector.messages|length > 0 %}
{% set status_color = collector.exceptionsCount ? 'red' %}
{% set icon %}
{{ include('@WebProfiler/Icon/messenger.svg') }}
{{ source('@WebProfiler/Icon/messenger.svg') }}
<span class="sf-toolbar-value">{{ collector.messages|length }}</span>
{% endset %}
@@ -31,7 +68,7 @@
{% block menu %}
<span class="label{{ collector.exceptionsCount ? ' label-status-error' }}{{ collector.messages is empty ? ' disabled' }}">
<span class="icon">{{ include('@WebProfiler/Icon/messenger.svg') }}</span>
<span class="icon">{{ source('@WebProfiler/Icon/messenger.svg') }}</span>
<strong>Messages</strong>
{% if collector.exceptionsCount > 0 %}
<span class="count">
@@ -41,36 +78,16 @@
</span>
{% endblock %}
{% block head %}
{{ parent() }}
<style>
.message-item thead th { position: relative; cursor: pointer; user-select: none; padding-right: 35px; }
.message-item tbody tr td:first-child { width: 170px; }
.message-item .label { float: right; padding: 1px 5px; opacity: .75; margin-left: 5px; }
.message-item .toggle-button { position: absolute; right: 6px; top: 6px; opacity: .5; pointer-events: none }
.message-item .icon svg { height: 24px; width: 24px; }
.message-item .sf-toggle-off .icon-close, .sf-toggle-on .icon-open { display: none; }
.message-item .sf-toggle-off .icon-open, .sf-toggle-on .icon-close { display: block; }
.message-bus .badge.status-some-errors { line-height: 16px; border-bottom: 2px solid #B0413E; }
.message-item tbody.sf-toggle-content.sf-toggle-visible { display: table-row-group; }
td.message-bus-dispatch-caller { background: #f1f2f3; }
.theme-dark td.message-bus-dispatch-caller { background: var(--base-1); }
</style>
{% endblock %}
{% block panel %}
{% import _self as helper %}
<h2>Messages</h2>
{% if collector.messages is empty %}
<div class="empty">
<div class="empty empty-panel">
<p>No messages have been collected.</p>
</div>
{% elseif 1 == collector.buses|length %}
<p class="text-muted">Ordered list of dispatched messages across all your buses</p>
{{ _self.render_bus_messages(collector.messages, true) }}
{% else %}
<div class="sf-tabs message-bus">
<div class="tab">
@@ -80,7 +97,7 @@
<div class="tab-content">
<p class="text-muted">Ordered list of dispatched messages across all your buses</p>
{{ helper.render_bus_messages(messages, true) }}
{{ _self.render_bus_messages(messages, true) }}
</div>
</div>
@@ -92,7 +109,7 @@
<div class="tab-content">
<p class="text-muted">Ordered list of messages dispatched on the <code>{{ bus }}</code> bus</p>
{{ helper.render_bus_messages(messages) }}
{{ _self.render_bus_messages(messages) }}
</div>
</div>
{% endfor %}
@@ -112,36 +129,33 @@
data-toggle-initial="{{ loop.first ? 'display' }}"
>
<span class="dump-inline">{{ profiler_dump(dispatchCall.message.type) }}</span>
{% if showBus %}
<span class="label">{{ dispatchCall.bus }}</span>
{% endif %}
{% if dispatchCall.exception is defined %}
<span class="label status-error">exception</span>
{% endif %}
<a class="toggle-button">
<span class="icon icon-close">{{ include('@WebProfiler/images/icon-minus-square.svg') }}</span>
<span class="icon icon-open">{{ include('@WebProfiler/images/icon-plus-square.svg') }}</span>
</a>
<button class="btn btn-link toggle-button" type="button">
<span class="icon icon-close">{{ source('@WebProfiler/Icon/chevron-down.svg') }}</span>
<span class="icon icon-open">{{ source('@WebProfiler/Icon/chevron-down.svg') }}</span>
</button>
</th>
</tr>
</thead>
<tbody id="message-item-{{ discr }}-{{ loop.index0 }}-details" class="sf-toggle-content">
<tr>
<td colspan="2" class="message-bus-dispatch-caller">
<span class="metadata">In
{% set caller = dispatchCall.caller %}
{% if caller.line %}
{% set link = caller.file|file_link(caller.line) %}
{% if link %}
<a href="{{ link }}" title="{{ caller.file }}">{{ caller.name }}</a>
{% else %}
<abbr title="{{ caller.file }}">{{ caller.name }}</abbr>
{% endif %}
<th scope="row" class="font-normal">Caller</th>
<td class="message-bus-dispatch-caller">
In
{% set caller = dispatchCall.caller %}
{% if caller.line %}
{% set link = caller.file|file_link(caller.line) %}
{% if link %}
<a href="{{ link }}" title="{{ caller.file }}">{{ caller.name }}</a>
{% else %}
{{ caller.name }}
<abbr title="{{ caller.file }}">{{ caller.name }}</abbr>
{% endif %}
line <a class="text-small sf-toggle" data-toggle-selector="#sf-trace-{{ discr }}-{{ loop.index0 }}">{{ caller.line }}</a>
</span>
{% else %}
{{ caller.name }}
{% endif %}
line <button type="button" class="btn-link text-small sf-toggle" data-toggle-selector="#sf-trace-{{ discr }}-{{ loop.index0 }}">{{ caller.line }}</button>
<div class="hidden" id="sf-trace-{{ discr }}-{{ loop.index0 }}">
<div class="trace">
@@ -157,27 +171,27 @@
</tr>
{% if showBus %}
<tr>
<td class="text-bold">Bus</td>
<th scope="row" class="font-normal">Bus</th>
<td>{{ dispatchCall.bus }}</td>
</tr>
{% endif %}
<tr>
<td class="text-bold">Message</td>
<th scope="row" class="font-normal">Message</th>
<td>{{ profiler_dump(dispatchCall.message.value, maxDepth=2) }}</td>
</tr>
<tr>
<td class="text-bold">Envelope stamps <span class="text-muted">when dispatching</span></td>
<th scope="row" class="font-normal">Envelope stamps <span class="block text-muted">when dispatching</span></th>
<td>
{% for item in dispatchCall.stamps %}
{{ profiler_dump(item) }}
{% else %}
<span class="text-muted">No items</span>
<span class="text-muted font-normal">No items</span>
{% endfor %}
</td>
</tr>
{% if dispatchCall.stamps_after_dispatch is defined %}
<tr>
<td class="text-bold">Envelope stamps <span class="text-muted">after dispatch</span></td>
<th scope="row" class="font-normal">Envelope stamps <span class="block text-muted">after dispatch</span></th>
<td>
{% for item in dispatchCall.stamps_after_dispatch %}
{{ profiler_dump(item) }}

View File

@@ -5,7 +5,7 @@
{% if events.messages|length %}
{% set icon %}
{% include('@WebProfiler/Icon/notifier.svg') %}
{{ source('@WebProfiler/Icon/notifier.svg') }}
<span class="sf-toolbar-value">{{ events.messages|length }}</span>
{% endset %}
@@ -17,7 +17,7 @@
{% for transport in events.transports %}
<div class="sf-toolbar-info-piece">
<b>{{ transport }}</b>
<b>{{ transport ?: '<em>Empty Transport Name</em>' }}</b>
<span class="sf-toolbar-status">{{ events.messages(transport)|length }}</span>
</div>
{% endfor %}
@@ -29,7 +29,7 @@
{% block head %}
{{ parent() }}
<style type="text/css">
<style>
/* utility classes */
.m-t-0 { margin-top: 0 !important; }
.m-t-10 { margin-top: 10px !important; }
@@ -68,7 +68,7 @@
{% set events = collector.events %}
<span class="label {{ events.messages|length ? '' : 'disabled' }}">
<span class="icon">{{ include('@WebProfiler/Icon/notifier.svg') }}</span>
<span class="icon">{{ source('@WebProfiler/Icon/notifier.svg') }}</span>
<strong>Notifications</strong>
{% if events.messages|length > 0 %}
@@ -85,7 +85,7 @@
<h2>Notifications</h2>
{% if not events.messages|length %}
<div class="empty">
<div class="empty empty-panel">
<p>No notifications were sent.</p>
</div>
{% endif %}
@@ -100,7 +100,7 @@
</div>
{% for transport in events.transports %}
<h3>{{ transport }}</h3>
<h3>{{ transport ?: '<em>Empty Transport Name</em>' }}</h3>
<div class="card-block">
<div class="sf-tabs sf-tabs-sm">
@@ -114,31 +114,31 @@
<span class="label">Subject</span>
<h2 class="m-t-10">{{ message.getSubject() ?? '(empty)' }}</h2>
</div>
{% if message.getNotification is defined %}
{% set notification = message.notification ?? null %}
{% if notification %}
<div class="card-block">
<div class="row">
<div class="col">
<span class="label">Content</span>
<pre class="prewrap">{{ message.getNotification().getContent() ?? '(empty)' }}</pre>
<pre class="prewrap">{{ notification.getContent() ?? '(empty)' }}</pre>
<span class="label">Importance</span>
<pre class="prewrap">{{ message.getNotification().getImportance() }}</pre>
<pre class="prewrap">{{ notification.getImportance() }}</pre>
</div>
</div>
</div>
{% endif %}
<div class="card-block">
<div class="sf-tabs sf-tabs-sm">
{% if message.getNotification is defined %}
{% if notification %}
<div class="tab">
<h3 class="tab-title">Notification</h3>
{% set notification = event.message.getNotification() %}
<div class="tab-content">
<pre class="prewrap" style="max-height: 600px">
{{- 'Subject: ' ~ notification.getSubject() }}<br/>
{{- 'Content: ' ~ notification.getContent() }}<br/>
{{- 'Importance: ' ~ notification.getImportance() }}<br/>
{{- 'Emoji: ' ~ (notification.getEmoji() is empty ? '(empty)' : notification.getEmoji()) }}<br/>
{{- 'Exception: ' ~ notification.getException() ?? '(empty)' }}<br/>
{{- 'Subject: ' ~ notification.getSubject() }}<br>
{{- 'Content: ' ~ notification.getContent() }}<br>
{{- 'Importance: ' ~ notification.getImportance() }}<br>
{{- 'Emoji: ' ~ (notification.getEmoji() is empty ? '(empty)' : notification.getEmoji()) }}<br>
{{- 'Exception: ' ~ notification.getException() ?? '(empty)' }}<br>
{{- 'ExceptionAsString: ' ~ (notification.getExceptionAsString() is empty ? '(empty)' : notification.getExceptionAsString()) }}
</pre>
</div>

View File

@@ -1,21 +1,43 @@
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
{% block head %}
{{ parent() }}
<style>
.empty-query-post-files {
display: flex;
justify-content: space-between;
}
.empty-query-post-files > div {
flex: 1;
}
.empty-query-post-files > div + div {
margin-left: 30px;
}
.empty-query-post-files h3 {
margin-top: 0;
}
.empty-query-post-files .empty {
margin-bottom: 0;
}
</style>
{% endblock %}
{% block toolbar %}
{% import _self as helper %}
{% set request_handler %}
{{ helper.set_handler(collector.controller) }}
{{ _self.set_handler(collector.controller) }}
{% endset %}
{% if collector.redirect %}
{% set redirect_handler %}
{{ helper.set_handler(collector.redirect.controller, collector.redirect.route, 'GET' != collector.redirect.method ? collector.redirect.method) }}
{{ _self.set_handler(collector.redirect.controller, collector.redirect.route, 'GET' != collector.redirect.method ? collector.redirect.method) }}
{% endset %}
{% endif %}
{% if collector.forwardtoken %}
{% set forward_profile = profile.childByToken(collector.forwardtoken) %}
{% set forward_handler %}
{{ helper.set_handler(forward_profile ? forward_profile.collector('request').controller : 'n/a') }}
{{ _self.set_handler(forward_profile ? forward_profile.collector('request').controller : 'n/a') }}
{% endset %}
{% endif %}
@@ -24,8 +46,8 @@
{% set icon %}
<span class="sf-toolbar-status sf-toolbar-status-{{ request_status_code_color }}">{{ collector.statuscode }}</span>
{% if collector.route %}
{% if collector.redirect %}{{ include('@WebProfiler/Icon/redirect.svg') }}{% endif %}
{% if collector.forwardtoken %}{{ include('@WebProfiler/Icon/forward.svg') }}{% endif %}
{% if collector.redirect %}<span class="sf-toolbar-request-icon">{{ source('@WebProfiler/Icon/redirect.svg') }}</span>{% endif %}
{% if collector.forwardtoken %}<span class="sf-toolbar-request-icon">{{ source('@WebProfiler/Icon/forward.svg') }}</span>{% endif %}
<span class="sf-toolbar-label">{{ 'GET' != collector.method ? collector.method }} @</span>
<span class="sf-toolbar-value sf-toolbar-info-piece-additional">{{ collector.route }}</span>
{% endif %}
@@ -99,16 +121,15 @@
{% block menu %}
<span class="label">
<span class="icon">{{ include('@WebProfiler/Icon/request.svg') }}</span>
<span class="icon">{{ source('@WebProfiler/Icon/request.svg') }}</span>
<strong>Request / Response</strong>
</span>
{% endblock %}
{% block panel %}
{% import _self as helper %}
{% set controller_name = _self.set_handler(collector.controller) %}
<h2>
{{ helper.set_handler(collector.controller) }}
{{ 'n/a' in controller_name ? 'Request / Response' : controller_name }}
</h2>
<div class="sf-tabs">
@@ -116,34 +137,52 @@
<h3 class="tab-title">Request</h3>
<div class="tab-content">
<h3>GET Parameters</h3>
{% if collector.requestquery.all is empty %}
<div class="empty">
<p>No GET parameters</p>
{% set has_no_query_post_or_files = collector.requestquery.all is empty and collector.requestrequest.all is empty and collector.requestfiles is empty %}
{% if has_no_query_post_or_files %}
<div class="empty-query-post-files" style="display: flex; align-items: stretch">
<div>
<h3>GET Parameters</h3>
<div class="empty"><p>None</p></div>
</div>
<div>
<h3>POST Parameters</h3>
<div class="empty"><p>None</p></div>
</div>
<div>
<h3>Uploaded Files</h3>
<div class="empty"><p>None</p></div>
</div>
</div>
{% else %}
{{ include('@WebProfiler/Profiler/bag.html.twig', { bag: collector.requestquery, maxDepth: 1 }, with_context = false) }}
{% endif %}
<h3>GET Parameters</h3>
<h3>POST Parameters</h3>
{% if collector.requestquery.all is empty %}
<div class="empty">
<p>No GET parameters</p>
</div>
{% else %}
{{ include('@WebProfiler/Profiler/bag.html.twig', { bag: collector.requestquery, maxDepth: 1 }, with_context = false) }}
{% endif %}
{% if collector.requestrequest.all is empty %}
<div class="empty">
<p>No POST parameters</p>
</div>
{% else %}
{{ include('@WebProfiler/Profiler/bag.html.twig', { bag: collector.requestrequest, maxDepth: 1 }, with_context = false) }}
{% endif %}
<h3>POST Parameters</h3>
<h4>Uploaded Files</h4>
{% if collector.requestrequest.all is empty %}
<div class="empty">
<p>No POST parameters</p>
</div>
{% else %}
{{ include('@WebProfiler/Profiler/bag.html.twig', { bag: collector.requestrequest, maxDepth: 1 }, with_context = false) }}
{% endif %}
{% if collector.requestfiles is empty %}
<div class="empty">
<p>No files were uploaded</p>
</div>
{% else %}
{{ include('@WebProfiler/Profiler/bag.html.twig', { bag: collector.requestfiles, maxDepth: 1 }, with_context = false) }}
<h4>Uploaded Files</h4>
{% if collector.requestfiles is empty %}
<div class="empty">
<p>No files were uploaded</p>
</div>
{% else %}
{{ include('@WebProfiler/Profiler/bag.html.twig', { bag: collector.requestfiles, maxDepth: 1 }, with_context = false) }}
{% endif %}
{% endif %}
<h3>Request Attributes</h3>
@@ -265,7 +304,7 @@
</div>
<div class="metric">
<span class="value">{{ include('@WebProfiler/Icon/' ~ (collector.statelesscheck ? 'yes' : 'no') ~ '.svg') }}</span>
<span class="value">{{ source('@WebProfiler/Icon/' ~ (collector.statelesscheck ? 'yes' : 'no') ~ '.svg') }}</span>
<span class="label">Stateless check enabled</span>
</div>
</div>
@@ -360,7 +399,7 @@
<div class="tab-content">
{% for child in profile.children %}
<h3>
{{ helper.set_handler(child.getcollector('request').controller) }}
{{ _self.set_handler(child.getcollector('request').controller) }}
<small>(token = <a href="{{ path('_profiler', { token: child.token }) }}">{{ child.token }}</a>)</small>
</h3>

View File

@@ -4,7 +4,7 @@
{% block menu %}
<span class="label">
<span class="icon">{{ include('@WebProfiler/Icon/router.svg') }}</span>
<span class="icon">{{ source('@WebProfiler/Icon/router.svg') }}</span>
<strong>Routing</strong>
</span>
{% endblock %}

View File

@@ -0,0 +1,355 @@
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
{% block head %}
{{ parent() }}
<style>
#collector-content .sf-serializer {
margin-bottom: 2em;
}
#collector-content .sf-serializer .trace {
border: var(--border);
background: var(--page-background);
padding: 10px;
margin: 0.5em 0;
overflow: auto;
}
#collector-content .sf-serializer .trace {
font-size: 12px;
}
#collector-content .sf-serializer .trace li {
margin-bottom: 0;
padding: 0;
}
#collector-content .sf-serializer .trace li.selected {
background: var(--highlight-selected-line);
}
</style>
{% endblock %}
{% block toolbar %}
{% if collector.handledCount > 0 %}
{% set icon %}
{{ source('@WebProfiler/Icon/serializer.svg') }}
<span class="sf-toolbar-value">
{{ collector.handledCount }}
</span>
{% endset %}
{% set text %}
<div class="sf-toolbar-info-piece">
<b>Total calls</b>
<span class="sf-toolbar-status">{{ collector.handledCount }}</span>
</div>
<div class="sf-toolbar-info-piece">
<b>Total time</b>
<span>
{{ '%.2f'|format(collector.totalTime * 1000) }} <span class="unit">ms</span>
</span>
</div>
<div class="detailed-metrics">
<div>
<div class="sf-toolbar-info-piece">
<b>Serialize</b>
<span class="sf-toolbar-status">{{ collector.data.serialize|length }}</span>
</div>
<div class="sf-toolbar-info-piece">
<b>Deserialize</b>
<span class="sf-toolbar-status">{{ collector.data.deserialize|length }}</span>
</div>
</div>
<div>
<div class="sf-toolbar-info-piece">
<b>Encode</b>
<span class="sf-toolbar-status">{{ collector.data.encode|length }}</span>
</div>
<div class="sf-toolbar-info-piece">
<b>Decode</b>
<span class="sf-toolbar-status">{{ collector.data.decode|length }}</span>
</div>
</div>
<div>
<div class="sf-toolbar-info-piece">
<b>Normalize</b>
<span class="sf-toolbar-status">{{ collector.data.normalize|length }}</span>
</div>
<div class="sf-toolbar-info-piece">
<b>Denormalize</b>
<span class="sf-toolbar-status">{{ collector.data.denormalize|length }}</span>
</div>
</div>
</div>
{% endset %}
{{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { link: profiler_url }) }}
{% endif %}
{% endblock %}
{% block menu %}
<span class="label {{ not collector.handledCount ? 'disabled' }}">
<span class="icon">{{ source('@WebProfiler/Icon/serializer.svg') }}</span>
<strong>Serializer</strong>
</span>
{% endblock %}
{% block panel %}
<h2>Serializer</h2>
<div class="sf-serializer sf-reset">
{% if not collector.handledCount %}
<div class="empty empty-panel">
<p>Nothing was handled by the serializer.</p>
</div>
{% else %}
<div class="metrics">
<div class="metric">
<span class="value">{{ collector.handledCount }}</span>
<span class="label">Handled</span>
</div>
<div class="metric">
<span class="value">{{ '%.2f'|format(collector.totalTime * 1000) }} <span class="unit">ms</span></span>
<span class="label">Total time</span>
</div>
</div>
<div class="sf-tabs">
{{ _self.render_serialize_tab(collector.data, true) }}
{{ _self.render_serialize_tab(collector.data, false) }}
{{ _self.render_normalize_tab(collector.data, true) }}
{{ _self.render_normalize_tab(collector.data, false) }}
{{ _self.render_encode_tab(collector.data, true) }}
{{ _self.render_encode_tab(collector.data, false) }}
</div>
{% endif %}
</div>
{% endblock %}
{% macro render_serialize_tab(collectorData, serialize) %}
{% set data = serialize ? collectorData.serialize : collectorData.deserialize %}
{% set cellPrefix = serialize ? 'serialize' : 'deserialize' %}
<div class="tab {{ not data ? 'disabled' }}">
<h3 class="tab-title">{{ serialize ? 'serialize' : 'deserialize' }} <span class="badge">{{ data|length }}</h3>
<div class="tab-content">
{% if not data|length %}
<div class="empty">
<p>Nothing was {{ serialize ? 'serialized' : 'deserialized' }}.</p>
</div>
{% else %}
<table>
<thead>
<tr>
<th>Data</th>
<th>Context</th>
<th>Normalizer</th>
<th>Encoder</th>
<th>Time</th>
<th>Caller</th>
</tr>
</thead>
<tbody>
{% for item in data %}
<tr>
<td>{{ _self.render_data_cell(item, loop.index, cellPrefix) }}</td>
<td>{{ _self.render_context_cell(item, loop.index, cellPrefix) }}</td>
<td>{{ _self.render_normalizer_cell(item, loop.index, cellPrefix) }}</td>
<td>{{ _self.render_encoder_cell(item, loop.index, cellPrefix) }}</td>
<td>{{ _self.render_time_cell(item) }}</td>
<td>{{ _self.render_caller_cell(item, loop.index, cellPrefix) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
</div>
</div>
{% endmacro %}
{% macro render_caller_cell(item, index, method) %}
{% if item.caller is defined %}
<span class="metadata">
{% set caller = item.caller %}
{% if caller.line %}
{% set link = caller.file|file_link(caller.line) %}
{% if link %}
<a href="{{ link }}" title="{{ caller.file }}">{{ caller.name }}</a>
{% else %}
<abbr title="{{ caller.file }}">{{ caller.name }}</abbr>
{% endif %}
{% else %}
{{ caller.name }}
{% endif %}
line <a class="text-small sf-toggle" data-toggle-selector="#sf-trace-{{ method }}-{{ index }}">{{ caller.line }}</a>
</span>
<div class="sf-serializer-compact hidden" id="sf-trace-{{ method }}-{{ index }}">
<div class="trace">
{{ caller.file|file_excerpt(caller.line)|replace({
'#DD0000': 'var(--highlight-string)',
'#007700': 'var(--highlight-keyword)',
'#0000BB': 'var(--highlight-default)',
'#FF8000': 'var(--highlight-comment)'
})|raw }}
</div>
</div>
{% endif %}
{% endmacro %}
{% macro render_normalize_tab(collectorData, normalize) %}
{% set data = normalize ? collectorData.normalize : collectorData.denormalize %}
{% set cellPrefix = normalize ? 'normalize' : 'denormalize' %}
<div class="tab {{ not data ? 'disabled' }}">
<h3 class="tab-title">{{ normalize ? 'normalize' : 'denormalize' }} <span class="badge">{{ data|length }}</h3>
<div class="tab-content">
{% if not data|length %}
<div class="empty">
<p>Nothing was {{ normalize ? 'normalized' : 'denormalized' }}.</p>
</div>
{% else %}
<table>
<thead>
<tr>
<th>Data</th>
<th>Context</th>
<th>Normalizer</th>
<th>Time</th>
<th>Caller</th>
</tr>
</thead>
<tbody>
{% for item in data %}
<tr>
<td>{{ _self.render_data_cell(item, loop.index, cellPrefix) }}</td>
<td>{{ _self.render_context_cell(item, loop.index, cellPrefix) }}</td>
<td>{{ _self.render_normalizer_cell(item, loop.index, cellPrefix) }}</td>
<td>{{ _self.render_time_cell(item) }}</td>
<td>{{ _self.render_caller_cell(item, loop.index, cellPrefix) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
</div>
</div>
{% endmacro %}
{% macro render_encode_tab(collectorData, encode) %}
{% set data = encode ? collectorData.encode : collectorData.decode %}
{% set cellPrefix = encode ? 'encode' : 'decode' %}
<div class="tab {{ not data ? 'disabled' }}">
<h3 class="tab-title">{{ encode ? 'encode' : 'decode' }} <span class="badge">{{ data|length }}</h3>
<div class="tab-content">
{% if not data|length %}
<div class="empty">
<p>Nothing was {{ encode ? 'encoded' : 'decoded' }}.</p>
</div>
{% else %}
<table>
<thead>
<tr>
<th>Data</th>
<th>Context</th>
<th>Encoder</th>
<th>Time</th>
<th>Caller</th>
</tr>
</thead>
<tbody>
{% for item in data %}
<tr>
<td>{{ _self.render_data_cell(item, loop.index, cellPrefix) }}</td>
<td>{{ _self.render_context_cell(item, loop.index, cellPrefix) }}</td>
<td>{{ _self.render_encoder_cell(item, loop.index, cellPrefix) }}</td>
<td>{{ _self.render_time_cell(item) }}</td>
<td>{{ _self.render_caller_cell(item, loop.index, cellPrefix) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endif %}
</div>
</div>
{% endmacro %}
{% macro render_data_cell(item, index, method) %}
{% set data_id = 'data-' ~ method ~ '-' ~ index %}
<span class="nowrap">{{ item.dataType }}</span>
<div>
<a class="btn btn-link text-small sf-toggle" data-toggle-selector="#{{ data_id }}" data-toggle-alt-content="Hide contents">Show contents</a>
<div id="{{ data_id }}" class="context sf-toggle-content sf-toggle-hidden">
{{ profiler_dump(item.data) }}
</div>
</div>
{% endmacro %}
{% macro render_context_cell(item, index, method) %}
{% set context_id = 'context-' ~ method ~ '-' ~ index %}
{% if item.type %}
<span class="nowrap">Type: {{ item.type }}</span>
<div class="nowrap">Format: {{ item.format ? item.format : 'none' }}</div>
{% else %}
<span class="nowrap">Format: {{ item.format ? item.format : 'none' }}</span>
{% endif %}
<div>
<a class="btn btn-link text-small sf-toggle" data-toggle-selector="#{{ context_id }}" data-toggle-alt-content="Hide context">Show context</a>
<div id="{{ context_id }}" class="context sf-toggle-content sf-toggle-hidden">
{{ profiler_dump(item.context) }}
</div>
</div>
{% endmacro %}
{% macro render_normalizer_cell(item, index, method) %}
{% set nested_normalizers_id = 'nested-normalizers-' ~ method ~ '-' ~ index %}
{% if item.normalizer is defined %}
<span class="nowrap"><a href="{{ item.normalizer.file|file_link(item.normalizer.line) }}" title="{{ item.normalizer.file }}">{{ item.normalizer.class }}</a> ({{ '%.2f'|format(item.normalizer.time * 1000) }} ms)</span>
{% endif %}
{% if item.normalization|length > 1 %}
<div>
<a class="btn btn-link text-small sf-toggle" data-toggle-selector="#{{ nested_normalizers_id }}" data-toggle-alt-content="Hide nested normalizers">Show nested normalizers</a>
<div id="{{ nested_normalizers_id }}" class="context sf-toggle-content sf-toggle-hidden">
<ul class="text-small" style="line-height:80%;margin-top:10px">
{% for normalizer in item.normalization %}
<li><span class="nowrap">x{{ normalizer.calls }} <a href="{{ normalizer.file|file_link(normalizer.line) }}" title="{{ normalizer.file }}">{{ normalizer.class }}</a> ({{ '%.2f'|format(normalizer.time * 1000) }} ms)</span></li>
{% endfor %}
</ul>
</div>
</div>
{% endif %}
{% endmacro %}
{% macro render_encoder_cell(item, index, method) %}
{% set nested_encoders_id = 'nested-encoders-' ~ method ~ '-' ~ index %}
{% if item.encoder is defined %}
<span class="nowrap"><a href="{{ item.encoder.file|file_link(item.encoder.line) }}" title="{{ item.encoder.file }}">{{ item.encoder.class }}</a> ({{ '%.2f'|format(item.encoder.time * 1000) }} ms)</span>
{% endif %}
{% if item.encoding|length > 1 %}
<div>
<a class="btn btn-link text-small sf-toggle" data-toggle-selector="#{{ nested_encoders_id }}" data-toggle-alt-content="Hide nested encoders">Show nested encoders</a>
<div id="{{ nested_encoders_id }}" class="context sf-toggle-content sf-toggle-hidden">
<ul class="text-small" style="line-height:80%;margin-top:10px">
{% for encoder in item.encoding %}
<li><span class="nowrap">x{{ encoder.calls }} <a href="{{ encoder.file|file_link(encoder.line) }}" title="{{ encoder.file }}">{{ encoder.class }}</a> ({{ '%.2f'|format(encoder.time * 1000) }} ms)</span></li>
{% endfor %}
</ul>
</div>
</div>
{% endif %}
{% endmacro %}
{% macro render_time_cell(item) %}
<span class="nowrap">{{ '%.2f'|format(item.time * 1000) }} ms</span>
{% endmacro %}

View File

@@ -23,7 +23,7 @@
margin: 1em 0;
width: 100%;
background-color: var(--table-background);
border: 1px solid var(--table-border);
border: 1px solid var(--table-border-color);
}
/* Typography */
@@ -44,7 +44,7 @@
.timeline-graph .timeline-subrequest,
.timeline-graph .timeline-border {
fill: none;
stroke: var(--table-border);
stroke: var(--table-border-color);
stroke-width: 1px;
}
@@ -54,7 +54,10 @@
}
.timeline-subrequest-pattern {
fill: var(--table-border);
fill: var(--gray-200);
}
.theme-dark .timeline-subrequest-pattern {
fill: var(--gray-600);
}
/* Timeline periods */

View File

@@ -1,6 +1,42 @@
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
{% import _self as helper %}
{% block head %}
{{ parent() }}
<style>
#timeline-control {
background: var(--table-background);
box-shadow: var(--shadow);
margin: 1em 0;
padding: 10px;
}
#timeline-control label {
font-weight: bold;
margin-right: 1em;
}
#timeline-control input {
background: var(--metric-value-background);
font-size: 16px;
padding: 4px;
text-align: right;
width: 5em;
}
#timeline-control .help {
margin-left: 1em;
}
.sf-profiler-timeline .legends {
font-size: 12px;
line-height: 1.5em;
}
.sf-profiler-timeline .legends button {
color: var(--color-text);
}
.sf-profiler-timeline + p.help {
margin-top: 0;
}
</style>
{% endblock %}
{% block toolbar %}
{% set has_time_events = collector.events|length > 0 %}
@@ -9,7 +45,7 @@
{% set status_color = has_time_events and collector.duration > 1000 ? 'yellow' %}
{% set icon %}
{{ include('@WebProfiler/Icon/time.svg') }}
{{ source('@WebProfiler/Icon/time.svg') }}
<span class="sf-toolbar-value">{{ total_time }}</span>
<span class="sf-toolbar-label">ms</span>
{% endset %}
@@ -30,7 +66,7 @@
{% block menu %}
<span class="label">
<span class="icon">{{ include('@WebProfiler/Icon/time.svg') }}</span>
<span class="icon">{{ source('@WebProfiler/Icon/time.svg') }}</span>
<strong>Performance</strong>
</span>
{% endblock %}
@@ -40,17 +76,21 @@
<h2>Performance metrics</h2>
<div class="metrics">
<div class="metric">
<span class="value">{{ '%.0f'|format(collector.duration) }} <span class="unit">ms</span></span>
<span class="label">Total execution time</span>
</div>
<div class="metric-group">
<div class="metric">
<span class="value">{{ '%.0f'|format(collector.duration) }} <span class="unit">ms</span></span>
<span class="label">Total execution time</span>
</div>
<div class="metric">
<span class="value">{{ '%.0f'|format(collector.inittime) }} <span class="unit">ms</span></span>
<span class="label">Symfony initialization</span>
<div class="metric">
<span class="value">{{ '%.0f'|format(collector.inittime) }} <span class="unit">ms</span></span>
<span class="label">Symfony initialization</span>
</div>
</div>
{% if profile.collectors.memory %}
<div class="metric-divider"></div>
<div class="metric">
<span class="value">{{ '%.2f'|format(profile.collectors.memory.memory / 1024 / 1024) }} <span class="unit">MiB</span></span>
<span class="label">Peak memory usage</span>
@@ -60,23 +100,20 @@
{% if profile.children|length > 0 %}
<div class="metric-divider"></div>
<div class="metric">
<span class="value">{{ profile.children|length }}</span>
<span class="label">Sub-Request{{ profile.children|length > 1 ? 's' }}</span>
</div>
<div class="metric-group">
<div class="metric">
<span class="value">{{ profile.children|length }}</span>
<span class="label">Sub-{{ profile_type|title }}{{ profile.children|length > 1 ? 's' }}</span>
</div>
{% if has_time_events %}
{% set subrequests_time = 0 %}
{% for child in profile.children %}
{% set subrequests_time = subrequests_time + child.getcollector('time').events.__section__.duration %}
{% endfor %}
{% else %}
{% set subrequests_time = 'n/a' %}
{% endif %}
{% set subrequests_time = has_time_events
? profile.children|reduce((total, child) => total + child.getcollector('time').events.__section__.duration, 0)
: 'n/a' %}
<div class="metric">
<span class="value">{{ subrequests_time }} <span class="unit">ms</span></span>
<span class="label">Sub-Request{{ profile.children|length > 1 ? 's' }} time</span>
<div class="metric">
<span class="value">{{ subrequests_time }} <span class="unit">ms</span></span>
<span class="label">Sub-{{ profile_type|title }}{{ profile.children|length > 1 ? 's' }} time</span>
</div>
</div>
{% endif %}
</div>
@@ -84,7 +121,7 @@
<h2>Execution timeline</h2>
{% if not collector.isStopwatchInstalled() %}
<div class="empty">
<div class="empty empty-panel">
<p>The Stopwatch component is not installed. If you want to see timing events, run: <code>composer require symfony/stopwatch</code>.</p>
</div>
{% elseif collector.events is empty %}
@@ -106,24 +143,24 @@
{% if profile.parent %}
<h3 class="dump-inline">
Sub-Request {{ profiler_dump(profile.getcollector('request').requestattributes.get('_controller')) }}
Sub-{{ profile_type|title }} {{ profiler_dump(profile.getcollector('request').requestattributes.get('_controller')) }}
<small>
{{ collector.events.__section__.duration }} ms
<a class="newline" href="{{ path('_profiler', { token: profile.parent.token, panel: 'time' }) }}">Return to parent request</a>
<a class="newline" href="{{ path('_profiler', { token: profile.parent.token, panel: 'time' }) }}">Return to parent {{ profile_type }}</a>
</small>
</h3>
{% elseif profile.children|length > 0 %}
<h3>
Main Request <small>{{ collector.events.__section__.duration }} ms</small>
Main {{ profile_type|title }} <small>{{ collector.events.__section__.duration }} ms</small>
</h3>
{% endif %}
{{ helper.display_timeline(token, collector.events, collector.events.__section__.origin) }}
{{ _self.display_timeline(token, collector.events, collector.events.__section__.origin) }}
{% if profile.children|length %}
<p class="help">Note: sections with a striped background correspond to sub-requests.</p>
<p class="help">Note: sections with a striped background correspond to sub-{{ profile_type }}s.</p>
<h3>Sub-requests <small>({{ profile.children|length }})</small></h3>
<h3>Sub-{{ profile_type }}s <small>({{ profile.children|length }})</small></h3>
{% for child in profile.children %}
{% set events = child.getcollector('time').events %}
@@ -132,7 +169,7 @@
<small>{{ events.__section__.duration }} ms</small>
</h4>
{{ helper.display_timeline(child.token, events, collector.events.__section__.origin) }}
{{ _self.display_timeline(child.token, events, collector.events.__section__.origin) }}
{% endfor %}
{% endif %}
@@ -143,22 +180,21 @@
</pattern>
</defs>
</svg>
<style type="text/css">
{% include '@WebProfiler/Collector/time.css.twig' %}
<style>
{{ include('@WebProfiler/Collector/time.css.twig') }}
</style>
<script>
{% include '@WebProfiler/Collector/time.js' %}
{{ source('@WebProfiler/Collector/time.js') }}
</script>
{% endblock %}
{% macro dump_request_data(token, events, origin) %}
{% autoescape 'js' %}
{% from _self import dump_events %}
{
id: "{{ token }}",
left: {{ "%F"|format(events.__section__.origin - origin) }},
end: "{{ '%F'|format(events.__section__.endtime) }}",
events: [ {{ dump_events(events) }} ],
events: [ {{ _self.dump_events(events) }} ],
}
{% endautoescape %}
{% endmacro %}
@@ -193,7 +229,6 @@
{% endmacro %}
{% macro display_timeline(token, events, origin) %}
{% import _self as helper %}
<div class="sf-profiler-timeline">
<div id="legend-{{ token }}" class="legends"></div>
<svg id="timeline-{{ token }}" class="timeline-graph"></svg>
@@ -206,7 +241,7 @@
new SvgRenderer(document.getElementById('timeline-{{ token }}')),
new Legend(document.getElementById('legend-{{ token }}'), theme),
document.getElementById('threshold'),
{{ helper.dump_request_data(token, events, origin) }}
{{ _self.dump_request_data(token, events, origin) }}
);
});
{% endautoescape %}</script>

View File

@@ -406,23 +406,23 @@ class SvgRenderer {
class Theme {
constructor(element) {
this.reservedCategoryColors = {
'default': '#777',
'section': '#999',
'event_listener': '#00b8f5',
'template': '#66cc00',
'doctrine': '#ff6633',
'messenger_middleware': '#bdb81e',
'controller.argument_value_resolver': '#8c5de6',
'http_client': '#ffa333',
'default': '#737373',
'section': '#a3a3a3',
'event_listener': '#54aeff',
'template': '#4ac26b',
'doctrine': '#fd8c73',
'messenger_middleware': '#ff8182',
'controller.argument_value_resolver': '#c297ff',
'http_client': '#d4a72c',
};
this.customCategoryColors = [
'#dbab09', // dark yellow
'#ea4aaa', // pink
'#964b00', // brown
'#22863a', // dark green
'#0366d6', // dark blue
'#17a2b8', // teal
'#d4a72c', // dark yellow
'#ffaba8', // light red
'#e6af05', // yellow
'#6fdd8b', // light green
'#76e3ea', // cyan
'#a475f9', // light purple
];
this.getCategoryColor = this.getCategoryColor.bind(this);

View File

@@ -1,11 +1,9 @@
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
{% import _self as helper %}
{% block toolbar %}
{% if collector.messages|length %}
{% set icon %}
{{ include('@WebProfiler/Icon/translation.svg') }}
{{ source('@WebProfiler/Icon/translation.svg') }}
{% set status_color = collector.countMissings ? 'red' : collector.countFallbacks ? 'yellow' %}
{% set error_count = collector.countMissings + collector.countFallbacks %}
<span class="sf-toolbar-value">{{ error_count ?: collector.countDefines }}</span>
@@ -44,7 +42,7 @@
{% block menu %}
<span class="label label-status-{{ collector.countMissings ? 'error' : collector.countFallbacks ? 'warning' }} {{ collector.messages is empty ? 'disabled' }}">
<span class="icon">{{ include('@WebProfiler/Icon/translation.svg') }}</span>
<span class="icon">{{ source('@WebProfiler/Icon/translation.svg') }}</span>
<strong>Translation</strong>
{% if collector.countMissings or collector.countFallbacks %}
{% set error_count = collector.countMissings + collector.countFallbacks %}
@@ -72,7 +70,7 @@
<h2>Messages</h2>
{% if collector.messages is empty %}
<div class="empty">
<div class="empty empty-panel">
<p>No translations have been called.</p>
</div>
{% else %}
@@ -105,7 +103,7 @@
</div>
{% else %}
{% block defined_messages %}
{{ helper.render_table(messages_defined) }}
{{ _self.render_table(messages_defined) }}
{% endblock %}
{% endif %}
</div>
@@ -126,7 +124,7 @@
</div>
{% else %}
{% block fallback_messages %}
{{ helper.render_table(messages_fallback, true) }}
{{ _self.render_table(messages_fallback, true) }}
{% endblock %}
{% endif %}
</div>
@@ -148,29 +146,27 @@
</div>
{% else %}
{% block missing_messages %}
{{ helper.render_table(messages_missing) }}
{{ _self.render_table(messages_missing) }}
{% endblock %}
{% endif %}
</div>
</div>
</div>
<script>Sfjs.createFilters();</script>
{% endblock messages %}
{% endif %}
{% endblock %}
{% macro render_table(messages, is_fallback) %}
<table data-filters>
<table>
<thead>
<tr>
<th data-filter="locale">Locale</th>
<th>Locale</th>
{% if is_fallback %}
<th>Fallback locale</th>
{% endif %}
<th data-filter="domain">Domain</th>
<th>Domain</th>
<th>Times used</th>
<th>Message ID</th>
<th>Message Preview</th>
@@ -178,7 +174,7 @@
</thead>
<tbody>
{% for message in messages %}
<tr data-filter-locale="{{ message.locale }}" data-filter-domain="{{ message.domain }}">
<tr>
<td class="font-normal text-small nowrap">{{ message.locale }}</td>
{% if is_fallback %}
<td class="font-normal text-small nowrap">{{ message.fallbackLocale|default('-') }}</td>
@@ -186,7 +182,7 @@
<td class="font-normal text-small text-bold nowrap">{{ message.domain }}</td>
<td class="font-normal text-small nowrap">{{ message.count }}</td>
<td>
<span class="nowrap">{{ message.id }}</span>
<span class="{{ message.id|length < 64 ? 'nowrap' }}">{{ message.id }}</span>
{% if message.transChoiceNumber is not null %}
<small class="newline">(pluralization is used)</small>

View File

@@ -1,14 +1,66 @@
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
{% block head %}
{{ parent() }}
<style>
#twig-dump pre {
font-size: var(--font-size-monospace);
line-height: 1.7;
background-color: var(--page-background);
border: var(--border);
border-radius: 6px;
padding: 15px;
box-shadow: 0 0 1px rgba(128, 128, 128, .2);
}
#twig-dump span {
border-radius: 2px;
padding: 1px 2px;
}
#twig-dump .status-error { background: transparent; color: var(--color-error); }
#twig-dump .status-warning { background: rgba(240, 181, 24, 0.3); }
#twig-dump .status-success { background: rgba(100, 189, 99, 0.2); }
#twig-dump .status-info { background: var(--info-background); }
.theme-dark #twig-dump .status-warning { color: var(--yellow-200); }
.theme-dark #twig-dump .status-success { color: var(--green-200); }
#twig-table tbody td {
position: relative;
}
#twig-table tbody td div {
margin: 0;
}
#twig-table .template-file-path {
color: var(--color-muted);
display: block;
}
</style>
{% endblock %}
{% block toolbar %}
{% set time = collector.templatecount ? '%0.0f'|format(collector.time) : 'n/a' %}
{% set icon %}
{{ include('@WebProfiler/Icon/twig.svg') }}
{{ source('@WebProfiler/Icon/twig.svg') }}
<span class="sf-toolbar-value">{{ time }}</span>
<span class="sf-toolbar-label">ms</span>
{% endset %}
{% set text %}
{% set template = collector.templates|keys|first %}
{% set file = collector.templatePaths[template]|default(false) %}
{% set link = file ? file|file_link(1) : false %}
<div class="sf-toolbar-info-piece">
<b>Entry View</b>
<span>
{% if link %}
<a href="{{ link }}" title="{{ file }}">
{{ template }}
</a>
{% else %}
{{ template }}
{% endif %}
</span>
</div>
<div class="sf-toolbar-info-piece">
<b>Render Time</b>
<span>{{ time }} ms</span>
@@ -32,7 +84,7 @@
{% block menu %}
<span class="label {{ 0 == collector.templateCount ? 'disabled' }}">
<span class="icon">{{ include('@WebProfiler/Icon/twig.svg') }}</span>
<span class="icon">{{ source('@WebProfiler/Icon/twig.svg') }}</span>
<strong>Twig</strong>
</span>
{% endblock %}
@@ -41,8 +93,8 @@
{% if collector.templatecount == 0 %}
<h2>Twig</h2>
<div class="empty">
<p>No Twig templates were rendered for this request.</p>
<div class="empty empty-panel">
<p>No Twig templates were rendered.</p>
</div>
{% else %}
<h2>Twig Metrics</h2>
@@ -53,19 +105,23 @@
<span class="label">Render time</span>
</div>
<div class="metric">
<span class="value">{{ collector.templatecount }}</span>
<span class="label">Template calls</span>
</div>
<div class="metric-divider"></div>
<div class="metric">
<span class="value">{{ collector.blockcount }}</span>
<span class="label">Block calls</span>
</div>
<div class="metric-group">
<div class="metric">
<span class="value">{{ collector.templatecount }}</span>
<span class="label">Template calls</span>
</div>
<div class="metric">
<span class="value">{{ collector.macrocount }}</span>
<span class="label">Macro calls</span>
<div class="metric">
<span class="value">{{ collector.blockcount }}</span>
<span class="label">Block calls</span>
</div>
<div class="metric">
<span class="value">{{ collector.macrocount }}</span>
<span class="label">Macro calls</span>
</div>
</div>
</div>
@@ -85,17 +141,14 @@
<tbody>
{% for template, count in collector.templates %}
<tr>
{%- set file = collector.templatePaths[template]|default(false) -%}
{%- set link = file ? file|file_link(1) : false -%}
<td>
<span class="sf-icon icon-twig">{{ include('@WebProfiler/Icon/twig.svg') }}</span>
{% set file = collector.templatePaths[template]|default(false) %}
{% set link = file ? file|file_link(1) : false %}
<td class="font-normal">
{% if link %}
<a href="{{ link }}" title="{{ file }}">{{ template }}</a>
<div>
<a class="text-muted" href="{{ link }}" title="{{ file }}">
{{ file|file_relative|default(file) }}
</a>
</div>
<a href="{{ link }}" title="{{ file }}" class="stretched-link">
{{ template }}
<span class="template-file-path">{{ file|file_relative|default(file) }}</span>
</a>
{% else %}
{{ template }}
{% endif %}

View File

@@ -1,10 +1,39 @@
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
{% block head %}
{{ parent() }}
<style>
#collector-content .sf-validator {
margin-bottom: 2em;
}
#collector-content .sf-validator .sf-validator-context,
#collector-content .sf-validator .trace {
border: var(--border);
background: var(--base-0);
padding: 10px;
margin: 0.5em 0;
overflow: auto;
}
#collector-content .sf-validator .trace {
font-size: 12px;
}
#collector-content .sf-validator .trace li {
margin-bottom: 0;
padding: 0;
}
#collector-content .sf-validator .trace li.selected {
background: var(--highlight-selected-line);
}
</style>
{% endblock %}
{% block toolbar %}
{% if collector.violationsCount > 0 or collector.calls|length %}
{% set status_color = collector.violationsCount ? 'red' %}
{% set icon %}
{{ include('@WebProfiler/Icon/validator.svg') }}
{{ source('@WebProfiler/Icon/validator.svg') }}
<span class="sf-toolbar-value">
{{ collector.violationsCount ?: collector.calls|length }}
</span>
@@ -27,7 +56,7 @@
{% block menu %}
<span class="label {{- collector.violationsCount ? ' label-status-error' }} {{ collector.calls is empty ? 'disabled' }}">
<span class="icon">{{ include('@WebProfiler/Icon/validator.svg') }}</span>
<span class="icon">{{ source('@WebProfiler/Icon/validator.svg') }}</span>
<strong>Validator</strong>
{% if collector.violationsCount > 0 %}
<span class="count">
@@ -54,7 +83,7 @@
{% else %}
{{ caller.name }}
{% endif %}
line <a class="text-small sf-toggle" data-toggle-selector="#sf-trace-{{ loop.index0 }}">{{ caller.line }}</a> (<a class="text-small sf-toggle" data-toggle-selector="#sf-context-{{ loop.index0 }}">context</a>):
line <button type="button" class="btn-link text-small sf-toggle" data-toggle-selector="#sf-trace-{{ loop.index0 }}">{{ caller.line }}</button> (<button type="button" class="btn-link text-small sf-toggle" data-toggle-selector="#sf-context-{{ loop.index0 }}">context</button>):
</span>
<div class="sf-validator-compact hidden" id="sf-trace-{{ loop.index0 }}">
@@ -96,8 +125,8 @@
{% endif %}
</div>
{% else %}
<div class="empty">
<p>No calls to the validator were collected during this request.</p>
<div class="empty empty-panel">
<p>No calls to the validator were collected.</p>
</div>
{% endfor %}
{% endblock %}

View File

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

View File

@@ -0,0 +1,26 @@
Icons are from "Tabler Icons" (https://github.com/tabler/tabler-icons), a set of
free MIT-licensed high-quality SVG icons.
-----
MIT License
Copyright (c) 2020-2022 Paweł Kuna
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

View File

@@ -1 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#AAA" d="M9.8 18L6 22.4c-.3.3-.8.4-1.1 0L1 18c-.4-.5-.1-1 .5-1H3V6.4C3 3.8 5.5 2 8.2 2h3.9c1.1 0 2 .9 2 2s-.9 2-2 2H8.2C7.7 6 7 6 7 6.4V17h2.2c.6 0 1 .5.6 1zM23 6l-3.8-4.5a.8.8 0 0 0-1.1 0L14.2 6c-.4.5-.1 1 .5 1H17v10.6c0 .4-.7.4-1.2.4h-3.9c-1.1 0-2 .9-2 2s.9 2 2 2h3.9c2.6 0 5.2-1.8 5.2-4.4V7h1.5c.6 0 .9-.5.5-1z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" data-icon-name="icon-tabler-arrows-down-up" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<line x1="17" y1="3" x2="17" y2="21"></line>
<path d="M10 18l-3 3l-3 -3"></path>
<line x1="7" y1="21" x2="7" y2="3"></line>
<path d="M20 6l-3 -3l-3 3"></path>
</svg>

Before

Width:  |  Height:  |  Size: 418 B

After

Width:  |  Height:  |  Size: 472 B

View File

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" data-icon-name="icon-tabler-alert-circle" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<circle cx="12" cy="12" r="9"></circle>
<line x1="12" y1="8" x2="12" y2="12"></line>
<line x1="12" y1="16" x2="12.01" y2="16"></line>
</svg>

After

Width:  |  Height:  |  Size: 441 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" data-icon-name="icon-tabler-paperclip" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M15 7l-6.5 6.5a1.5 1.5 0 0 0 3 3l6.5 -6.5a3 3 0 0 0 -6 -6l-6.5 6.5a4.5 4.5 0 0 0 9 9l6.5 -6.5"></path>
</svg>

After

Width:  |  Height:  |  Size: 408 B

View File

@@ -1 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#AAA" d="M2.3 6l9-4.6a1.5 1.5 0 0 1 1.4 0l9 4.7a1.5 1.5 0 0 1 0 2.6l-9 4.7a1.5 1.5 0 0 1-1.4 0l-9-4.7a1.5 1.5 0 0 1 0-2.6zm18.3 5L12 15.4 3.4 11a1.4 1.4 0 0 0-1.2 2.4l9.2 4.8a1.4 1.4 0 0 0 1.2 0l9.2-4.8a1.4 1.4 0 0 0-1.3-2.4zm0 4.5L12 19.9l-8.6-4.4a1.4 1.4 0 0 0-1.2 2.4l9.2 4.7a1.4 1.4 0 0 0 1.2 0l9.2-4.7a1.4 1.4 0 0 0-1.3-2.5z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" data-icon-name="icon-tabler-stack-2" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" role="img">
<title>Cache</title>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<polyline points="12 4 4 8 12 12 20 8 12 4"></polyline>
<polyline points="4 12 12 16 20 12"></polyline>
<polyline points="4 16 12 20 20 16"></polyline>
</svg>

Before

Width:  |  Height:  |  Size: 434 B

After

Width:  |  Height:  |  Size: 490 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" data-icon-name="icon-tabler-chevron-down" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M6 9l6 6l6 -6"></path>
</svg>

After

Width:  |  Height:  |  Size: 331 B

View File

@@ -1 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#AAA" d="M21.1 18.3c.8.8.8 2 0 2.8-.4.4-.9.6-1.4.6s-1-.2-1.4-.6L12 14.8l-6.3 6.3c-.4.4-.9.6-1.4.6s-1-.2-1.4-.6a2 2 0 0 1 0-2.8L9.2 12 2.9 5.7a2 2 0 0 1 0-2.8 2 2 0 0 1 2.8 0L12 9.2l6.3-6.3a2 2 0 0 1 2.8 0c.8.8.8 2 0 2.8L14.8 12l6.3 6.3z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" data-icon-name="icon-tabler-x" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>

Before

Width:  |  Height:  |  Size: 341 B

After

Width:  |  Height:  |  Size: 380 B

View File

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" data-icon-name="icon-tabler-terminal-2" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M8 9l3 3l-3 3"></path>
<line x1="13" y1="15" x2="16" y2="15"></line>
<rect x="3" y="4" width="18" height="16" rx="2"></rect>
</svg>

After

Width:  |  Height:  |  Size: 437 B

View File

@@ -1 +1,13 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#AAA" d="M11 5.1C11 3.4 9.6 2 7.9 2H5.1A3.1 3.1 0 0 0 2 5.1V18c0 1.6 1.4 3 3.1 3H8c1.7 0 3.1-1.4 3.1-3.1V5.1zM5.2 4h2.7C8.4 4 9 4.8 9 5.3V11H4V5.3C4 4.8 4.6 4 5.2 4zM22 5.1C22 3.4 20.6 2 18.9 2H16c-1.6 0-3 1.4-3 3.1V18c0 1.7 1.4 3.1 3.1 3.1H19c1.7 0 3.1-1.4 3.1-3.1V5.1zM16 4h2.8c.6 0 1.2.8 1.2 1.3V8h-5V5.3c0-.5.5-1.3 1-1.3z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" data-icon-name="icon-tabler-adjustments" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" role="img">
<title>Config</title>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<circle cx="6" cy="10" r="2"></circle>
<line x1="6" y1="4" x2="6" y2="8"></line>
<line x1="6" y1="12" x2="6" y2="20"></line>
<circle cx="12" cy="16" r="2"></circle>
<line x1="12" y1="4" x2="12" y2="14"></line>
<line x1="12" y1="18" x2="12" y2="20"></line>
<circle cx="18" cy="7" r="2"></circle>
<line x1="18" y1="4" x2="18" y2="5"></line>
<line x1="18" y1="9" x2="18" y2="20"></line>
</svg>

Before

Width:  |  Height:  |  Size: 430 B

After

Width:  |  Height:  |  Size: 751 B

View File

@@ -0,0 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" data-icon-name="icon-tabler-download" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M4 17v2a2 2 0 0 0 2 2h12a2 2 0 0 0 2 -2v-2"></path>
<polyline points="7 11 12 16 17 11"></polyline>
<line x1="12" y1="4" x2="12" y2="16"></line>
</svg>

After

Width:  |  Height:  |  Size: 457 B

View File

@@ -1 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#AAA" d="M19.2 20.8c.4.7.1 1.6-.6 2l-.7.2c-.5 0-1-.3-1.3-.8l-3.7-6.7-1 .1-.9-.1-3.7 6.7c-.4.5-.9.8-1.5.8l-.7-.2c-.7-.4-1-1.3-.6-2l3.8-6.9c-.5-.7-.9-1.6-.9-2.6.1-2.4 2-4.3 4.4-4.3s4.3 1.9 4.3 4.3c0 .9-.3 1.8-.8 2.5l3.9 7zM5.2 11c.6 0 1-.3 1-.8 0-2.1 1.6-3.8 3.7-4.1.5-.1.9-.6.8-1.2-.1-.5-.6-.9-1.1-.9-3.1.5-5.3 3-5.3 6.1-.1.6.4.9.9.9zm8.4-5c2.1.3 3.7 2.1 3.8 4.2 0 .5.5.8 1 .8.6 0 1-.3 1-.8 0-3.1-2.4-5.6-5.5-6.1-.5-.1-1.1.3-1.1.8-.2.6.2 1 .8 1.1zM9 3c.5-.1.9-.6.8-1.1-.1-.6-.6-.9-1.1-.8a9 9 0 0 0-7.4 8.7c0 .6.4 1.2 1 1.2.5 0 1-.6 1-1.2C3.3 6.5 5.7 3.5 9 3zm5.7-2c-.5-.1-1.1.3-1.1.9s.3 1.1.8 1.1c3.3.5 5.8 3.4 5.8 6.8 0 .5.5 1.2 1 1.2.6 0 1-.7 1-1.2A9 9 0 0 0 14.7 1z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" data-icon-name="icon-tabler-access-point" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<line x1="12" y1="12" x2="12" y2="12.01"></line>
<path d="M14.828 9.172a4 4 0 0 1 0 5.656"></path>
<path d="M17.657 6.343a8 8 0 0 1 0 11.314"></path>
<path d="M9.168 14.828a4 4 0 0 1 0 -5.656"></path>
<path d="M6.337 17.657a8 8 0 0 1 0 -11.314"></path>
</svg>

Before

Width:  |  Height:  |  Size: 772 B

After

Width:  |  Height:  |  Size: 568 B

View File

@@ -1 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#AAA" d="M23.5 9.5c0-.2-1.2.2-1.6.2l.4-.8C23 7.4 22 6.6 21 7.5c-.4.4 0 1.1 0 1.8v.3l-.6-.3c-.5-.8-1.1-.2-1.1 0 0 .3.7.9 1.1.9h.2v.5c0 .7-.8 1.1-1.7 1.2V9.1c0-4.3-3.3-6.4-6.9-6.4-3.5 0-6.9 2-6.9 6.4v2.8c-.9-.2-1.8-.5-1.8-1.2v-.2h.2c.5 0 1.1-.2 1.1-.4.2-1.4-.6-.5-1.1-.5h-.3l.1-.4c0-.5 1.2-1.7-.8-1.9-.4 0-.5.9-.4 1.3l.4 1.2c-.1-.2-.3-.2-.5-.3-.2-.2-1.6-1.9-1.9 0-.1 1.1 1 1.2 1.9 1l.3-.1-.2 1.2c0 1.3 1.5 1.6 2.9 1.7v5.2c0 1.6.5 2.8 2.2 2.8 1.8 0 2.4-1.3 2.4-2.9 0 1.6.6 2.9 2.3 2.9s2.3-2.2 2.3-2.8c0 1.7.7 2.8 2.4 2.8s2.2-1.2 2.2-2.9v-5.1c1.4-.1 2.9-.4 2.9-1.7l-.1-1c.4.5 1.1.8 1.7.5 1.2-.7.2-1.4.2-1.6zM6.8 8.4c0-1.5 1-2.5 2.3-2.5 1.3 0 2.3 1.1 2.3 2.5s-1 2.6-2.2 2.6c.6 0 1.1-.5 1.1-1.2 0-.6-.5-1.2-1.2-1.2-.6 0-1.2.5-1.2 1.2 0 .6.5 1.2 1.2 1.2-1.3 0-2.3-1.1-2.3-2.6zm5.1 7.5c-2.9-.1-3.1-1.6-3.1-2.5 0-.9 1.7-.3 3.2-.3 1.5 0 3.1-.7 3.1.2 0 1-.8 2.7-3.2 2.6zM15 11c.6-.1 1-.6 1-1.2s-.5-1.2-1.2-1.2c-.6 0-1.2.5-1.2 1.2 0 .6.5 1.2 1.1 1.2-1.3 0-2.3-1.2-2.3-2.6 0-1.5 1-2.5 2.3-2.5C16 5.9 17 7 17 8.4c.1 1.4-.8 2.5-2 2.6z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" data-icon-name="icon-tabler-ghost" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" role="img">
<title>Exception</title>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M5 11a7 7 0 0 1 14 0v7a1.78 1.78 0 0 1 -3.1 1.4a1.65 1.65 0 0 0 -2.6 0a1.65 1.65 0 0 1 -2.6 0a1.65 1.65 0 0 0 -2.6 0a1.78 1.78 0 0 1 -3.1 -1.4v-7"></path>
<line x1="10" y1="10" x2="10.01" y2="10"></line>
<line x1="14" y1="10" x2="14.01" y2="10"></line>
<path d="M10 14a3.5 3.5 0 0 0 4 0"></path>
</svg>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 649 B

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" data-icon-name="icon-tabler-file" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M14 3v4a1 1 0 0 0 1 1h4"></path>
<path d="M17 21h-10a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2z"></path>
</svg>

After

Width:  |  Height:  |  Size: 426 B

View File

@@ -1 +1,4 @@
<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>
<svg xmlns="http://www.w3.org/2000/svg" data-icon-name="icon-tabler-filter" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M5.5 5h13a1 1 0 0 1 .5 1.5l-5 5.5l0 7l-4 -3l0 -4l-5 -5.5a1 1 0 0 1 .5 -1.5"></path>
</svg>

Before

Width:  |  Height:  |  Size: 207 B

After

Width:  |  Height:  |  Size: 386 B

View File

@@ -1 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#AAA" d="M20.5 4H18V2.5c0-.8-.7-1.5-1.5-1.5h-9C6.7 1 6 1.7 6 2.5V4H3.5C2.7 4 2 4.7 2 5.5v16c0 .8.7 1.5 1.5 1.5h17c.8 0 1.5-.7 1.5-1.5v-16c0-.8-.7-1.5-1.5-1.5zM9 4h6v1H9V4zm10 16H5V7h1.1c.2.6.8 1 1.4 1h9c.7 0 1.2-.4 1.4-1H19v13zm-2-9c0 .6-.4 1-1 1H8c-.6 0-1-.4-1-1s.4-1 1-1h8c.6 0 1 .4 1 1zm0 3c0 .6-.4 1-1 1H8c-.6 0-1-.4-1-1s.4-1 1-1h8c.6 0 1 .4 1 1zm-4 3c0 .6-.4 1-1 1H8c-.6 0-1-.4-1-1s.4-1 1-1h4c.6 0 1 .4 1 1z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" data-icon-name="icon-tabler-forms" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" role="img">
<title>Cache</title>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M12 3a3 3 0 0 0 -3 3v12a3 3 0 0 0 3 3"></path>
<path d="M6 3a3 3 0 0 1 3 3v12a3 3 0 0 1 -3 3"></path>
<path d="M13 7h7a1 1 0 0 1 1 1v8a1 1 0 0 1 -1 1h-7"></path>
<path d="M5 7h-1a1 1 0 0 0 -1 1v8a1 1 0 0 0 1 1h1"></path>
<path d="M17 12h.01"></path>
<path d="M13 12h.01"></path>
</svg>

Before

Width:  |  Height:  |  Size: 517 B

After

Width:  |  Height:  |  Size: 636 B

View File

@@ -1 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#aaa" d="M23.6 11l-6.5-6.6a1.2 1.2 0 0 0-2.1.9V9H1.4A1.8 1.8 0 0 0 0 10.8v2.6A1.6 1.6 0 0 0 1.4 15H15v3.7a1.2 1.2 0 0 0 2 1l6.7-6.8a1.3 1.3 0 0 0 0-1.8z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" data-icon-name="icon-tabler-arrow-narrow-right" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<line x1="5" y1="12" x2="19" y2="12"></line>
<line x1="15" y1="16" x2="19" y2="12"></line>
<line x1="15" y1="8" x2="19" y2="12"></line>
</svg>

Before

Width:  |  Height:  |  Size: 257 B

After

Width:  |  Height:  |  Size: 449 B

View File

@@ -1 +1,9 @@
<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>
<svg xmlns="http://www.w3.org/2000/svg" data-icon-name="icon-tabler-affiliate" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M5.931 6.936l1.275 4.249m5.607 5.609l4.251 1.275"></path>
<path d="M11.683 12.317l5.759 -5.759"></path>
<circle cx="5.5" cy="5.5" r="1.5"></circle>
<circle cx="18.5" cy="5.5" r="1.5"></circle>
<circle cx="18.5" cy="18.5" r="1.5"></circle>
<circle cx="8.5" cy="15.5" r="4.5"></circle>
</svg>

Before

Width:  |  Height:  |  Size: 771 B

After

Width:  |  Height:  |  Size: 609 B

View File

@@ -1 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#AAA" d="M21 4v13.8c0 2.7-2.5 5.2-5.2 5.2H6c-.6 0-1-.4-1-1s.4-1 1-1h9.8c1.6 0 3.2-1.7 3.2-3.2V4c0-.6.4-1 1-1s1 .4 1 1zM5.5 20A2.5 2.5 0 0 1 3 17.5v-14C3 2.1 4.1 1 5.5 1h10.1C16.9 1 18 2.1 18 3.5v14.1c0 1.4-1.1 2.5-2.5 2.5h-10zM9 11.4c0 .3.3.6.6.6h1.8c.3 0 .6-.3.6-.6V4.6c0-.3-.3-.6-.6-.6H9.6c-.3 0-.6.3-.6.6v6.8zm0 5c0 .3.3.6.6.6h1.8c.3 0 .6-.3.6-.6v-1.8c0-.3-.3-.6-.6-.6H9.6c-.3 0-.6.3-.6.6v1.8z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" data-icon-name="icon-tabler-file-alert" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" role="img">
<title>Logger</title>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M14 3v4a1 1 0 0 0 1 1h4"></path>
<path d="M17 21h-10a2 2 0 0 1 -2 -2v-14a2 2 0 0 1 2 -2h7l5 5v11a2 2 0 0 1 -2 2z"></path>
<line x1="12" y1="17" x2="12.01" y2="17"></line>
<line x1="12" y1="11" x2="12" y2="14"></line>
</svg>

Before

Width:  |  Height:  |  Size: 501 B

After

Width:  |  Height:  |  Size: 572 B

View File

@@ -1 +1,6 @@
<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>
<svg xmlns="http://www.w3.org/2000/svg" data-icon-name="icon-tabler-mail" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<rect x="3" y="5" width="18" height="14" rx="2"></rect>
<polyline points="3 7 12 13 21 7"></polyline>
</svg>

Before

Width:  |  Height:  |  Size: 643 B

After

Width:  |  Height:  |  Size: 398 B

View File

@@ -1 +1,14 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#AAA" d="M6 18.9V15h12v3.9c0 .7-.2 1.1-1 1.1H7c-.8 0-1-.4-1-1.1zM20 1c-.6 0-1 .5-1 1.1v18c0 .5-.4.9-.9.9H5.9a.9.9 0 0 1-.9-.9v-18C5 1.5 4.6 1 4 1c-.5 0-1 .5-1 1.1v18C3 21.7 4.3 23 5.9 23h12.2c1.6 0 2.9-1.3 2.9-2.9v-18c0-.6-.4-1.1-1-1.1zm-2 8H6v5h12V9z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" data-icon-name="icon-tabler-cpu" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" role="img">
<title>Memory</title>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<rect x="5" y="5" width="14" height="14" rx="1"></rect>
<path d="M9 9h6v6h-6z"></path>
<path d="M3 10h2"></path>
<path d="M3 14h2"></path>
<path d="M10 3v2"></path>
<path d="M14 3v2"></path>
<path d="M21 10h-2"></path>
<path d="M21 14h-2"></path>
<path d="M14 21v-2"></path>
<path d="M10 21v-2"></path>
</svg>

Before

Width:  |  Height:  |  Size: 356 B

After

Width:  |  Height:  |  Size: 666 B

View File

@@ -1 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#AAA" d="M2.6 17.5h18.8c.9 0 1.6.7 1.6 1.6v1.5c0 1-.7 1.6-1.6 1.6H2.6c-.9 0-1.6-.7-1.6-1.6v-1.5c0-.9.7-1.6 1.6-1.6zM1 11.2v1.6c0 .9.7 1.6 1.6 1.6h18.8c.9 0 1.6-.7 1.6-1.6v-1.6c0-.8-.7-1.6-1.6-1.6H2.6A1.6 1.6 0 0 0 1 11.2zm0-7.8v1.5a1.6 1.6 0 0 0 1.6 1.6h18.8c.9 0 1.6-.7 1.6-1.6V3.4c0-1-.7-1.6-1.6-1.6H2.6A1.6 1.6 0 0 0 1 3.4z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" data-icon-name="icon-tabler-menu-2" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" role="img">
<title>Menu</title>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<line x1="4" y1="6" x2="20" y2="6"></line>
<line x1="4" y1="12" x2="20" y2="12"></line>
<line x1="4" y1="18" x2="20" y2="18"></line>
</svg>

Before

Width:  |  Height:  |  Size: 431 B

After

Width:  |  Height:  |  Size: 469 B

View File

@@ -1 +1,8 @@
<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>
<svg xmlns="http://www.w3.org/2000/svg" data-icon-name="icon-tabler-subtask" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<line x1="6" y1="9" x2="12" y2="9"></line>
<line x1="4" y1="5" x2="8" y2="5"></line>
<path d="M6 5v11a1 1 0 0 0 1 1h5"></path>
<rect x="12" y="7" width="8" height="4" rx="1"></rect>
<rect x="12" y="15" width="8" height="4" rx="1"></rect>
</svg>

Before

Width:  |  Height:  |  Size: 989 B

After

Width:  |  Height:  |  Size: 548 B

View File

@@ -1 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 319 B

View File

@@ -1 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 12 12"><path fill="#B0413E" 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>
<svg xmlns="http://www.w3.org/2000/svg" data-icon-name="icon-tabler-x" width="24" height="24" viewBox="0 0 24 24" stroke-width="3" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<line x1="18" y1="6" x2="6" y2="18"></line>
<line x1="6" y1="6" x2="18" y2="18"></line>
</svg>

Before

Width:  |  Height:  |  Size: 322 B

After

Width:  |  Height:  |  Size: 378 B

View File

@@ -1 +1,7 @@
<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>
<svg xmlns="http://www.w3.org/2000/svg" data-icon-name="icon-tabler-bell-ringing" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M10 5a2 2 0 0 1 4 0a7 7 0 0 1 4 6v3a4 4 0 0 0 2 3h-16a4 4 0 0 0 2 -3v-3a7 7 0 0 1 4 -6"></path>
<path d="M9 17v1a3 3 0 0 0 6 0v-1"></path>
<path d="M21 6.727a11.05 11.05 0 0 0 -2.794 -3.727"></path>
<path d="M3 6.727a11.05 11.05 0 0 1 2.792 -3.727"></path>
</svg>

Before

Width:  |  Height:  |  Size: 432 B

After

Width:  |  Height:  |  Size: 577 B

View File

@@ -1 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#aaa" d="M23 7.8L14 .4a1.3 1.3 0 0 0-2 .9V4a13.6 13.6 0 0 1-2.2.6l-1.3.5c-.5.1-1 .4-1.4.6l-.7.4-.7.4a10.6 10.6 0 0 0-1.4 1A13.2 13.2 0 0 0 3 8.8a15.3 15.3 0 0 0-1.1 1.5 17.6 17.6 0 0 0-.9 1.6l-.5 1.7c-.2.5 0 1.2 0 1.7a10.2 10.2 0 0 0 0 1.5A5.7 5.7 0 0 0 1 18l.4 1.2 1 2 1 1.4 1 1c.2.2.4.1.3-.2l-.3-1.2-.3-1.6-.1-1.9v-1a3.4 3.4 0 0 1 .2-1 6.4 6.4 0 0 1 .3-.8l.4-.8.6-.6.6-.6.7-.4a7.5 7.5 0 0 1 .8-.2 4.5 4.5 0 0 1 .8-.2h2.5a3.8 3.8 0 0 1 1.2.3v3.1a1.3 1.3 0 0 0 2 1l9-7.5a1.5 1.5 0 0 0 0-2.3z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" data-icon-name="icon-tabler-corner-down-right" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" role="img">
<title>Redirect</title>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M6 6v6a3 3 0 0 0 3 3h10l-4 -4m0 8l4 -4"></path>
</svg>

Before

Width:  |  Height:  |  Size: 596 B

After

Width:  |  Height:  |  Size: 400 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" data-icon-name="icon-tabler-arrow-back-up" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M9 13l-4 -4l4 -4m-4 4h11a4 4 0 0 1 0 8h-1"></path>
</svg>

After

Width:  |  Height:  |  Size: 360 B

View File

@@ -1 +1,6 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#AAA" d="M15.8 6.4h-1.1s-.1.1-.1 0l.8-.7c.5-.5.5-1.3 0-1.9L14 2.4c-.5-.5-1.4-.5-1.9 0l-.6.8c-.1 0 0 0 0-.1v-1c0-.8-1-1.4-1.8-1.4h-2c-.8 0-1.9.6-1.9 1.4v1.1l.1.1-.8-.8c-.5-.5-1.3-.5-1.9 0L1.8 3.9c-.5.5-.5 1.4 0 1.9l.8.6c0 .1 0 0-.1 0H1.4C.7 6.4 0 7.5 0 8.2v2C0 11 .7 12 1.4 12h1.2l.1-.1-.8.7c-.5.5-.5 1.3 0 1.9L3.3 16c.5.5 1.4.5 1.9 0l.6-.8-.1.1v1.2c0 .8 1.1 1.4 1.9 1.4h2c.8 0 1.8-.6 1.8-1.4v-1.2s-.1-.1 0-.1l.7.8c.5.5 1.3.5 1.9 0l1.4-1.4c.5-.5.5-1.4 0-1.9l-.8-.7.1.1h1.1c.8 0 1.3-1.1 1.3-1.8v-2c0-.8-.6-1.9-1.3-1.9zM8.6 13a3.8 3.8 0 1 1 3.8-3.8A4 4 0 0 1 8.6 13zm13.7 2.6l-.6.2s0 .1 0 0l.3-.5c.2-.4 0-.8-.4-1l-1-.4c-.4-.2-.8 0-1 .4l-.1.5-.2-.6c-.2-.4-.8-.5-1.2-.3l-1.1.4c-.4.2-.8.7-.7 1.1l.2.6h.1l-.5-.3c-.4-.2-.8 0-1 .4l-.4 1c-.2.4 0 .8.4 1l.5.1-.6.2c-.4.2-.5.8-.4 1.2l.4 1.1c.2.4.7.8 1.1.7l.6-.2s0-.1 0 0l-.3.5c-.2.4 0 .8.4 1l1 .4c.4.2.8 0 1-.4l.1-.5.2.6c.2.4.9.5 1.2.3l1.1-.4c.4-.2.8-.7.6-1.1l-.2-.6s-.1 0 0 0l.5.3c.4.2.8 0 1-.4l.4-1c.2-.4 0-.8-.4-1l-.5-.1.6-.2c.4-.2.5-.8.3-1.2l-.4-1.1c-.1-.4-.6-.8-1-.7zm-2.4 4.9a2 2 0 0 1-2.7-1.2 2 2 0 0 1 1.2-2.7 2 2 0 0 1 2.7 1.2 2 2 0 0 1-1.2 2.7z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" data-icon-name="icon-tabler-browser" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<rect x="4" y="4" width="16" height="16" rx="1"></rect>
<line x1="4" y1="8" x2="20" y2="8"></line>
<line x1="8" y1="4" x2="8" y2="8"></line>
</svg>

Before

Width:  |  Height:  |  Size: 1.2 KiB

After

Width:  |  Height:  |  Size: 443 B

View File

@@ -1 +1,9 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#AAA" d="M13 3v18c0 1.1-.9 2-2 2s-2-.9-2-2V3c0-1.1.9-2 2-2s2 .9 2 2zm10.2 1.6l-1.8-1.4c-.2-.3-.6-.2-1-.2H14v5h6.4c.4 0 .8-.3 1.1-.5l1.8-1.6c.3-.3.3-1-.1-1.3zm-3.7 4.8c-.3-.3-.7-.4-1.1-.4H14v5h4.4a2 2 0 0 0 1.1-.3l1.8-1.5c.4-.3.4-.9 0-1.3l-1.8-1.5zM3.5 7c-.4 0-.7 0-1 .3L.7 8.8c-.4.3-.4.9 0 1.3l1.8 1.6c.3.2.6.3 1 .3H8V7H3.5z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" data-icon-name="icon-tabler-directions" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M12 21v-4"></path>
<path d="M12 13v-4"></path>
<path d="M12 5v-2"></path>
<path d="M10 21h4"></path>
<path d="M8 5v4h11l2 -2l-2 -2z"></path>
<path d="M14 13v4h-8l-2 -2l2 -2z"></path>
</svg>

Before

Width:  |  Height:  |  Size: 429 B

After

Width:  |  Height:  |  Size: 509 B

View File

@@ -1 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#AAA" d="M11.6.4A8 8 0 0 0 5.2 13L.8 17.5a1.3 1.3 0 0 0-.4.9c0 .3.1.6.4.9s.5.3.9.3c.3 0 .6 0 .9-.3l4.3-4.5a7.9 7.9 0 0 0 4.7 1.5 8 8 0 0 0 0-16zm5.5 8a5.5 5.5 0 0 1-5.5 5.4 5.5 5.5 0 1 1 5.5-5.5z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" data-icon-name="icon-tabler-search" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<circle cx="10" cy="10" r="7"></circle>
<line x1="21" y1="21" x2="15" y2="15"></line>
</svg>

Before

Width:  |  Height:  |  Size: 300 B

After

Width:  |  Height:  |  Size: 383 B

View File

@@ -0,0 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" data-icon-name="icon-tabler-arrows-right" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<line x1="21" y1="17" x2="3" y2="17"></line>
<path d="M18 4l3 3l-3 3"></path>
<path d="M18 20l3 -3l-3 -3"></path>
<line x1="21" y1="7" x2="3" y2="7"></line>
</svg>

After

Width:  |  Height:  |  Size: 458 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" data-icon-name="icon-tabler-moon" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M12 3c.132 0 .263 0 .393 0a7.5 7.5 0 0 0 7.92 12.446a9 9 0 1 1 -8.313 -12.454z" />
</svg>

After

Width:  |  Height:  |  Size: 377 B

View File

@@ -0,0 +1,12 @@
<svg xmlns="http://www.w3.org/2000/svg" data-icon-name="icon-tabler-sun-high" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M14.828 14.828a4 4 0 1 0 -5.656 -5.656a4 4 0 0 0 5.656 5.656z" />
<path d="M6.343 17.657l-1.414 1.414" />
<path d="M6.343 6.343l-1.414 -1.414" />
<path d="M17.657 6.343l1.414 -1.414" />
<path d="M17.657 17.657l1.414 1.414" />
<path d="M4 12h-2" />
<path d="M12 4v-2" />
<path d="M20 12h2" />
<path d="M12 20v2" />
</svg>

After

Width:  |  Height:  |  Size: 644 B

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" data-icon-name="icon-tabler-device-laptop" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<line x1="3" y1="19" x2="21" y2="19" />
<rect x="5" y="6" width="14" height="10" rx="1" />
</svg>

After

Width:  |  Height:  |  Size: 389 B

View File

@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-arrow-autofit-width" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M4 12v-6a2 2 0 0 1 2 -2h12a2 2 0 0 1 2 2v6" />
<path d="M10 18h-7" />
<path d="M21 18h-7" />
<path d="M6 15l-3 3l3 3" />
<path d="M18 15l3 3l-3 3" />
</svg>

After

Width:  |  Height:  |  Size: 483 B

View File

@@ -0,0 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" class="icon icon-tabler icon-tabler-rectangle" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<rect x="3" y="5" width="18" height="14" rx="2" />
</svg>

After

Width:  |  Height:  |  Size: 349 B

View File

@@ -0,0 +1,5 @@
<svg xmlns="http://www.w3.org/2000/svg" data-icon-name="icon-tabler-settings" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M10.325 4.317c.426 -1.756 2.924 -1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543 -.94 3.31 .826 2.37 2.37a1.724 1.724 0 0 0 1.065 2.572c1.756 .426 1.756 2.924 0 3.35a1.724 1.724 0 0 0 -1.066 2.573c.94 1.543 -.826 3.31 -2.37 2.37a1.724 1.724 0 0 0 -2.572 1.065c-.426 1.756 -2.924 1.756 -3.35 0a1.724 1.724 0 0 0 -2.573 -1.066c-1.543 .94 -3.31 -.826 -2.37 -2.37a1.724 1.724 0 0 0 -1.065 -2.572c-1.756 -.426 -1.756 -2.924 0 -3.35a1.724 1.724 0 0 0 1.066 -2.573c-.94 -1.543 .826 -3.31 2.37 -2.37c1 .608 2.296 .07 2.572 -1.065z"></path>
<circle cx="12" cy="12" r="3"></circle>
</svg>

After

Width:  |  Height:  |  Size: 881 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="M12 .9C5.8.9.9 5.8.9 12a11 11 0 1 0 22.2 0A11 11 0 0 0 12 .9zm6.5 6c-.6 0-.9-.3-.9-.8 0-.2 0-.4.2-.6l.2-.4c0-.3-.5-.4-.6-.4-1.8.1-2.3 2.5-2.7 4.4l-.2 1c1 .2 1.8 0 2.2-.3.6-.4-.2-.7-.1-1.2.1-.3.5-.5.7-.6.5 0 .7.5.7.9 0 .7-1 1.8-3 1.8l-.6-.1-.6 2.4c-.4 1.6-.8 3.8-2.4 5.7-1.4 1.7-2.9 1.9-3.5 1.9-1.2 0-1.9-.6-2-1.5 0-.8.7-1.3 1.2-1.3.6 0 1.1.5 1.1 1s-.2.6-.4.6c-.1.1-.3.2-.3.4 0 .1.1.3.4.3.5 0 .8-.3 1.1-.5 1.2-.9 1.6-2.7 2.2-5.7l.1-.7.7-3.2c-.8-.6-1.3-1.4-2.4-1.7-.6-.1-1.1.1-1.5.5-.4.5-.2 1.1.2 1.5l.7.6c.7.8 1.2 1.6 1 2.5-.3 1.5-2 2.6-4 1.9-1.8-.6-2-1.8-1.8-2.5.2-.6.6-.7 1.1-.6.5.2.6.7.6 1.2l-.1.3c-.2.1-.3.3-.3.4-.1.4.4.6.7.7.7.3 1.6-.2 1.8-.8a1 1 0 0 0-.4-1.1l-.7-.8c-.4-.4-1.1-1.4-.7-2.6.1-.5.4-.9.7-1.3a4 4 0 0 1 2.8-.6c1.2.4 1.8 1.1 2.6 1.8.5-1.2 1-2.4 1.8-3.5.9-.9 1.9-1.6 3.1-1.7 1.3.2 2.2.7 2.2 1.6 0 .4-.2 1.1-.9 1.1z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="currentColor" d="M12 .9C5.8.9.9 5.8.9 12a11 11 0 1 0 22.2 0A11 11 0 0 0 12 .9zm6.5 6c-.6 0-.9-.3-.9-.8 0-.2 0-.4.2-.6l.2-.4c0-.3-.5-.4-.6-.4-1.8.1-2.3 2.5-2.7 4.4l-.2 1c1 .2 1.8 0 2.2-.3.6-.4-.2-.7-.1-1.2.1-.3.5-.5.7-.6.5 0 .7.5.7.9 0 .7-1 1.8-3 1.8l-.6-.1-.6 2.4c-.4 1.6-.8 3.8-2.4 5.7-1.4 1.7-2.9 1.9-3.5 1.9-1.2 0-1.9-.6-2-1.5 0-.8.7-1.3 1.2-1.3.6 0 1.1.5 1.1 1s-.2.6-.4.6c-.1.1-.3.2-.3.4 0 .1.1.3.4.3.5 0 .8-.3 1.1-.5 1.2-.9 1.6-2.7 2.2-5.7l.1-.7.7-3.2c-.8-.6-1.3-1.4-2.4-1.7-.6-.1-1.1.1-1.5.5-.4.5-.2 1.1.2 1.5l.7.6c.7.8 1.2 1.6 1 2.5-.3 1.5-2 2.6-4 1.9-1.8-.6-2-1.8-1.8-2.5.2-.6.6-.7 1.1-.6.5.2.6.7.6 1.2l-.1.3c-.2.1-.3.3-.3.4-.1.4.4.6.7.7.7.3 1.6-.2 1.8-.8a1 1 0 0 0-.4-1.1l-.7-.8c-.4-.4-1.1-1.4-.7-2.6.1-.5.4-.9.7-1.3a4 4 0 0 1 2.8-.6c1.2.4 1.8 1.1 2.6 1.8.5-1.2 1-2.4 1.8-3.5.9-.9 1.9-1.6 3.1-1.7 1.3.2 2.2.7 2.2 1.6 0 .4-.2 1.1-.9 1.1z"/></svg>

Before

Width:  |  Height:  |  Size: 942 B

After

Width:  |  Height:  |  Size: 950 B

View File

@@ -1 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#AAA" d="M15.1 4.3a13 13 0 0 0-6.2 0c-.3 0-.7-.2-.7-.5v-.4c0-1.2 1-2.3 2.3-2.3h3c1.2 0 2.3 1 2.3 2.3v.3c0 .4-.4.6-.7.6zm5.8 9.7a9 9 0 0 1-17.8 0 9 9 0 0 1 17.8 0zm-4.2 1c0-.6-.4-1-1-1H13V8.4c0-.6-.4-1-1-1s-1 .4-1 1v6.2c0 .6.4 1.3 1 1.3h3.7c.5.1 1-.3 1-.9z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" data-icon-name="icon-tabler-chart-infographic" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" role="img">
<title>Time</title>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<circle cx="7" cy="7" r="4"></circle>
<path d="M7 3v4h4"></path>
<line x1="9" y1="17" x2="9" y2="21"></line>
<line x1="17" y1="14" x2="17" y2="21"></line>
<line x1="13" y1="13" x2="13" y2="21"></line>
<line x1="21" y1="12" x2="21" y2="21"></line>
</svg>

Before

Width:  |  Height:  |  Size: 360 B

After

Width:  |  Height:  |  Size: 606 B

View File

@@ -1 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#AAA" d="M5.4 6H7v.3c0 1.2-.7 1.9-1.7 1.9-1.1 0-1.4-.4-1.4-1.1 0-.9.6-1.1 1.5-1.1zm3.8-6H2.7C1.2 0 0 .9 0 2.4v6.5C0 10.4 1.2 11 2.7 11h1.2l3.3 3.2c.4.2.8.3.8-.1V9.9l.1-1.1h-.4c-.3.1-.7-.1-.7-.4v-.5c0 .7-1 1-1.8 1-1.5 0-2.4-.7-2.4-2s1.1-2 2.6-2H7v-.4c0-1-.4-1.6-1.5-1.6-.7 0-1.1.2-1.5.7l-.3.2a.4.4 0 0 1-.4-.4l.1-.2c.4-.7 1-1.2 2.2-1.2C7.2 2 8 3 8 4.5v3c1-1.4 1.8-2.4 4-2.4V2.4C12 .9 10.7 0 9.2 0zm11.5 6h-8C10.8 6 9 7.2 9 9v8c0 1.8 2 3.3 4 3.3v3c0 .5.5.7.9.3l4-3.7h2.7c1.8 0 3.3-1.2 3.3-3V9c.1-1.8-1.4-3-3.2-3zM13 9h7v.8s-.7.3-1.1.3h-4.8a10 10 0 0 1-1.1-.4V9zm-.5 7.9l-.6-.5c1-.8 1.8-2.1 2.2-3.4l.7.3c-.6 1.5-1.4 2.6-2.3 3.6zM17 12v4.8c0 .7-.2.8-1.2.8l-1.4-.1-.2-.7 1.4.1c.4 0 .4 0 .4-.3V12h-2.9l-1.1.1v-.8s.7-.4 1.1-.4h6.8c.5 0 1.1.4 1.1.4v.8l-1.1-.1H17zm3.7 4.8c-1-1.1-1.6-1.9-2.3-3.6l.6-.2a9 9 0 0 0 2.2 3.3l-.5.5z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" data-icon-name="icon-tabler-language-hiragana" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M4 5h7"></path>
<path d="M7 4c0 4.846 0 7 .5 8"></path>
<path d="M10 8.5c0 2.286 -2 4.5 -3.5 4.5s-2.5 -1.135 -2.5 -2c0 -2 1 -3 3 -3s5 .57 5 2.857c0 1.524 -.667 2.571 -2 3.143"></path>
<path d="M12 20l4 -9l4 9"></path>
<path d="M19.1 18h-6.2"></path>
</svg>

Before

Width:  |  Height:  |  Size: 922 B

After

Width:  |  Height:  |  Size: 579 B

View File

@@ -1 +1,7 @@
<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>
<svg xmlns="http://www.w3.org/2000/svg" data-icon-name="icon-tabler-seeding" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" role="img">
<title>Twig</title>
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M12 10a6 6 0 0 0 -6 -6h-3v2a6 6 0 0 0 6 6h3"></path>
<path d="M12 14a6 6 0 0 1 6 -6h3v1a6 6 0 0 1 -6 6h-3"></path>
<line x1="12" y1="20" x2="12" y2="10"></line>
</svg>

Before

Width:  |  Height:  |  Size: 343 B

After

Width:  |  Height:  |  Size: 507 B

View File

@@ -1 +1,7 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24"><path fill="#aaa" d="M19.5 22.5H4.3a2.9 2.9 0 0 1-2.9-2.9V4.4a2.9 2.9 0 0 1 2.9-2.9h14.2a1 1 0 0 1 0 2H4.3a.9.9 0 0 0-.9.9v15.2a.9.9 0 0 0 .9.9h15.2a.9.9 0 0 0 1-.9v-8.3a1 1 0 1 1 2 0v8.3a2.9 2.9 0 0 1-3 2.9zM13 17.3L22.9 6a1.5 1.5 0 1 0-2.3-2L12 14 8 9.1A1.5 1.5 0 0 0 5.7 11l5 6.3a1.5 1.5 0 0 0 1.2.5 1.5 1.5 0 0 0 1.1-.5z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" data-icon-name="icon-tabler-checklist" width="24" height="24" viewBox="0 0 24 24" stroke-width="1.5" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M9.615 20h-2.615a2 2 0 0 1 -2 -2v-12a2 2 0 0 1 2 -2h8a2 2 0 0 1 2 2v8"></path>
<path d="M14 19l2 2l4 -4"></path>
<path d="M9 8h4"></path>
<path d="M9 12h2"></path>
</svg>

Before

Width:  |  Height:  |  Size: 417 B

After

Width:  |  Height:  |  Size: 481 B

View File

@@ -0,0 +1,8 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M12 18m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0"></path>
<path d="M7 6m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0"></path>
<path d="M17 6m-2 0a2 2 0 1 0 4 0a2 2 0 1 0 -4 0"></path>
<path d="M7 8v2a2 2 0 0 0 2 2h6a2 2 0 0 0 2 -2v-2"></path>
<path d="M12 12l0 4"></path>
</svg>

After

Width:  |  Height:  |  Size: 527 B

View File

@@ -1 +1,4 @@
<svg xmlns="http://www.w3.org/2000/svg" width="28" height="28" viewBox="0 0 12 12"><path fill="#5E976E" d="M12 3.1c0 .4-.1.8-.4 1.1L5.9 9.8c-.3.3-.6.4-1 .4s-.7-.1-1-.4L.4 6.3C.1 6 0 5.6 0 5.2c0-.4.2-.7.4-.9.2-.3.6-.4.9-.4.4 0 .8.1 1.1.4l2.5 2.5 4.7-4.7c.3-.3.7-.4 1-.4.4 0 .7.2.9.4.3.3.5.6.5 1z"/></svg>
<svg xmlns="http://www.w3.org/2000/svg" data-icon-name="icon-tabler-check" width="24" height="24" viewBox="0 0 24 24" stroke-width="3" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round">
<path stroke="none" d="M0 0h24v24H0z" fill="none"></path>
<path d="M5 12l5 5l10 -10"></path>
</svg>

Before

Width:  |  Height:  |  Size: 304 B

After

Width:  |  Height:  |  Size: 325 B

View File

@@ -0,0 +1,50 @@
{% set status_code = profile.statuscode|default(0) %}
{% set interrupted = command_collector is same as false ? null : command_collector.interruptedBySignal %}
{% set css_class = status_code == 113 or interrupted is not null ? 'status-warning' : status_code > 0 ? 'status-error' : 'status-success' %}
<div class="terminal status {{ css_class }}">
<div class="container">
<h2>
<span class="status-request-method">
{{ profile.method|upper }}
</span>
<span class="status-command">
{{ profile.url }}
</span>
</h2>
<dl class="metadata">
{% if interrupted %}
<span class="status-response-status-code">Interrupted</span>
<dt>Signal</dt>
<dd class="status-response-status-text">{{ interrupted }}</dd>
<dt>Exit code</dt>
<dd class="status-response-status-text">{{ status_code }}</dd>
{% elseif status_code == 0 %}
<span class="status-response-status-code">Success</span>
{% elseif status_code > 0 %}
<span class="status-response-status-code">Error</span>
<dt>Exit code</dt>
<dd class="status-response-status-text"><span class="status-response-status-code">{{ status_code }}</span></dd>
{% endif %}
{% if request_collector.requestserver.has('SYMFONY_CLI_BINARY_NAME') %}
<dt>Symfony CLI</dt>
<dd>v{{ request_collector.requestserver.get('SYMFONY_CLI_VERSION') }}</dd>
{% endif %}
<dt>Application</dt>
<dd>
<a href="{{ path('_profiler_search_results', { token: token, limit: 10, ip: profile.ip, type: 'command' }) }}">{{ profile.ip }}</a>
</dd>
<dt>Profiled on</dt>
<dd><time data-convert-to-user-timezone data-render-as-datetime datetime="{{ profile.time|date('c') }}">{{ profile.time|date('r') }}</time></dd>
<dt>Token</dt>
<dd>{{ profile.token }}</dd>
</dl>
</div>
</div>

View File

@@ -0,0 +1,99 @@
{% 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' %}
{% if request_collector and request_collector.redirect %}
{% set redirect = request_collector.redirect %}
{% set link_to_source_code = redirect.controller.class is defined ? redirect.controller.file|file_link(redirect.controller.line) %}
{% set redirect_route_name = '@' ~ redirect.route %}
<div class="status status-compact status-warning">
<span class="icon icon-redirect">{{ source('@WebProfiler/Icon/redirect.svg') }}</span>
<span class="status-response-status-code">{{ redirect.status_code }}</span> redirect from
<span class="status-request-method">{{ redirect.method }}</span>
{% if link_to_source_code %}
<a href="{{ link_to_source_code }}" title="{{ redirect.controller.file }}">{{ redirect_route_name }}</a>
{% else %}
{{ redirect_route_name }}
{% endif %}
(<a href="{{ path('_profiler', { token: redirect.token, panel: request.query.get('panel', 'request') }) }}">{{ redirect.token }}</a>)
</div>
{% endif %}
<div class="status {{ css_class }}">
{% if status_code > 399 %}
<p class="status-error-details">
<span class="icon">{{ source('@WebProfiler/Icon/alert-circle.svg') }}</span>
<span class="status-response-status-code">Error {{ status_code }}</span>
<span class="status-response-status-text">{{ request_collector.statusText }}</span>
</p>
{% endif %}
<h2>
<span class="status-request-method">
{{ profile.method|upper }}
</span>
{% set profile_title = profile.url|length < 160 ? profile.url : profile.url[:160] ~ '…' %}
{% if profile.method|upper in ['GET', 'HEAD'] %}
<a href="{{ profile.url }}">{{ profile_title }}</a>
{% else %}
{{ profile_title }}
{% endif %}
</h2>
<dl class="metadata">
{% if status_code < 400 %}
<dt>Response</dt>
<dd>
<span class="status-response-status-code">{{ status_code }}</span>
<span class="status-response-status-text">{{ request_collector.statusText }}</span>
</dd>
{% endif %}
{% set referer = request_collector ? request_collector.requestheaders.get('referer') : null %}
{% if referer %}
<dt></dt>
<dd>
<span class="icon icon-referer">{{ source('@WebProfiler/Icon/referrer.svg') }}</span>
<a href="{{ referer }}" class="referer">Browse referrer URL</a>
</dd>
{% endif %}
<dt>IP</dt>
<dd>
<a href="{{ path('_profiler_search_results', { token: token, limit: 10, ip: profile.ip }) }}">{{ profile.ip }}</a>
</dd>
<dt>Profiled on</dt>
<dd><time data-convert-to-user-timezone data-render-as-datetime datetime="{{ profile.time|date('c') }}">{{ profile.time|date('r') }}</time></dd>
<dt>Token</dt>
<dd>{{ profile.token }}</dd>
</dl>
</div>
{% 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' %}
<div class="status status-compact status-compact-forward">
<span class="icon icon-forward">{{ source('@WebProfiler/Icon/forward.svg') }}</span>
Forwarded to
{% set link = controller.file is defined ? controller.file|file_link(controller.line) : null -%}
{%- if link %}<a href="{{ link }}" title="{{ controller.file }}">{% endif -%}
{% 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: request_collector.forwardtoken }) }}">{{ request_collector.forwardtoken }}</a>)
</div>
{%- endif %}

View File

@@ -1,16 +1,25 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="{{ _charset }}" />
<meta name="robots" content="noindex,nofollow" />
<meta name="viewport" content="width=device-width,initial-scale=1" />
<meta charset="{{ _charset }}">
<meta name="robots" content="noindex,nofollow">
<meta name="viewport" content="width=device-width,initial-scale=1">
<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==">
{% set request_collector = profile is defined ? profile.collectors.request|default(null) : null %}
{% set status_code = request_collector is not null ? request_collector.statuscode|default(0) : 0 %}
{% set favicon_color = status_code > 399 ? 'b41939' : status_code > 299 ? 'af8503' : '000000' %}
<link rel="icon" type="image/svg+xml" href="data:image/svg+xml,%3Csvg viewBox='0 0 600 600' xmlns='http://www.w3.org/2000/svg' fill-rule='evenodd' clip-rule='evenodd' stroke-linejoin='round' stroke-miterlimit='2'%3E%3Cstyle%3E%23circle %7B fill: %23{{ favicon_color }}; %7D %23sf %7B fill: %23ffffff; %7D%3C/style%3E%3Cpath fill='none' d='M0 0h600v600H0z'/%3E%3CclipPath id='a'%3E%3Cpath d='M0 0h600v600H0z'/%3E%3C/clipPath%3E%3Cg clip-path='url(%23a)'%3E%3Cpath id='circle' d='M599.985 299.974c0 165.696-134.307 300.024-300.003 300.024C134.302 599.998 0 465.67 0 299.974 0 134.304 134.302-.002 299.982-.002c165.696 0 300.003 134.307 300.003 299.976z' fill-rule='nonzero'/%3E%3Cpath id='sf' d='M431.154 110.993c-30.474 1.043-57.08 17.866-76.884 41.076-21.926 25.49-36.508 55.696-47.03 86.55-18.791-15.416-33.282-35.364-63.457-44.04-23.311-6.702-47.794-3.948-70.314 12.833-10.667 7.965-18.016 19.995-21.51 31.34-9.05 29.416 9.506 55.61 17.942 65.004l18.444 19.743c3.792 3.879 12.95 13.983 8.467 28.458-4.82 15.764-23.809 25.938-43.285 19.958-8.703-2.67-21.199-9.147-18.396-18.257 1.145-3.739 3.82-6.553 5.264-9.74 1.305-2.788 1.941-4.858 2.337-6.099 3.557-11.602-1.31-26.714-13.747-30.56-11.613-3.562-23.488-.738-28.094 14.202-5.22 16.979 2.905 47.795 46.436 61.206 51 15.694 94.13-12.084 100.249-48.287 3.857-22.675-6.392-39.536-25.147-61.2l-15.293-16.92c-9.254-9.248-12.437-25.018-2.856-37.134 8.093-10.233 19.6-14.581 38.476-9.457 27.543 7.468 39.809 26.58 60.285 41.996-8.44 27.741-13.977 55.584-18.973 80.548l-3.07 18.626c-14.636 76.766-25.816 118.939-54.856 143.144-5.858 4.167-14.218 10.399-26.821 10.843-6.622.203-8.757-4.355-8.847-6.344-.15-4.628 3.755-6.756 6.349-8.837 3.889-2.124 9.757-5.633 9.356-16.882-.423-13.293-11.431-24.815-27.35-24.286-11.919.402-30.09 11.608-29.4 32.149.701 21.22 20.472 37.118 50.288 36.107 15.935-.535 51.528-7.018 86.592-48.699 40.82-47.8 52.235-102.576 60.82-142.673l9.591-52.946a177.574 177.574 0 0017.209 1.22c50.844 1.075 76.257-25.249 76.653-44.41.257-11.591-7.6-23.011-18.61-22.739-7.863.22-17.759 5.473-20.123 16.353-2.332 10.671 16.17 20.316 1.712 29.704-10.27 6.643-28.683 11.319-54.615 7.526l4.712-26.061c9.623-49.416 21.493-110.193 66.528-111.68 3.284-.155 15.282.139 15.56 8.088.08 2.637-.582 3.332-3.68 9.393-3.166 4.729-4.36 8.773-4.204 13.394.433 12.608 10.024 20.91 23.916 20.429 18.572-.626 23.906-18.7 23.6-27.998-.759-21.846-23.776-35.647-54.224-34.641z' fill-rule='nonzero'/%3E%3C/g%3E%3C/svg%3E"/>
{% block head %}
<style{% if csp_style_nonce is defined and csp_style_nonce %} nonce="{{ csp_style_nonce }}"{% endif %}>
{{ include('@WebProfiler/Profiler/profiler.css.twig') }}
</style>
{% block stylesheets %}
<style{% if csp_style_nonce is defined and csp_style_nonce %} nonce="{{ csp_style_nonce }}"{% endif %}>
{{ include('@WebProfiler/Profiler/profiler.css.twig') }}
</style>
{% endblock %}
{% block javascripts %}
{% endblock %}
{% endblock %}
</head>
<body>
@@ -27,6 +36,10 @@
}
document.body.classList.add(localStorage.getItem('symfony/profiler/width') || 'width-normal');
document.body.classList.add(
(navigator.appVersion.indexOf('Win') !== -1) ? 'windows' : (navigator.appVersion.indexOf('Mac') !== -1) ? 'macos' : 'linux'
);
</script>
{% block body '' %}

View File

@@ -1,6 +1,6 @@
{% block toolbar %}
{% set icon %}
{{ include('@WebProfiler/Icon/symfony.svg') }}
{{ source('@WebProfiler/Icon/symfony.svg') }}
<span class="sf-toolbar-value sf-toolbar-ajax-request-counter">
Loading&hellip;

View File

@@ -1,14 +1,12 @@
<div id="header">
<div class="container">
<h1>{{ include('@WebProfiler/Icon/symfony.svg') }} Symfony <span>Profiler</span></h1>
<h1><a href="{{ path('_profiler_home') }}">{{ source('@WebProfiler/Icon/symfony.svg') }} Symfony Profiler</a></h1>
<div class="search">
<form method="get" action="https://symfony.com/search" target="_blank">
<div class="form-row">
<input name="q" id="search-id" type="search" placeholder="search on symfony.com">
<button type="submit" class="btn">Search</button>
</div>
</form>
</div>
<div class="search">
<form method="get" action="https://symfony.com/search" target="_blank">
<div class="form-row">
<input name="q" id="search-id" type="search" placeholder="search on symfony.com" aria-label="Search on symfony.com">
<button type="submit" class="visually-hidden">Search</button>
</div>
</form>
</div>
</div>

View File

@@ -1,153 +1,82 @@
{% extends '@WebProfiler/Profiler/base.html.twig' %}
{% block body %}
{{ include('@WebProfiler/Profiler/header.html.twig', with_context = false) }}
<div class="container">
{{ include('@WebProfiler/Profiler/header.html.twig', {profile_type: profile_type}, with_context = false) }}
<div id="summary">
<div id="summary">
{% block summary %}
{% if profile is defined %}
{% 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 }}">
<div class="container">
<h2 class="break-long-words">
{% if profile.method|upper in ['GET', 'HEAD'] %}
<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>
{% if request_collector and request_collector.redirect -%}
{%- set redirect = request_collector.redirect -%}
{%- set controller = redirect.controller -%}
{%- set redirect_route = '@' ~ redirect.route %}
<dl class="metadata">
<dt>
<span class="label">{{ redirect.status_code }}</span>
Redirect from
</dt>
<dd>
{{ 'GET' != redirect.method ? redirect.method }}
{% if redirect.controller.class is defined -%}
{%- set link = controller.file|file_link(controller.line) -%}
{% if link %}<a href="{{ link }}" title="{{ controller.file }}">{% endif -%}
{{ redirect_route }}
{%- if link %}</a>{% endif -%}
{%- else -%}
{{ redirect_route }}
{%- endif %}
(<a href="{{ path('_profiler', { token: redirect.token, panel: request.query.get('panel', 'request') }) }}">{{ redirect.token }}</a>)
</dd>
</dl>
{%- endif %}
{% 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 is defined ? controller.file|file_link(controller.line) : null -%}
{%- if link %}<a href="{{ link }}" title="{{ controller.file }}">{% endif -%}
{% 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: request_collector.forwardtoken }) }}">{{ request_collector.forwardtoken }}</a>)
</dd>
</dl>
{%- endif %}
<dl class="metadata">
<dt>Method</dt>
<dd>{{ profile.method|upper }}</dd>
<dt>HTTP Status</dt>
<dd>{{ status_code }}</dd>
<dt>IP</dt>
<dd>
<a href="{{ path('_profiler_search_results', { token: token, limit: 10, ip: profile.ip }) }}">{{ profile.ip }}</a>
</dd>
<dt>Profiled on</dt>
<dd><time datetime="{{ profile.time|date('c') }}">{{ profile.time|date('r') }}</time></dd>
<dt>Token</dt>
<dd>{{ profile.token }}</dd>
</dl>
</div>
</div>
{{ include('@WebProfiler/Profiler/_%s_summary.html.twig'|format(profile_type), {
profile: profile,
command_collector: profile.collectors.command|default(false) ,
request_collector: request_collector,
request: request,
token: token
}, with_context=false) }}
{% endif %}
{% endblock %}
</div>
<div id="content" class="container">
<div id="main">
<div id="sidebar">
<div id="sidebar-shortcuts">
<div class="shortcuts">
<a href="#" id="sidebarShortcutsMenu" class="visible-small">
<span class="icon">{{ include('@WebProfiler/Icon/menu.svg') }}</span>
</a>
<div id="content">
<div id="main">
<div id="sidebar">
{% block sidebar %}
<div id="sidebar-contents">
<div id="sidebar-shortcuts">
{% block sidebar_shortcuts_links %}
<div class="shortcuts">
<a class="btn btn-link" href="{{ path('_profiler_search', { limit: 10, type: profile_type }) }}">Last 10</a>
<a class="btn btn-link" href="{{ path('_profiler', { token: 'latest', type: profile_type }|merge(request.query.all)) }}">Latest</a>
<a class="btn btn-sm" href="{{ path('_profiler_search', { limit: 10 }) }}">Last 10</a>
<a class="btn btn-sm" href="{{ path('_profiler', { token: 'latest' }|merge(request.query.all)) }}">Latest</a>
<a class="sf-toggle btn btn-link" data-toggle-selector="#sidebar-search" {% if tokens is defined or about is defined %}data-toggle-initial="display"{% endif %}>
{{ source('@WebProfiler/Icon/search.svg') }} <span class="hidden-small">Search</span>
</a>
</div>
{% endblock sidebar_shortcuts_links %}
<a class="sf-toggle btn btn-sm" data-toggle-selector="#sidebar-search" {% if tokens is defined or about is defined %}data-toggle-initial="display"{% endif %}>
{{ include('@WebProfiler/Icon/search.svg') }} <span class="hidden-small">Search</span>
</a>
{{ render(controller('web_profiler.controller.profiler::searchBarAction', query={type: profile_type }|merge(request.query.all))) }}
</div>
{{ render(controller('web_profiler.controller.profiler::searchBarAction', query=request.query.all)) }}
</div>
{% if templates is defined %}
<ul id="menu-profiler">
{% if 'request' is same as(profile_type) %}
{% set excludes = ['command'] %}
{% elseif 'command' is same as(profile_type) %}
{% set excludes = ['request', 'router'] %}
{% endif %}
{% for name, template in templates|filter((t, n) => n not in excludes) %}
{% set menu -%}
{%- if block('menu', template) is defined -%}
{% with { collector: profile.getcollector(name), profiler_markup_version: profiler_markup_version } %}
{{- block('menu', template) -}}
{% endwith %}
{%- endif -%}
{%- endset %}
{% if menu is not empty %}
<li class="{{ name }} {{ name == panel ? 'selected' }}">
<a href="{{ path('_profiler', { token: token, panel: name, type: profile_type }) }}">{{ menu|raw }}</a>
</li>
{% endif %}
{% endfor %}
</ul>
{% endif %}
</div>
{{ include('@WebProfiler/Profiler/settings.html.twig') }}
{% endblock sidebar %}
</div>
{% if templates is defined %}
<ul id="menu-profiler">
{% for name, template in templates %}
{% set menu -%}
{%- if block('menu', template) is defined -%}
{% with { collector: profile.getcollector(name), profiler_markup_version: profiler_markup_version } %}
{{- block('menu', template) -}}
{% endwith %}
{%- endif -%}
{%- endset %}
{% if menu is not empty %}
<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">
<div id="collector-content">
{{ include('@WebProfiler/Profiler/base_js.html.twig') }}
{% block panel '' %}
<div id="collector-wrapper">
<div id="collector-content">
{{ include('@WebProfiler/Profiler/base_js.html.twig') }}
{% block panel '' %}
</div>
</div>
</div>
</div>
</div>
<script>
(function () {
Sfjs.addEventListener(document.getElementById('sidebarShortcutsMenu'), 'click', function (event) {
event.preventDefault();
Sfjs.toggleClass(document.getElementById('sidebar'), 'expanded');
})
}());
</script>
{% endblock %}

View File

@@ -1,79 +1,79 @@
{# Mixins
========================================================================= #}
{% set mixins = {
'break_long_words': '-ms-word-break: break-all; word-break: break-all; word-break: break-word; -webkit-hyphens: auto; -moz-hyphens: auto; hyphens: auto;',
'monospace_font': 'font-family: monospace; font-size: 13px; font-size-adjust: 0.5;',
'sans_serif_font': 'font-family: Helvetica, Arial, sans-serif;',
'subtle_border_and_shadow': 'background: #FFF; border: 1px solid #E0E0E0; box-shadow: 0px 0px 1px rgba(128, 128, 128, .2);'
} %}
#header {
margin-bottom: 30px;
}
{# Normalization
(normalize.css v3.0.3 | MIT License | github.com/necolas/normalize.css)
========================================================================= #}
html{font-family:sans-serif;-webkit-text-size-adjust:100%;-ms-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background-color:transparent}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{margin:.67em 0;font-size:2em}mark{color:#000;background:#ff0}small{font-size:80%}sub,sup{position:relative;font-size:75%;line-height:0;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{height:0;-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{margin:0;font:inherit;color:inherit}button{overflow:visible}button,select{text-transform:none}button,html input[type="button"],input[type="reset"],input[type="submit"]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{padding:0;border:0}input{line-height:normal}input[type="checkbox"],input[type="radio"]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box;padding:0}input[type="number"]::-webkit-inner-spin-button,input[type="number"]::-webkit-outer-spin-button{height:auto}input[type="search"]{-webkit-box-sizing:content-box;-moz-box-sizing:content-box;box-sizing:content-box;-webkit-appearance:textfield}input[type="search"]::-webkit-search-cancel-button,input[type="search"]::-webkit-search-decoration{-webkit-appearance:none}fieldset{padding:.35em .625em .75em;margin:0 2px;border:1px solid silver}legend{padding:0;border:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}
{# Basic styles
========================================================================= #}
html, body {
height: 100%;
#source {
background: var(--page-background);
border: 1px solid var(--menu-border-color);
box-shadow: 0 0 0 5px var(--page-background);
border-radius: 6px;
margin: 0 30px 45px 0;
max-width: 960px;
padding: 15px 20px;
}
.width-full #source {
max-width: unset;
width: 100%;
}
body {
background-color: #F9F9F9;
color: #aaa;
display: flex;
flex-direction: column;
{{ mixins.sans_serif_font|raw }}
font-size: 14px;
line-height: 1.4;
#source code {
font-size: 15px;
}
.header {
background-color: #222;
position: fixed;
top: 0;
display: flex;
width: 100%;
#source .source-file-name {
border-bottom: 1px solid var(--table-border-color);
font-size: 18px;
font-weight: 500;
margin: 0 0 15px 0;
padding: 0 0 15px;
}
.header h1 {
color: #FFF;
font-weight: normal;
font-size: 21px;
#source .source-file-name small {
color: var(--color-muted);
}
#source .source-content {
overflow-x: auto;
}
#source .source-content ol {
margin: 0;
padding: 10px 10px 8px;
}
#source .source-content ol li {
margin: 0 0 2px 0;
padding-left: 5px;
}
#source .source-content ol li::marker {
color: var(--color-muted);
font-family: var(--font-family-monospace);
padding-right: 5px;
}
#source .source-content li.selected {
background: var(--yellow-100);
border-radius: 4px;
}
.theme-dark #source .source-content li.selected {
background: var(--gray-600);
}
#source .source-content li.selected::marker {
color: var(--color-text);
font-weight: bold;
}
#source span[style="color: #FF8000"] { color: var(--highlight-comment) !important; }
#source span[style="color: #007700"] { color: var(--highlight-keyword) !important; }
#source span[style="color: #0000BB"] { color: var(--color-text) !important; }
#source span[style="color: #DD0000"] { color: var(--highlight-string) !important; }
.file-metadata dt {
color: var(--header-metadata-key);
display: block;
font-weight: bold;
}
.file-metadata dd {
color: var(--header-metadata-value);
margin: 5px 0 20px;
/* needed to break the long file paths */
overflow-wrap: break-word;
word-wrap: break-word;
word-break: break-all;
}
a.doc {
color: #FFF;
text-decoration: none;
margin: auto;
margin-right: 10px;
}
a.doc:hover {
text-decoration: underline;
}
.empty {
padding: 10px;
color: #555;
}
.source {
margin-top: 41px;
}
.source li code {
color: #555;
}
.source li.selected {
background: rgba(255, 255, 153, 0.5);
}
.anchor {
position: relative;
display: inline-block;
top: -7em;
visibility: hidden;
}

View File

@@ -2,21 +2,63 @@
{% block head %}
<style>
{{ include('@WebProfiler/Profiler/profiler.css.twig') }}
{{ include('@WebProfiler/Profiler/open.css.twig') }}
</style>
{% endblock %}
{% block body %}
{% set source = filename|file_excerpt(line, -1) %}
<div class="header">
<h1>{{ file }}{% if 0 < line %} <small>line {{ line }}</small>{% endif %}</h1>
<a class="doc" href="https://symfony.com/doc/{{ constant('Symfony\\Component\\HttpKernel\\Kernel::VERSION') }}/reference/configuration/framework.html#ide" rel="help">Open in your IDE?</a>
</div>
<div class="source">
{% if source is null %}
<p class="empty">The file is not readable.</p>
{% else %}
{{ source|raw }}
{% endif %}
<div class="container">
{{ include('@WebProfiler/Profiler/header.html.twig', with_context = false) }}
{% set source = file_info.pathname|file_excerpt(line, -1) %}
<div id="content">
<div id="main">
<div id="source">
<h1 class="source-file-name">{{ file }}{% if 0 < line %} <small>line {{ line }}</small>{% endif %}</h1>
<div class="source-content">
{% if source is null %}
<p class="empty empty-panel">The file is not readable.</p>
{% else %}
{{ source|raw }}
{% endif %}
</div>
</div>
<div id="sidebar">
<dl class="file-metadata">
<dt>Filepath:</dt>
<dd>{{ file_info.pathname }}</dd>
<dt>Last modified:</dt>
<dd>{{ file_info.mTime|date }}</dd>
<dt>Size:</dt>
{% set file_size_in_kb = file_info.size / 1024 %}
{% set file_num_lines = source|split("\n")|length - 1 %}
<dd>
{{ file_size_in_kb < 1 ? file_info.size ~ ' bytes' : file_size_in_kb|number_format(0) ~ ' KB' }}
/ {{ file_num_lines }} lines
</dd>
</dl>
<a class="doc-link" href="https://symfony.com/doc/{{ constant('Symfony\\Component\\HttpKernel\\Kernel::VERSION') }}/reference/configuration/framework.html#ide" rel="help">Open this file in your IDE?</a>
</div>
</div>
</div>
</div>
<script>
window.addEventListener('load', function () {
const selectedLineElement = document.querySelector('.source-content li.selected');
if (null === selectedLineElement) {
return;
}
const selectedLineYCoordinate = selectedLineElement.getBoundingClientRect().y;
console.log(selectedLineYCoordinate);
window.scrollTo({ top: selectedLineYCoordinate - 20, left: 0, behavior: 'smooth' });
});
</script>
{% endblock %}

View File

@@ -2,56 +2,124 @@
{% 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>
<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">{{ source('@WebProfiler/Icon/search.svg') }}</span></a>
{%- endif -%}
{% endmacro %}
{% import _self as helper %}
{% block head %}
{{ parent() }}
<style>
#search-results td {
font-family: var(--font-family-system);
vertical-align: middle;
}
#search-results .sf-search {
visibility: hidden;
margin-left: 2px;
}
#search-results tr:hover .sf-search {
visibility: visible;
}
#search-results .sf-search svg {
stroke-width: 3;
}
</style>
{% endblock %}
{% block summary %}
<div class="status">
<div class="container">
<h2>Profile Search</h2>
</div>
<h2>Profile Search</h2>
</div>
{% endblock %}
{% block sidebar_search_css_class %}{% endblock %}
{% block sidebar_shortcuts_links %}{% endblock %}
{% block panel %}
<div class="sf-tabs" data-processed="true">
<div class="tab-navigation" role="tablist">
<button class="tab-control {{ 'request' == profile_type ? 'active' }}" role="tab" {{ 'request' == profile_type ? 'aria-selected="true"' : 'tabindex="-1"' }} >
<a href="{{ path('_profiler_search_results', {token: 'empty', limit: 10}) }}">
HTTP Requests
</a>
</button>
<button class="tab-control {{ 'command' == profile_type ? 'active' }}" role="tab" {{ 'command' == profile_type ? 'aria-selected="true"' : 'tabindex="-1"' }}>
<a href="{{ path('_profiler_search_results', {token: 'empty', limit: 10, type: 'command'}) }}">
Console Commands
</a>
</button>
</div>
</div>
<h2>{{ tokens ? tokens|length : 'No' }} results found</h2>
{% if tokens %}
<table id="search-results">
<thead>
<tr>
<th scope="col" class="text-center">Status</th>
<th scope="col">IP</th>
<th scope="col">Method</th>
<th scope="col">URL</th>
<th scope="col" class="text-center">
{% if 'command' == profile_type %}
Exit code
{% else %}
Status
{% endif %}
</th>
<th scope="col">
{% if 'command' == profile_type %}
Application
{% else %}
IP
{% endif %}
</th>
<th scope="col">
{% if 'command' == profile_type %}
Mode
{% else %}
Method
{% endif %}
</th>
<th scope="col">
{% if 'command' == profile_type %}
Command
{% else %}
URL
{% endif %}
</th>
<th scope="col">Time</th>
<th scope="col">Token</th>
</tr>
</thead>
<tbody>
{% for result in tokens %}
{% set css_class = result.status_code|default(0) > 399 ? 'status-error' : result.status_code|default(0) > 299 ? 'status-warning' : 'status-success' %}
{% if 'command' == profile_type %}
{% set css_class = result.status_code == 113 ? 'status-warning' : result.status_code > 0 ? 'status-error' : 'status-success' %}
{% else %}
{% set css_class = result.status_code|default(0) > 399 ? 'status-error' : result.status_code|default(0) > 299 ? 'status-warning' : 'status-success' %}
{% endif %}
<tr>
<td class="text-center">
<span class="label {{ css_class }}">{{ result.status_code|default('n/a') }}</span>
</td>
<td>
<span class="nowrap">{{ result.ip }} {{ helper.profile_search_filter(request, result, 'ip') }}</span>
<span class="nowrap">{{ result.ip }} {{ _self.profile_search_filter(request, result, 'ip') }}</span>
</td>
<td>
<span class="nowrap">{{ result.method }} {{ helper.profile_search_filter(request, result, 'method') }}</span>
<span class="nowrap">{{ result.method }} {{ _self.profile_search_filter(request, result, 'method') }}</span>
</td>
<td class="break-long-words">
{{ result.url }}
{{ helper.profile_search_filter(request, result, 'url') }}
{{ _self.profile_search_filter(request, result, 'url') }}
</td>
<td class="text-small">
<span class="nowrap">{{ result.time|date('d-M-Y') }}</span>
<span class="nowrap newline">{{ result.time|date('H:i:s') }}</span>
<time data-convert-to-user-timezone data-render-as-date datetime="{{ result.time|date('c') }}">
{{ result.time|date('d-M-Y') }}
</time>
<time class="newline" data-convert-to-user-timezone data-render-as-time datetime="{{ result.time|date('c') }}">
{{ result.time|date('H:i:s') }}
</time>
</td>
<td class="nowrap"><a href="{{ path('_profiler', { token: result.token }) }}">{{ result.token }}</a></td>
</tr>
@@ -59,7 +127,7 @@
</tbody>
</table>
{% else %}
<div class="empty">
<div class="empty empty-panel">
<p>The query returned no result.</p>
</div>
{% endif %}

View File

@@ -1,33 +1,66 @@
<div id="sidebar-search" class="hidden">
<div id="sidebar-search" class="{{ (render_hidden_by_default ?? true) ? 'hidden' }}">
<form action="{{ path('_profiler_search') }}" method="get">
<div class="form-group">
<label for="ip">IP</label>
<label for="ip">
{% if 'command' == profile_type %}
Application
{% else %}
IP
{% endif %}
</label>
<input type="text" name="ip" id="ip" value="{{ ip }}">
</div>
<div class="form-group">
<label for="method">Method</label>
<select name="method" id="method">
<option value="">Any</option>
{% for m in ['DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT'] %}
<option {{ m == method ? 'selected="selected"' }}>{{ m }}</option>
{% endfor %}
</select>
<div class="form-group-row">
<div class="form-group">
<label for="method">
{% if 'command' == profile_type %}
Mode
{% else %}
Method
{% endif %}
</label>
<select name="method" id="method">
<option value="">Any</option>
{% if 'command' == profile_type %}
{% set methods = ['BATCH', 'INTERACTIVE'] %}
{% else %}
{% set methods = ['DELETE', 'GET', 'HEAD', 'PATCH', 'POST', 'PUT'] %}
{% endif %}
{% for m in methods %}
<option {{ m == method ? 'selected="selected"' }}>{{ m }}</option>
{% endfor %}
</select>
</div>
<div class="form-group">
<label for="status_code">
{% if 'command' == profile_type %}
Exit code
{% set min_and_max = 'min=%d max=%d'|format(0, 255) %}
{% else %}
Status
{% set min_and_max = 'min=%d max=%d'|format(100, 599) %}
{% endif %}
</label>
<input type="number" name="status_code" id="status_code" {{ min_and_max }} value="{{ status_code }}">
</div>
</div>
<div class="form-group">
<label for="status_code">Status</label>
<input type="number" name="status_code" id="status_code" min="100" max="599" value="{{ status_code }}">
</div>
<div class="form-group">
<label for="url">URL</label>
<label for="url">
{% if 'command' == profile_type %}
Command
{% else %}
URL
{% endif %}
</label>
<input type="text" name="url" id="url" value="{{ url }}">
</div>
<div class="form-group">
<label for="token">Token</label>
<input type="text" name="token" id="token" value="{{ token }}">
<input type="text" name="token" id="token" size="8" value="{{ token }}">
</div>
<div class="form-group">
@@ -40,17 +73,21 @@
<input type="date" name="end" id="end" value="{{ end }}">
</div>
<div class="form-group">
<label for="limit">Results</label>
<select name="limit" id="limit">
{% for l in [10, 50, 100] %}
<option {{ l == limit ? 'selected="selected"' }}>{{ l }}</option>
{% endfor %}
</select>
</div>
<div class="form-group-row form-group-row-search-button">
<div class="form-group">
<label for="limit">Results</label>
<select name="limit" id="limit">
{% for l in [10, 50, 100] %}
<option {{ l == limit ? 'selected="selected"' }}>{{ l }}</option>
{% endfor %}
</select>
</div>
<div class="form-group">
<button type="submit" class="btn btn-sm">Search</button>
<input type="hidden" name="type" value="{{ profile_type }}">
<div class="form-group">
<button type="submit" class="btn btn-sm">Search</button>
</div>
</div>
</form>
</div>

View File

@@ -1,23 +1,63 @@
<style>
:root {
--settings-modal-shadow: 0 0 0 1px var(--gray-400), 5px 5px 20px 0 var(--gray-800);
--settings-modal-header-background: var(--gray-200);
--settings-modal-content-background: var(--gray-100);
--settings-option-background: var(--page-background);
--settings-option-border-color: var(--gray-300);
--settings-option-color: var(--color-text);
--settings-option-icon-color: var(--gray-400);
--settings-option-active-border-color: #3b82f6;
--settings-option-active-background: #eff6ff;
--settings-option-active-color: var(--color-text);
--settings-option-active-icon-color: var(--gray-400);
}
.theme-dark {
--settings-modal-shadow: 0 0 0 1px var(--gray-600), 5px 5px 10px 0 var(--gray-900);
--settings-modal-header-background: var(--gray-800);
--settings-modal-content-background: var(--gray-700);
--settings-option-background: transparent;
--settings-option-border-color: var(--gray-500);
--settings-option-color: var(--color-text);
--settings-option-icon-color: var(--gray-300);
--settings-option-active-border-color: #93c5fd;
--settings-option-active-background: var(--gray-700);
--settings-option-active-color: var(--color-text);
--settings-option-active-icon-color: #93c5fd;
}
#open-settings {
color: var(--color-muted);
display: block;
margin: 15px 15px 5px;
align-items: center;
display: flex;
margin: 10px 0 5px;
}
#open-settings .icon {
margin-right: 4px;
}
#open-settings .icon, #open-settings svg {
height: 18px;
width: 18px;
}
.modal-wrap {
-webkit-transition: all 0.3s ease-in-out;
-webkit-transition-duration: 0.3s;
-webkit-transition-property: opacity, visibility;
-webkit-transition-timing-function: ease-in-out;
align-items: center;
background: rgba(0, 0, 0, 0.8);
background: rgba(0, 0, 0, 0.70);
display: flex;
backdrop-filter: blur(2px);
height: 100%;
justify-content: center;
left: 0;
opacity: 1;
opacity: 0;
overflow: auto;
position: fixed;
top: 0;
transition: all 0.3s ease-in-out;
transition-duration: 0.3s;
transition-property: opacity, visibility;
transition-timing-function: ease-in-out;
visibility: hidden;
width: 100%;
z-index: 100000;
@@ -27,8 +67,9 @@
visibility: visible;
}
.modal-wrap .modal-container {
box-shadow: 5px 5px 10px 0px rgba(0, 0, 0, 0.5);
color: var(--base-6);
border-radius: 6px;
box-shadow: var(--settings-modal-shadow);
color: var(--color-text);
margin: 1em;
max-width: 94%;
width: 600px;
@@ -36,19 +77,20 @@
.modal-wrap .modal-header {
align-items: center;
background: var(--table-header);
background: var(--settings-modal-header-background);
border-top-left-radius: 6px;
border-top-right-radius: 6px;
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);
color: var(--color-muted);
cursor: pointer;
font-size: 28px;
line-height: 1;
@@ -57,32 +99,85 @@
.modal-wrap .modal-header .close-modal:hover { opacity: 1; }
.modal-wrap .modal-content {
background: var(--base-1);
background: var(--settings-modal-content-background);
border-bottom-left-radius: 6px;
border-bottom-right-radius: 6px;
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;
font-size: 18px;
margin: 0 0 10px;
}
.modal-content input, .modal-content .form-help {
margin-left: 15px;
.modal-content .settings-group + h4 {
margin-top: 30px;
}
.modal-content label {
.modal-content .settings-group {
border: 1px solid var(--settings-option-border-color);
border-radius: 4px;
display: flex;
margin-bottom: 15px;
}
.modal-content .settings-group label {
cursor: pointer;
display: flex;
flex: 1;
font-size: 16px;
margin-left: 3px;
margin: 0;
}
.modal-content .form-help {
color: var(--color-muted);
.modal-content .settings-group label input {
position: absolute;
clip: rect(0, 0, 0, 0);
pointer-events: none;
opacity: 0;
}
.modal-content .settings-group:has(input:focus-visible) {
outline: 2px dotted var(--settings-option-active-border-color);
outline-offset: 2px;
}
.modal-content .settings-group label input:checked + p {
box-shadow: inset 0 0 0 2px var(--settings-option-active-border-color);
background-color: var(--settings-option-active-background);
color: var(--settings-option-active-color);
}
.modal-content .settings-group label input:checked + p svg {
color: var(--settings-option-active-icon-color);
}
.modal-content .settings-group label p {
align-items: center;
background: var(--settings-option-background);
color: var(--settings-option-color);
flex: 1;
font-size: 14px;
margin: 5px 0 15px 33px;
margin: 0;
padding: 10px 15px;
text-align: center;
}
.modal-content .form-help + h4 {
margin-top: 45px;
.modal-content .settings-group label:first-child p {
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}
.modal-content .settings-group label:last-child p {
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
.modal-content .settings-group label + label p {
border-left: 1px solid var(--settings-option-border-color);
}
.modal-content label p span {
display: block;
font-size: 16px;
}
.modal-content label p abbr {
border: 0;
text-decoration: none;
}
.modal-content label svg {
color: var(--settings-option-icon-color);
height: 36px;
width: 36px;
}
@media (max-width: 768px) {
@@ -92,51 +187,69 @@
#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>
<a href="#" id="open-settings">
<span class="icon">{{ source('@WebProfiler/Icon/settings.svg') }}</span>
Profiler 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>
<button aria-label="Close" 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>
<div class="settings-group">
<label for="settings-theme-auto">
<input class="config-option" type="radio" name="theme" value="auto" id="settings-theme-auto">
<p>
{{ source('@WebProfiler/Icon/settings-theme-system.svg') }}
<span>System / OS</span>
</p>
</label>
<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>
<label for="settings-theme-light">
<input class="config-option" type="radio" name="theme" value="light" id="settings-theme-light">
<p>
{{ source('@WebProfiler/Icon/settings-theme-light.svg') }}
<span>Light</span>
</p>
</label>
<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>
<label for="settings-theme-dark">
<input class="config-option" type="radio" name="theme" value="dark" id="settings-theme-dark">
<p>
{{ source('@WebProfiler/Icon/settings-theme-dark.svg') }}
<span>Dark</span>
</p>
</label>
</div>
<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>
<div class="settings-group">
<label for="settings-width-normal">
<input class="config-option" type="radio" name="width" value="normal" id="settings-width-normal">
<p>
{{ source('@WebProfiler/Icon/settings-width-fixed.svg') }}
<span>Fixed width</span>
</p>
</label>
<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>
<label for="settings-width-full">
<input class="config-option" type="radio" name="width" value="full" id="settings-width-full">
<p>
{{ source('@WebProfiler/Icon/settings-width-fitted.svg') }}
<span>Fit to window</span>
</p>
</label>
</div>
</div>
</div>
</div>
@@ -144,7 +257,6 @@
<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;
@@ -160,10 +272,13 @@
}
});
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 resolvedSettingValue = 'theme-auto' === settingValue
? (matchMedia('(prefers-color-scheme: dark)').matches ? 'theme-dark' : 'theme-light')
: settingValue;
document.body.classList.add(resolvedSettingValue);
if (resolvedSettingValue.startsWith('theme-')) {
document.body.style.colorScheme = resolvedSettingValue.endsWith('-light') ? 'light' : 'dark';
}
});
});
@@ -171,22 +286,30 @@
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]
const modalWrapper = document.getElementsByClassName('modal-wrap')[0];
const closeModal = () => {
modalWindow.classList.remove('visible');
setTimeout(() => openModalButton.focus(), 30);
};
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');
setTimeout(() => closeModalButton.focus(), 30);
event.preventDefault();
});
closeModalButton.addEventListener('click', function() {
modalWindow.classList.remove('visible');
});
closeModalButton.addEventListener('click', closeModal);
modalWrapper.addEventListener('click', function(event) {
if (event.target == event.currentTarget) {
modalWindow.classList.remove('visible');
closeModal();
}
});
modalWrapper.addEventListener('keydown', function(event) {
if (event.key === 'Esc' || event.key === 'Escape') {
closeModal();
}
});
})();

View File

@@ -1,8 +1,53 @@
{# when updating any of these colors, do the same in profiler.css.twig #}
{% set colors = { 'success': '#4F805D', 'warning': '#A46A1F', 'error': '#B0413E' } %}
.sf-toolbarreset {
--sf-toolbar-font-family-system: system-ui, -apple-system, "Segoe UI", Roboto, "Helvetica Neue", Arial, "Noto Sans", "Liberation Sans", sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol", "Noto Color Emoji";
--sf-toolbar-font-family-monospace: "Ubuntu Mono", "JetBrains Mono", ui-monospace, "Roboto Mono", SFMono-Regular, Menlo, Monaco, Consolas,"Liberation Mono", "Courier New", monospace;
--sf-toolbar-white: #fff;
--sf-toolbar-black: #000;
--sf-toolbar-gray-50: #fafafa;
--sf-toolbar-gray-100: #f5f5f5;
--sf-toolbar-gray-200: #e5e5e5;
--sf-toolbar-gray-300: #d4d4d4;
--sf-toolbar-gray-400: #a3a3a3;
--sf-toolbar-gray-500: #737373;
--sf-toolbar-gray-600: #525252;
--sf-toolbar-gray-700: #404040;
--sf-toolbar-gray-800: #262626;
--sf-toolbar-gray-900: #171717;
--sf-toolbar-red-50: #FEFBFC;
--sf-toolbar-red-100: #FCE9ED;
--sf-toolbar-red-200: #F5B8C5;
--sf-toolbar-red-300: #EF869C;
--sf-toolbar-red-400: #E85574;
--sf-toolbar-red-500: #E1244B;
--sf-toolbar-red-600: #B41939;
--sf-toolbar-red-700: #83122A;
--sf-toolbar-red-800: #510B1A;
--sf-toolbar-red-900: #20040A;
--sf-toolbar-yellow-50: #fef7e1;
--sf-toolbar-yellow-100: #fef2cd;
--sf-toolbar-yellow-200: #fde496;
--sf-toolbar-yellow-300: #fcd55f;
--sf-toolbar-yellow-400: #fbc728;
--sf-toolbar-yellow-500: #e6af05;
--sf-toolbar-yellow-600: #af8503;
--sf-toolbar-yellow-700: #785b02;
--sf-toolbar-yellow-800: #413101;
--sf-toolbar-yellow-900: #0a0800;
--sf-toolbar-green-50: #eff5f5;
--sf-toolbar-green-100: #deeaea;
--sf-toolbar-green-200: #bbd5d5;
--sf-toolbar-green-300: #99bfbf;
--sf-toolbar-green-400: #76a9a9;
--sf-toolbar-green-500: #598e8e;
--sf-toolbar-green-600: #436c6c;
--sf-toolbar-green-700: #2e4949;
--sf-toolbar-green-800: #182727;
--sf-toolbar-green-900: #030404;
}
.sf-minitoolbar {
background-color: #222;
background-color: var(--sf-toolbar-gray-800);
border-top-left-radius: 4px;
bottom: 0;
box-sizing: border-box;
@@ -21,6 +66,7 @@
}
.sf-minitoolbar svg,
.sf-minitoolbar img {
color: var(--sf-toolbar-gray-200);
max-height: 24px;
max-width: 24px;
display: inline;
@@ -35,7 +81,7 @@
display: none;
}
.sf-toolbarreset * {
.sf-toolbarreset *:not(svg rect) {
box-sizing: content-box;
vertical-align: baseline;
letter-spacing: normal;
@@ -43,11 +89,11 @@
}
.sf-toolbarreset {
background-color: #222;
background-color: var(--sf-toolbar-gray-800);
bottom: 0;
box-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2);
color: #EEE;
font: 11px Arial, sans-serif;
box-shadow: inset 0 1px 0 var(--sf-toolbar-black), 0 -1px 0 rgba(0, 0, 0, 0.5);
color: var(--sf-toolbar-gray-200);
font: 11px var(--sf-toolbar-font-family-system);
left: 0;
margin: 0;
padding: 0 36px 0 0;
@@ -63,7 +109,7 @@
-moz-osx-font-smoothing: auto;
}
.sf-toolbarreset abbr {
border: dashed #777;
border: dashed var(--sf-toolbar-gray-500);
border-width: 0 0 1px;
}
.sf-toolbarreset svg,
@@ -74,23 +120,27 @@
}
.sf-toolbarreset .sf-cancel-button {
color: #444;
color: var(--sf-toolbar-gray-700);
}
.sf-toolbarreset .hide-button {
background: #444;
background: var(--sf-toolbar-gray-800);
color: var(--sf-toolbar-gray-300);
display: block;
position: absolute;
top: 0;
top: 2px;
right: 0;
width: 36px;
height: 36px;
height: 34px;
cursor: pointer;
text-align: center;
border: none;
margin: 0;
padding: 0;
}
.sf-toolbarreset .hide-button:hover {
background: var(--sf-toolbar-gray-700);
}
.sf-toolbarreset .hide-button svg {
max-height: 18px;
margin-top: 1px;
@@ -102,6 +152,7 @@
float: left;
height: 36px;
margin-right: 0;
position: relative;
white-space: nowrap;
max-width: 15%;
}
@@ -117,15 +168,16 @@
display: inline-block;
}
.sf-toolbar-block .sf-toolbar-value {
color: #F5F5F5;
color: var(--sf-toolbar-gray-100);
font-size: 13px;
line-height: 36px;
padding: 0;
}
.sf-toolbar-block .sf-toolbar-label,
.sf-toolbar-block .sf-toolbar-class-separator {
color: #AAA;
color: var(--sf-toolbar-gray-400);
font-size: 12px;
margin-left: 2px;
}
.sf-toolbar-block .sf-toolbar-info {
@@ -134,7 +186,7 @@
z-index: 100000;
}
.sf-toolbar-block hr {
border-top: 1px solid #777;
border-top: 1px solid var(--sf-toolbar-gray-500);
margin: 4px 0;
padding-top: 4px;
}
@@ -159,6 +211,7 @@
}
.sf-toolbar-block .sf-toolbar-info-piece .sf-toolbar-status {
border-radius: 4px;
padding: 2px 5px;
margin-bottom: 0;
}
@@ -179,73 +232,101 @@ div.sf-toolbar .sf-toolbar-block a:hover {
}
.sf-toolbar-block .sf-toolbar-info-piece b {
color: #AAA;
color: var(--sf-toolbar-gray-400);
display: table-cell;
font-size: 11px;
padding: 4px 8px 4px 0;
}
.sf-toolbar-block:not(.sf-toolbar-block-dump) .sf-toolbar-info-piece span {
color: #F5F5F5;
color: var(--sf-toolbar-gray-100);
}
.sf-toolbar-block .sf-toolbar-info-piece span {
font-size: 12px;
}
div.sf-toolbar .sf-toolbar-block .sf-toolbar-info-piece.sf-toolbar-info-php-ext a {
text-decoration: none;
}
.sf-toolbar-block .sf-toolbar-info {
background-color: #444;
background-color: var(--sf-toolbar-gray-700);
border-radius: 4px;
border-bottom-left-radius: 0;
bottom: 36px;
color: #F5F5F5;
color: var(--sf-toolbar-gray-100);
display: none;
padding: 9px 0;
position: absolute;
}
.sf-toolbar-block.sf-toolbar-block-right .sf-toolbar-info {
border-bottom-left-radius: 4px;
border-bottom-right-radius: 0;
}
.sf-toolbar-block .sf-toolbar-info:empty {
visibility: hidden;
}
.sf-toolbar-block .sf-toolbar-status {
display: inline-block;
color: #FFF;
background-color: #666;
color: var(--sf-toolbar-white);
background-color: var(--sf-toolbar-gray-600);
padding: 3px 6px;
margin-bottom: 2px;
vertical-align: middle;
margin: 0 4px;
min-width: 15px;
min-height: 13px;
text-align: center;
}
.sf-toolbar-block .sf-toolbar-status-green {
background-color: {{ colors.success|raw }};
.sf-toolbar-block .sf-toolbar-status.sf-toolbar-status-green,
.sf-toolbar-block .sf-toolbar-info .sf-toolbar-status.sf-toolbar-status-green {
background-color: #059669;
color: var(--white);
}
.sf-toolbar-block .sf-toolbar-status-red {
background-color: {{ colors.error|raw }};
.sf-toolbar-block .sf-toolbar-status.sf-toolbar-status-red,
.sf-toolbar-block .sf-toolbar-info .sf-toolbar-status.sf-toolbar-status-red {
background-color: var(--sf-toolbar-red-500);
color: var(--sf-toolbar-red-50);
}
.sf-toolbar-block .sf-toolbar-status-yellow {
background-color: {{ colors.warning|raw }};
.sf-toolbar-block .sf-toolbar-status.sf-toolbar-status-yellow,
.sf-toolbar-block .sf-toolbar-info .sf-toolbar-status.sf-toolbar-status-yellow {
background-color: var(--sf-toolbar-yellow-300);
color: var(--sf-toolbar-yellow-800);
}
.sf-toolbar-block.sf-toolbar-status-green {
background-color: {{ colors.success|raw }};
color: #FFF;
.sf-toolbar-block.sf-toolbar-status-green::before,
.sf-toolbar-block.sf-toolbar-status-red::before,
.sf-toolbar-block.sf-toolbar-status-yellow::before {
background: var(--sf-toolbar-yellow-400);
border-radius: 6px;
content: '';
position: absolute;
bottom: 1px;
left: 0;
width: 98%;
height: 3px;
z-index: 10005;
}
.sf-toolbar-block.sf-toolbar-status-red {
background-color: {{ colors.error|raw }};
color: #FFF;
.sf-toolbar-block.sf-toolbar-status-red::before {
background: var(--sf-toolbar-red-400);
}
.sf-toolbar-block.sf-toolbar-status-yellow {
background-color: {{ colors.warning|raw }};
color: #FFF;
.sf-toolbar-block.sf-toolbar-status-green::before {
background: var(--sf-toolbar-green-400);
}
.sf-toolbar-block-request.sf-toolbar-block.sf-toolbar-status-green::before,
.sf-toolbar-block-request.sf-toolbar-block.sf-toolbar-status-red::before,
.sf-toolbar-block-request.sf-toolbar-block.sf-toolbar-status-yellow::before {
display: none;
}
.sf-toolbar-block-request .sf-toolbar-status {
color: #FFF;
border-radius: 6px;
color: #fff;
display: inline-block;
font-size: 14px;
height: 36px;
line-height: 36px;
padding: 0 10px;
flex-shrink: 0;
font-size: 13px;
font-weight: 500;
padding: 4px 8px;
}
.sf-toolbar-block-request .sf-toolbar-info-piece a {
background-color: transparent;
@@ -259,12 +340,27 @@ div.sf-toolbar .sf-toolbar-block a:hover {
padding: 2px 4px;
line-height: 18px;
}
.sf-toolbar-block.sf-toolbar-block-request .sf-toolbar-redirection-status.sf-toolbar-status-yellow {
background-color: var(--sf-toolbar-yellow-300);
border-radius: 4px;
color: var(--sf-toolbar-yellow-800);
padding: 1px 4px;
}
.sf-toolbar-block.sf-toolbar-block-request .sf-toolbar-info-piece .sf-toolbar-redirection-method {
background: transparent;
color: var(--sf-toolbar-gray-300);
border: 1px solid var(--sf-toolbar-gray-400);
padding: 1px 4px;
}
.sf-toolbar-block-request .sf-toolbar-info-piece span.sf-toolbar-redirection-method {
font-size: 12px;
height: 17px;
line-height: 17px;
margin-right: 5px;
}
.sf-toolbar-block-request .sf-toolbar-request-icon svg {
stroke-width: 3px;
}
.sf-toolbar-block-ajax .sf-toolbar-icon {
cursor: pointer;
@@ -273,28 +369,31 @@ div.sf-toolbar .sf-toolbar-block a:hover {
.sf-toolbar-status-green .sf-toolbar-label,
.sf-toolbar-status-yellow .sf-toolbar-label,
.sf-toolbar-status-red .sf-toolbar-label {
color: #FFF;
}
.sf-toolbar-status-green svg path,
.sf-toolbar-status-green svg .sf-svg-path,
.sf-toolbar-status-red svg path,
.sf-toolbar-status-red svg .sf-svg-path,
.sf-toolbar-status-yellow svg path,
.sf-toolbar-status-yellow svg .sf-svg-path {
fill: #FFF;
color: var(--sf-toolbar-white);
}
.sf-toolbar-block-config svg path,
.sf-toolbar-block-config svg .sf-svg-path {
fill: #FFF;
fill: var(--sf-toolbar-white);
}
.sf-toolbar-block .sf-toolbar-icon {
display: block;
color: var(--sf-toolbar-gray-300);
align-items: center;
display: flex;
height: 36px;
padding: 0 7px;
overflow: hidden;
text-overflow: ellipsis;
}
.sf-toolbar-block:hover .sf-toolbar-icon {
border-bottom-left-radius: 4px;
border-bottom-right-radius: 4px;
box-shadow: 1px 0 0 var(--sf-toolbar-black), inset 0 -1px 0 var(--sf-toolbar-black);
}
.sf-toolbar-block.sf-toolbar-block-right:hover .sf-toolbar-icon {
box-shadow: -1px 0 0 var(--sf-toolbar-black), inset 0 -1px 0 var(--sf-toolbar-black);
}
.sf-toolbar-block-request .sf-toolbar-icon {
padding-left: 0;
padding-right: 0;
@@ -303,17 +402,19 @@ div.sf-toolbar .sf-toolbar-block a:hover {
.sf-toolbar-block .sf-toolbar-icon img,
.sf-toolbar-block .sf-toolbar-icon svg {
border-width: 0;
position: relative;
top: 8px;
vertical-align: baseline;
}
.sf-toolbar-block .sf-toolbar-icon img + span,
.sf-toolbar-block .sf-toolbar-icon svg + span {
margin-left: 4px;
}
.sf-toolbar-block-config .sf-toolbar-icon .sf-toolbar-value {
margin-left: 4px;
.sf-toolbar-block-config .sf-toolbar-icon .sf-toolbar-value,
.sf-toolbar-block.sf-toolbar-block-sf-cli .sf-toolbar-value {
margin-left: 5px;
}
.sf-toolbar-block-config .sf-toolbar-icon .sf-toolbar-label,
.sf-toolbar-block.sf-toolbar-block-sf-cli .sf-toolbar-label {
margin-left: 0;
}
.sf-toolbar-block:hover,
@@ -322,7 +423,7 @@ div.sf-toolbar .sf-toolbar-block a:hover {
}
.sf-toolbar-block:hover .sf-toolbar-icon,
.sf-toolbar-block.hover .sf-toolbar-icon {
background-color: #444;
background-color: var(--sf-toolbar-gray-700);
position: relative;
z-index: 10002;
}
@@ -340,26 +441,29 @@ div.sf-toolbar .sf-toolbar-block a:hover {
overflow-y: auto;
}
.sf-toolbar-info-piece b.sf-toolbar-ajax-info {
color: #F5F5F5;
color: var(--sf-toolbar-gray-100);
}
.sf-toolbar-ajax-requests {
table-layout: auto;
border: 1px solid var(--sf-toolbar-gray-500);
font-variant: tabular-nums;
margin: 5px 0 0;
width: 100%;
}
.sf-toolbar-ajax-requests td {
background-color: #444;
border-bottom: 1px solid #777;
color: #F5F5F5;
background-color: var(--sf-toolbar-gray-700);
border: 1px solid var(--sf-toolbar-gray-500);
color: var(--sf-toolbar-gray-100);
font-size: 12px;
padding: 4px;
vertical-align: middle;
}
.sf-toolbar-ajax-requests tr:last-child td {
border-bottom: 0;
.sf-toolbar-ajax-requests thead {
border: 0;
}
.sf-toolbar-ajax-requests th {
background-color: #222;
border-bottom: 0;
color: #AAA;
background-color: var(--sf-toolbar-gray-800);
border: 1px solid var(--sf-toolbar-gray-500);
color: var(--sf-toolbar-gray-200);
font-size: 11px;
padding: 4px;
}
@@ -378,13 +482,17 @@ div.sf-toolbar .sf-toolbar-block a:hover {
.sf-ajax-request-duration {
text-align: right;
}
.sf-toolbar-block .sf-toolbar-info-piece .sf-toolbar-ajax-requests .sf-toolbar-status {
font-size: 11px;
padding: 1px 3px;
}
.sf-ajax-request-loading {
animation: sf-blink .5s ease-in-out infinite;
}
@keyframes sf-blink {
0% { background: #222; }
50% { background: #444; }
100% { background: #222; }
0% { background: var(--sf-toolbar-gray-800); }
50% { background: var(--sf-toolbar-gray-700); }
100% { background: var(--sf-toolbar-gray-800); }
}
.sf-toolbar-block.sf-toolbar-block-dump .sf-toolbar-info {
@@ -396,8 +504,8 @@ div.sf-toolbar .sf-toolbar-block a:hover {
}
.sf-toolbar-block-dump pre.sf-dump {
background-color: #222;
border-color: #777;
background-color: var(--sf-toolbar-gray-800);
border-color: var(--sf-toolbar-gray-500);
border-radius: 0;
margin: 6px 0 12px 0;
}
@@ -415,45 +523,48 @@ div.sf-toolbar .sf-toolbar-block a:hover {
display: block;
}
.sf-toolbar-block-dump .sf-toolbar-info-piece .sf-toolbar-file-line {
color: #AAA;
color: var(--sf-toolbar-gray-400);
margin-left: 4px;
}
.sf-toolbar-block-dump .sf-toolbar-info img {
display: none;
}
.sf-toolbar-block-serializer .detailed-metrics {
display: grid;
grid-template-columns: repeat(3, 1fr);
grid-gap: 15px;
margin-top: 15px;
}
/* Responsive Design */
.sf-toolbar-icon .sf-toolbar-label,
.sf-toolbar-icon .sf-toolbar-value {
display: none;
}
.sf-toolbar-block-config .sf-toolbar-icon .sf-toolbar-label {
.sf-toolbar-block-config .sf-toolbar-icon .sf-toolbar-label,
.sf-cli .sf-toolbar-icon .sf-toolbar-label {
display: inline-block;
}
/* Legacy Design - these styles are maintained to make old panels look
a bit better on the new toolbar */
.sf-toolbar-block .sf-toolbar-info-piece-additional-detail {
color: #AAA;
color: var(--sf-toolbar-gray-400);
font-size: 12px;
}
.sf-toolbar-status-green .sf-toolbar-info-piece-additional-detail,
.sf-toolbar-status-yellow .sf-toolbar-info-piece-additional-detail,
.sf-toolbar-status-red .sf-toolbar-info-piece-additional-detail {
color: #FFF;
color: var(--sf-toolbar-white);
}
@media (min-width: 768px) {
.sf-toolbar-icon .sf-toolbar-label,
.sf-toolbar-icon .sf-toolbar-value {
display: inline;
}
.sf-toolbar-block .sf-toolbar-icon img,
.sf-toolbar-block .sf-toolbar-icon svg {
top: 6px;
}
.sf-toolbar-block-time .sf-toolbar-icon svg,
.sf-toolbar-block-memory .sf-toolbar-icon svg {
display: none;
@@ -473,20 +584,24 @@ div.sf-toolbar .sf-toolbar-block a:hover {
padding-left: 5px;
}
.sf-toolbar-block-request .sf-toolbar-icon {
display: flex;
align-items: center;
padding-left: 0;
padding-right: 0;
}
.sf-toolbar-block-request .sf-toolbar-label {
margin-left: 4px;
margin-right: 1px;
}
.sf-toolbar-block-request .sf-toolbar-status + .sf-toolbar-request-icon {
display: inline-flex;
margin-left: 5px;
}
.sf-toolbar-block-request .sf-toolbar-status + svg {
margin-left: 5px;
}
.sf-toolbar-block-request .sf-toolbar-icon svg + .sf-toolbar-label {
.sf-toolbar-block-request .sf-toolbar-icon .sf-toolbar-request-icon + .sf-toolbar-label {
margin-left: 0;
}
.sf-toolbar-block-request .sf-toolbar-label + .sf-toolbar-value {
margin-right: 10px;
margin-right: 5px;
}
.sf-toolbar-block-request:hover .sf-toolbar-info {
@@ -505,6 +620,10 @@ div.sf-toolbar .sf-toolbar-block a:hover {
margin-left: 0;
margin-right: 0;
}
.sf-toolbarreset .sf-toolbar-block.sf-toolbar-block-right:not(.sf-toolbar-block-sf-cli) .sf-toolbar-info {
border-bottom-left-radius: 4px;
border-bottom-right-radius: 0;
}
}
@media (min-width: 1024px) {
@@ -521,9 +640,9 @@ div.sf-toolbar .sf-toolbar-block a:hover {
/***** Error Toolbar *****/
.sf-error-toolbar .sf-toolbarreset {
background: #222;
color: #f5f5f5;
font: 13px/36px Arial, sans-serif;
background: var(--sf-toolbar-gray-800);
color: var(--sf-toolbar-gray-100);
font: 13px/36px var(--sf-toolbar-font-family-system);
height: 36px;
padding: 0 15px;
text-align: left;

View File

@@ -1,12 +1,12 @@
<!-- START of Symfony Web Debug Toolbar -->
<div id="sfMiniToolbar-{{ token }}" class="sf-minitoolbar" data-no-turbolink>
<div id="sfMiniToolbar-{{ token }}" class="sf-minitoolbar" data-no-turbolink data-turbo="false">
<button type="button" title="Show Symfony toolbar" id="sfToolbarMiniToggler-{{ token }}" accesskey="D" aria-expanded="false" aria-controls="sfToolbarMainContent-{{ token }}">
{{ include('@WebProfiler/Icon/symfony.svg') }}
{{ source('@WebProfiler/Icon/symfony.svg') }}
</button>
</div>
<div id="sfToolbarClearer-{{ token }}" class="sf-toolbar-clearer"></div>
<div id="sfToolbarMainContent-{{ token }}" class="sf-toolbarreset clear-fix" data-no-turbolink>
<div id="sfToolbarMainContent-{{ token }}" class="sf-toolbarreset notranslate clear-fix" data-no-turbolink>
{% for name, template in templates %}
{% if block('toolbar', template) is defined %}
{% with {
@@ -40,7 +40,7 @@
{% 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') }}
{{ source('@WebProfiler/Icon/close.svg') }}
</button>
</div>
<!-- END of Symfony Web Debug Toolbar -->

View File

@@ -1,21 +1,681 @@
<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 {
{{ include('@WebProfiler/Profiler/toolbar.html.twig', {
templates: {
'request': '@WebProfiler/Profiler/cancel.html.twig'
},
profile: null,
profiler_url: url('_profiler', {token: token}),
profiler_markup_version: 2,
} %}
profiler_markup_version: 3,
}) }}
</div>
{{ include('@WebProfiler/Profiler/base_js.html.twig') }}
<style{% if csp_style_nonce %} nonce="{{ csp_style_nonce }}"{% endif %}>
{{ include('@WebProfiler/Profiler/toolbar.css.twig') }}
</style>
<script{% if csp_script_nonce %} nonce="{{ csp_script_nonce }}"{% endif %}>/*<![CDATA[*/
(function () {
Sfjs.loadToolbar('{{ token }}');
})();
{# 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 #}
<script{% if csp_script_nonce is defined and csp_script_nonce %} nonce="{{ csp_script_nonce }}"{% endif %}>/*<![CDATA[*/
if (typeof Sfjs === 'undefined' || typeof Sfjs.loadToolbar === 'undefined') {
Sfjs = (function() {
"use strict";
if ('classList' in document.documentElement) {
var hasClass = function (el, cssClass) { return el.classList.contains(cssClass); };
var removeClass = function(el, cssClass) { el.classList.remove(cssClass); };
var addClass = function(el, cssClass) { el.classList.add(cssClass); };
var toggleClass = function(el, cssClass) { el.classList.toggle(cssClass); };
} else {
var hasClass = function (el, cssClass) { return el.className.match(new RegExp('\\b' + cssClass + '\\b')); };
var removeClass = function(el, cssClass) { el.className = el.className.replace(new RegExp('\\b' + cssClass + '\\b'), ' '); };
var addClass = function(el, cssClass) { if (!hasClass(el, cssClass)) { el.className += " " + cssClass; } };
var toggleClass = function(el, cssClass) { hasClass(el, cssClass) ? removeClass(el, cssClass) : addClass(el, cssClass); };
}
var noop = function() {};
var profilerStorageKey = 'symfony/profiler/';
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.addEventListener('readystatechange', () => {
if (document.readyState !== 'complete') {
return;
}
document.querySelectorAll('[data-clipboard-text]').forEach(function (element) {
removeClass(element, 'hidden');
element.addEventListener('click', function () {
navigator.clipboard.writeText(element.getAttribute('data-clipboard-text'));
if (element.classList.contains("label")) {
let oldContent = element.textContent;
element.textContent = "✅ Copied!";
element.classList.add("status-success");
setTimeout(() => {
element.textContent = oldContent;
element.classList.remove("status-success");
}, 7000);
}
});
});
});
}
var request = function(url, onSuccess, onError, payload, options, tries) {
var xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');
options = options || {};
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) {
if (4 !== xhr.readyState) {
return null;
}
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;
}
if (200 === xhr.status) {
(onSuccess || noop)(xhr);
} else {
(onError || noop)(xhr);
}
};
if (options.onSend) {
options.onSend(tries);
}
xhr.send(payload || '');
};
var getPreference = function(name) {
if (!window.localStorage) {
return null;
}
return localStorage.getItem(profilerStorageKey + name);
};
var setPreference = function(name, value) {
if (!window.localStorage) {
return null;
}
localStorage.setItem(profilerStorageKey + name, value);
};
var requestStack = [];
var extractHeaders = function(xhr, stackElement) {
/* Here we avoid to call xhr.getResponseHeader in order to */
/* prevent polluting the console with CORS security errors */
var allHeaders = xhr.getAllResponseHeaders();
var ret;
if (ret = allHeaders.match(/^x-debug-token:\s+(.*)$/im)) {
stackElement.profile = ret[1];
}
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;
var pendingRequests = 0;
var renderAjaxRequests = function() {
var requestCounter = document.querySelector('.sf-toolbar-ajax-request-counter');
if (!requestCounter) {
return;
}
requestCounter.textContent = requestStack.length;
var infoSpan = document.querySelector(".sf-toolbar-ajax-info");
if (infoSpan) {
infoSpan.textContent = requestStack.length + ' AJAX request' + (requestStack.length !== 1 ? 's' : '');
}
var ajaxToolbarPanel = document.querySelector('.sf-toolbar-block-ajax');
if (requestStack.length) {
ajaxToolbarPanel.style.display = 'block';
} else {
ajaxToolbarPanel.style.display = 'none';
}
if (pendingRequests > 0) {
addClass(ajaxToolbarPanel, 'sf-ajax-request-loading');
} else if (successStreak < 4) {
addClass(ajaxToolbarPanel, 'sf-toolbar-status-red');
removeClass(ajaxToolbarPanel, 'sf-ajax-request-loading');
} else {
removeClass(ajaxToolbarPanel, 'sf-ajax-request-loading');
removeClass(ajaxToolbarPanel, 'sf-toolbar-status-red');
}
};
var startAjaxRequest = function(index) {
var tbody = document.querySelector('.sf-toolbar-ajax-request-list');
if (!tbody) {
return;
}
var nbOfAjaxRequest = tbody.rows.length;
if (nbOfAjaxRequest >= 100) {
tbody.deleteRow(0);
}
var request = requestStack[index];
pendingRequests++;
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);
var typeCell = document.createElement('td');
typeCell.textContent = request.type;
row.appendChild(typeCell);
var statusCodeCell = document.createElement('td');
var statusCode = document.createElement('span');
statusCode.textContent = 'n/a';
statusCodeCell.appendChild(statusCode);
row.appendChild(statusCodeCell);
var pathCell = document.createElement('td');
pathCell.className = 'sf-ajax-request-url';
if ('GET' === request.method) {
var pathLink = document.createElement('a');
pathLink.setAttribute('href', request.url);
pathLink.textContent = request.url;
pathCell.appendChild(pathLink);
} else {
pathCell.textContent = request.url;
}
pathCell.setAttribute('title', request.url);
row.appendChild(pathCell);
var durationCell = document.createElement('td');
durationCell.className = 'sf-ajax-request-duration';
durationCell.textContent = 'n/a';
row.appendChild(durationCell);
request.liveDurationHandle = setInterval(function() {
durationCell.textContent = (new Date() - request.start) + ' ms';
}, 100);
row.className = 'sf-ajax-request sf-ajax-request-loading';
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 profilerCell = row.children[1];
var methodCell = row.children[2];
var statusCodeCell = row.children[4];
var statusCodeElem = statusCodeCell.children[0];
var durationCell = row.children[6];
if (request.error) {
row.className = 'sf-ajax-request sf-ajax-request-error';
methodCell.className = 'sf-ajax-request-error';
successStreak = 0;
} else {
row.className = 'sf-ajax-request sf-ajax-request-ok';
successStreak++;
}
if (request.statusCode) {
if (request.statusCode < 300) {
statusCodeElem.setAttribute('class', 'sf-toolbar-status');
} else if (request.statusCode < 400) {
statusCodeElem.setAttribute('class', 'sf-toolbar-status sf-toolbar-status-yellow');
} else {
statusCodeElem.setAttribute('class', 'sf-toolbar-status sf-toolbar-status-red');
}
statusCodeElem.textContent = request.statusCode;
} else {
statusCodeElem.setAttribute('class', 'sf-toolbar-status sf-toolbar-status-red');
}
if (request.duration) {
durationCell.textContent = request.duration + ' ms';
}
if (request.profilerUrl) {
profilerCell.textContent = '';
var profilerLink = document.createElement('a');
profilerLink.setAttribute('href', request.profilerUrl);
profilerLink.textContent = request.profile;
profilerCell.appendChild(profilerLink);
}
renderAjaxRequests();
};
{% if excluded_ajax_paths is defined %}
if (window.fetch && window.fetch.polyfill === undefined) {
var oldFetch = window.fetch;
window.fetch = function () {
var promise = oldFetch.apply(this, arguments);
var url = arguments[0];
var params = arguments[1];
var paramType = Object.prototype.toString.call(arguments[0]);
if (paramType === '[object Request]') {
url = arguments[0].url;
params = {
method: arguments[0].method,
credentials: arguments[0].credentials,
headers: arguments[0].headers,
mode: arguments[0].mode,
redirect: arguments[0].redirect
};
} else {
url = String(url);
}
if (!url.match(new RegExp({{ excluded_ajax_paths|json_encode|raw }}))) {
var method = 'GET';
if (params && params.method !== undefined) {
method = params.method;
}
var stackElement = {
error: false,
url: url,
method: method,
type: 'fetch',
start: new Date()
};
var idx = requestStack.push(stackElement) - 1;
promise.then(function (r) {
stackElement.duration = new Date() - stackElement.start;
stackElement.error = r.status < 200 || r.status >= 400;
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;
finishAjaxRequest(idx);
});
startAjaxRequest(idx);
}
return promise;
};
}
if (window.XMLHttpRequest && XMLHttpRequest.prototype.addEventListener) {
var proxied = XMLHttpRequest.prototype.open;
XMLHttpRequest.prototype.open = function(method, url, async, user, pass) {
var self = this;
/* prevent logging AJAX calls to static and inline files, like templates */
var path = url;
if (url.slice(0, 1) === '/') {
if (0 === url.indexOf('{{ request.basePath|e('js') }}')) {
path = url.slice({{ request.basePath|length }});
}
}
else if (0 === url.indexOf('{{ (request.schemeAndHttpHost ~ request.basePath)|e('js') }}')) {
path = url.slice({{ (request.schemeAndHttpHost ~ request.basePath)|length }});
}
if (!path.match(new RegExp({{ excluded_ajax_paths|json_encode|raw }}))) {
var stackElement = {
error: false,
url: url,
method: method,
type: 'xhr',
start: new Date()
};
var idx = requestStack.push(stackElement) - 1;
this.addEventListener('readystatechange', function() {
if (self.readyState == 4) {
stackElement.duration = new Date() - stackElement.start;
stackElement.error = self.status < 200 || self.status >= 400;
stackElement.statusCode = self.status;
extractHeaders(self, stackElement);
finishAjaxRequest(idx);
}
}, false);
startAjaxRequest(idx);
}
proxied.apply(this, Array.prototype.slice.call(arguments));
};
}
{% endif %}
return {
hasClass: hasClass,
removeClass: removeClass,
addClass: addClass,
toggleClass: toggleClass,
getPreference: getPreference,
setPreference: setPreference,
addEventListener: addEventListener,
request: request,
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);
if (el && el.getAttribute('data-sfurl') !== url) {
request(
url,
function(xhr) {
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); },
'',
options
);
}
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 notranslate">\
<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);
elOn.style.display = elOff.style.display;
elOff.style.display = tmp;
if (el) {
el.style.display = 'none' === tmp ? 'none' : 'block';
}
return this;
},
};
})();
}
Sfjs.loadToolbar('{{ token }}');
/*]]>*/</script>

View File

@@ -2,17 +2,53 @@
{% block title 'Redirection Intercepted' %}
{% block body %}
<div class="sf-reset">
<div class="block-exception">
<h1>This request redirects to <a href="{{ location }}">{{ location }}</a>.</h1>
<p>
<small>
The redirect was intercepted by the web debug toolbar to help debugging.
{% block head %}
{{ parent() }}
<style>
.sf-redirection-details {
background: var(--page-background);
box-shadow: inset 0 0 0 1px var(--menu-border-color), 0 0 0 5px var(--page-background);
border-radius: 6px;
margin: 45px auto 30px;
max-width: 800px;
padding: 30px 45px;
}
.sf-redirection-details h1 {
font-size: 21px;
font-weight: bold;
margin: 0 0 10px;
}
.sf-redirection-details p {
margin-top: 0;
}
.sf-redirection-details .sf-redirection-help {
color: var(--color-muted);
font-size: 14px;
margin: 45px 0 0;
}
</style>
{% endblock %}
{% block body %}
<div class="container">
{{ include('@WebProfiler/Profiler/header.html.twig', with_context = false) }}
<div class="sf-reset sf-redirection-details">
<div class="block-exception">
<h1>Redirection Intercepted</h1>
{% set absolute_url = host in location ? location : host ~ location %}
<p>This request redirects to <strong>{{ absolute_url }}</strong></p>
<p><a class="btn" href="{{ location }}">Follow redirect</a></p>
<p class="sf-redirection-help">
The redirect was intercepted by the Symfony Web Debug toolbar to help debugging.
For more information, see the "intercept-redirects" option of the Profiler.
</small>
</p>
</p>
</div>
</div>
</div>
{% endblock %}

View File

@@ -50,7 +50,7 @@
<td class="font-normal text-muted nowrap">{{ loop.index }}</td>
<td class="break-long-words">{{ trace.name }}</td>
<td class="break-long-words">{{ trace.path }}</td>
<td class="font-normal">
<td class="break-long-words font-normal">
{% if trace.level == 1 %}
Path almost matches, but
<span class="newline">{{ trace.log }}</span>

View File

@@ -1 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 337 B

View File

@@ -1 +0,0 @@
<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>

Before

Width:  |  Height:  |  Size: 442 B