[WebProfilerBundle] Show AJAX requests in the symfony profiler toolbar
This commit is contained in:
parent
ea6ce1c8af
commit
37f7dd7483
@ -0,0 +1,34 @@
|
||||
<?php
|
||||
|
||||
/*
|
||||
* This file is part of the Symfony package.
|
||||
*
|
||||
* (c) Fabien Potencier <fabien@symfony.com>
|
||||
*
|
||||
* For the full copyright and license information, please view the LICENSE
|
||||
* file that was distributed with this source code.
|
||||
*/
|
||||
|
||||
namespace Symfony\Bundle\FrameworkBundle\DataCollector;
|
||||
|
||||
use Symfony\Component\HttpFoundation\Request;
|
||||
use Symfony\Component\HttpFoundation\Response;
|
||||
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
|
||||
|
||||
/**
|
||||
* AjaxDataCollector.
|
||||
*
|
||||
* @author Bart van den Burg <bart@burgov.nl>
|
||||
*/
|
||||
class AjaxDataCollector extends DataCollector
|
||||
{
|
||||
public function collect(Request $request, Response $response, \Exception $exception = null)
|
||||
{
|
||||
// all collecting is done client side
|
||||
}
|
||||
|
||||
public function getName()
|
||||
{
|
||||
return 'ajax';
|
||||
}
|
||||
}
|
@ -15,6 +15,7 @@
|
||||
<parameter key="data_collector.router.class">Symfony\Bundle\FrameworkBundle\DataCollector\RouterDataCollector</parameter>
|
||||
<parameter key="data_collector.form.class">Symfony\Component\Form\Extension\DataCollector\FormDataCollector</parameter>
|
||||
<parameter key="data_collector.form.extractor.class">Symfony\Component\Form\Extension\DataCollector\FormDataExtractor</parameter>
|
||||
<parameter key="data_collector.ajax.class">Symfony\Bundle\FrameworkBundle\DataCollector\AjaxDataCollector</parameter>
|
||||
</parameters>
|
||||
|
||||
<services>
|
||||
@ -28,6 +29,10 @@
|
||||
<tag name="data_collector" template="@WebProfiler/Collector/request.html.twig" id="request" priority="255" />
|
||||
</service>
|
||||
|
||||
<service id="data_collector.ajax" class="%data_collector.ajax.class%" public="false">
|
||||
<tag name="data_collector" template="@WebProfiler/Collector/ajax.html.twig" id="ajax" priority="255" />
|
||||
</service>
|
||||
|
||||
<service id="data_collector.exception" class="%data_collector.exception.class%" public="false">
|
||||
<tag name="data_collector" template="@WebProfiler/Collector/exception.html.twig" id="exception" priority="255" />
|
||||
</service>
|
||||
@ -64,6 +69,5 @@
|
||||
<tag name="data_collector" template="@WebProfiler/Collector/form.html.twig" id="form" priority="255" />
|
||||
<argument type="service" id="data_collector.form.extractor" />
|
||||
</service>
|
||||
|
||||
</services>
|
||||
</container>
|
||||
|
@ -108,6 +108,7 @@ class ProfilerController
|
||||
'request' => $request,
|
||||
'templates' => $this->getTemplateManager()->getTemplates($profile),
|
||||
'is_ajax' => $request->isXmlHttpRequest(),
|
||||
'excluded_ajax_paths' => null
|
||||
)), 200, array('Content-Type' => 'text/html'));
|
||||
}
|
||||
|
||||
|
@ -45,6 +45,7 @@ class Configuration implements ConfigurationInterface
|
||||
->end()
|
||||
->end()
|
||||
->booleanNode('intercept_redirects')->defaultFalse()->end()
|
||||
->scalarNode('excluded_ajax_paths')->defaultValue('^/bundles|^/_wdt')->end()
|
||||
->end()
|
||||
;
|
||||
|
||||
|
@ -45,6 +45,7 @@ class WebProfilerExtension extends Extension
|
||||
$loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config'));
|
||||
$loader->load('profiler.xml');
|
||||
$container->setParameter('web_profiler.debug_toolbar.position', $config['position']);
|
||||
$container->setParameter('web_profiler.debug_toolbar.excluded_ajax_paths', $config['excluded_ajax_paths']);
|
||||
|
||||
if ($config['toolbar'] || $config['intercept_redirects']) {
|
||||
$loader->load('toolbar.xml');
|
||||
|
@ -38,14 +38,16 @@ class WebDebugToolbarListener implements EventSubscriberInterface
|
||||
protected $interceptRedirects;
|
||||
protected $mode;
|
||||
protected $position;
|
||||
protected $excludedAjaxPaths;
|
||||
|
||||
public function __construct(\Twig_Environment $twig, $interceptRedirects = false, $mode = self::ENABLED, $position = 'bottom', UrlGeneratorInterface $urlGenerator = null)
|
||||
public function __construct(\Twig_Environment $twig, $interceptRedirects = false, $mode = self::ENABLED, $position = 'bottom', UrlGeneratorInterface $urlGenerator = null, $excludedAjaxPaths = '^/bundles|^/_wdt')
|
||||
{
|
||||
$this->twig = $twig;
|
||||
$this->urlGenerator = $urlGenerator;
|
||||
$this->interceptRedirects = (bool) $interceptRedirects;
|
||||
$this->mode = (int) $mode;
|
||||
$this->position = $position;
|
||||
$this->excludedAjaxPaths = $excludedAjaxPaths;
|
||||
}
|
||||
|
||||
public function isEnabled()
|
||||
@ -113,6 +115,7 @@ class WebDebugToolbarListener implements EventSubscriberInterface
|
||||
'@WebProfiler/Profiler/toolbar_js.html.twig',
|
||||
array(
|
||||
'position' => $this->position,
|
||||
'excluded_ajax_paths' => $this->excludedAjaxPaths,
|
||||
'token' => $response->headers->get('X-Debug-Token'),
|
||||
)
|
||||
))."\n";
|
||||
|
@ -16,6 +16,7 @@
|
||||
<argument>%web_profiler.debug_toolbar.mode%</argument>
|
||||
<argument>%web_profiler.debug_toolbar.position%</argument>
|
||||
<argument type="service" id="router" on-invalid="ignore" />
|
||||
<argument>%web_profiler.debug_toolbar.excluded_ajax_paths%</argument>
|
||||
</service>
|
||||
</services>
|
||||
</container>
|
||||
|
@ -0,0 +1,29 @@
|
||||
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
|
||||
|
||||
{% block toolbar %}
|
||||
{% set icon %}
|
||||
<span>
|
||||
<img width="13" height="28" alt="AJAX requests" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAYAAAByDd+UAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gUIEiMpCg3ocwAAAB1pVFh0Q29tbWVudAAAAAAAQ3JlYXRlZCB3aXRoIEdJTVBkLmUHAAABAElEQVRIx+3WMQ6CMBQG4L+EMHEIuAOTiZvxEl1g4AAubsa4ungAhnbhFmwmTtwBDsHE8pxIAGkh0BCjdKNJ8/H6Cn8ZEWHNYWHlsTpoD03W9ZPuuxMy19Uu9mIByX3WPBdpSDdcO3PGKhzCoqREmUQI04KMgiqsGTrUMo2NofYUpKoOeLzO2DtOpzcqrINCUPsFZ/cwDQMtpqrUngtymTOuONWqHfmPD38DN3ADvyiA0zCgpGwFbit8XTfD5ZgBAAFA5cXIJWeLKuQyZyL2JsXTXOzj5+1zyQTUkaPNwpHriLKHPpeDlU4N3lmHpo+awrR52Gxv/xa2BAMA9vM37zcnf5IxgZnMRAAAAABJRU5ErkJggg==">
|
||||
<span class="sf-toolbar-ajax-requests">0</span>
|
||||
</span>
|
||||
{% endset %}
|
||||
{% set text %}
|
||||
<div class="sf-toolbar-info-piece">
|
||||
<p><b>AJAX requests</b> <span class="sf-toolbar-ajax-info"></span></p>
|
||||
<div>
|
||||
<table class="sf-toolbar-ajax-requests">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Method</th>
|
||||
<th>URL</th>
|
||||
<th>Time</th>
|
||||
<th>Profile</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody class="sf-toolbar-ajax-request-list"></tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
{% endset %}
|
||||
{% include '@WebProfiler/Profiler/toolbar_item.html.twig' with { 'link': false } %}
|
||||
{% endblock %}
|
@ -65,6 +65,146 @@
|
||||
}
|
||||
|
||||
localStorage.setItem(profilerStorageKey + name, value);
|
||||
},
|
||||
|
||||
requestStack = [],
|
||||
|
||||
renderAjaxRequests = function() {
|
||||
var requestCounter = document.getElementsByClassName('sf-toolbar-ajax-requests');
|
||||
if (!requestCounter.length) {
|
||||
return;
|
||||
}
|
||||
|
||||
var tbodies = document.getElementsByClassName('sf-toolbar-ajax-request-list');
|
||||
var state = 'ok';
|
||||
if (tbodies.length) {
|
||||
var tbody = tbodies[0];
|
||||
|
||||
var rows = document.createDocumentFragment();
|
||||
|
||||
if (requestStack.length) {
|
||||
var firstItem = requestStack.length > 20 ? requestStack.length - 20 : 0;
|
||||
for (var i = firstItem; i < requestStack.length; i++) {
|
||||
var request = requestStack[i];
|
||||
|
||||
var row = document.createElement('tr');
|
||||
rows.appendChild(row);
|
||||
|
||||
var methodCell = document.createElement('td');
|
||||
methodCell.innerHTML = request.method;
|
||||
row.appendChild(methodCell);
|
||||
|
||||
var pathCell = document.createElement('td');
|
||||
pathCell.className = 'sf-ajax-request-url';
|
||||
pathCell.innerHTML = request.url;
|
||||
pathCell.setAttribute('title', request.url);
|
||||
row.appendChild(pathCell);
|
||||
|
||||
var durationCell = document.createElement('td');
|
||||
durationCell.className = 'sf-ajax-request-duration';
|
||||
|
||||
if (request.duration) {
|
||||
durationCell.innerText = request.duration + "ms";
|
||||
} else {
|
||||
durationCell.innerText = '-';
|
||||
}
|
||||
row.appendChild(durationCell);
|
||||
|
||||
row.appendChild(document.createTextNode(' '));
|
||||
var profilerCell = document.createElement('td');
|
||||
|
||||
if (request.profilerUrl) {
|
||||
var profilerLink = document.createElement('a');
|
||||
profilerLink.setAttribute('href', request.profilerUrl);
|
||||
profilerLink.innerText = request.profile;
|
||||
profilerCell.appendChild(profilerLink);
|
||||
} else {
|
||||
profilerCell.innerText = 'n/a';
|
||||
}
|
||||
|
||||
row.appendChild(profilerCell);
|
||||
|
||||
var requestState = 'ok';
|
||||
if (request.error) {
|
||||
requestState = 'error';
|
||||
if (state != "loading" && i > requestStack.length - 4) {
|
||||
state = 'error';
|
||||
}
|
||||
} else if (request.loading) {
|
||||
requestState = 'loading';
|
||||
state = 'loading'
|
||||
}
|
||||
row.className = 'sf-ajax-request sf-ajax-request-' + requestState;
|
||||
}
|
||||
|
||||
while (tbody.firstChild) {
|
||||
tbody.removeChild(tbody.firstChild);
|
||||
}
|
||||
tbody.appendChild(rows);
|
||||
|
||||
var infoSpans = document.getElementsByClassName("sf-toolbar-ajax-info");
|
||||
if (infoSpans.length) {
|
||||
var text = firstItem == 0 ? 'Showing ' + requestStack.length + ' items' : 'Showing the last 20 '
|
||||
+ 'out of ' + requestStack.length + ' items';
|
||||
infoSpans[0].innerText = text;
|
||||
}
|
||||
} else {
|
||||
var cell = document.createElement('td');
|
||||
cell.setAttribute('colspan', '4');
|
||||
cell.innerText = "No AJAX requests fired yet.";
|
||||
var row = document.createElement('tr');
|
||||
row.appendChild(cell);
|
||||
tbody.appendChild(row);
|
||||
}
|
||||
}
|
||||
|
||||
requestCounter[0].innerText = requestStack.length;
|
||||
|
||||
var className = 'sf-toolbar-ajax-requests sf-toolbar-status';
|
||||
if (state == 'ok') {
|
||||
className += ' sf-toolbar-status-green';
|
||||
} else if (state == 'error') {
|
||||
className += ' sf-toolbar-status-red';
|
||||
} else {
|
||||
className += ' sf-ajax-request-loading';
|
||||
}
|
||||
|
||||
requestCounter[0].className = className;
|
||||
};
|
||||
|
||||
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 */
|
||||
if (url.substr(0, 1) === '/' && !url.match(new RegExp("{{ excluded_ajax_paths }}"))) {
|
||||
var stackElement = {
|
||||
loading: true,
|
||||
error: false,
|
||||
url: url,
|
||||
method: method,
|
||||
start: new Date()
|
||||
};
|
||||
|
||||
requestStack.push(stackElement);
|
||||
|
||||
this.addEventListener("readystatechange", function() {
|
||||
if (self.readyState == 4) {
|
||||
stackElement.duration = new Date() - stackElement.start;
|
||||
stackElement.loading = false;
|
||||
stackElement.error = self.status < 200 || self.status >= 400;
|
||||
stackElement.profile = self.getResponseHeader("X-Debug-Token");
|
||||
stackElement.profilerUrl = self.getResponseHeader("X-Debug-Token-Link");
|
||||
|
||||
Sfjs.renderAjaxRequests();
|
||||
}
|
||||
}, false);
|
||||
|
||||
Sfjs.renderAjaxRequests();
|
||||
}
|
||||
|
||||
proxied.apply(this, Array.prototype.slice.call(arguments));
|
||||
};
|
||||
|
||||
return {
|
||||
@ -80,6 +220,8 @@
|
||||
|
||||
request: request,
|
||||
|
||||
renderAjaxRequests: renderAjaxRequests,
|
||||
|
||||
load: function(selector, url, onSuccess, onError, options) {
|
||||
var el = document.getElementById(selector);
|
||||
|
||||
|
@ -283,6 +283,56 @@
|
||||
line-height: 19px;
|
||||
}
|
||||
|
||||
table.sf-toolbar-ajax-requests {
|
||||
border-collapse: collapse;
|
||||
margin: 0 -10px -10px;
|
||||
}
|
||||
.sf-toolbar-ajax-requests th, .sf-toolbar-ajax-requests td {
|
||||
border: 1px solid #ddd;
|
||||
padding: 0 3px;
|
||||
}
|
||||
.sf-toolbar-ajax-requests th {
|
||||
background-color: #eee;
|
||||
}
|
||||
.sf-ajax-request-url {
|
||||
max-width: 300px;
|
||||
line-height: 9px;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
.sf-ajax-request-duration {
|
||||
text-align: right;
|
||||
}
|
||||
.sf-ajax-request-error {
|
||||
color: red;
|
||||
}
|
||||
.sf-ajax-request-loading {
|
||||
-webkit-animation: sf-blink .5s ease-in-out infinite;
|
||||
-o-animation: sf-blink .5s ease-in-out infinite;
|
||||
-moz-animation: sf-blink .5s ease-in-out infinite;
|
||||
animation: sf-blink .5s ease-in-out infinite;
|
||||
}
|
||||
@-webkit-keyframes sf-blink {
|
||||
0% { color: black; }
|
||||
50% { color: #bbb; }
|
||||
100% { color: black; }
|
||||
}
|
||||
@-moz-keyframes sf-blink {
|
||||
0% { color: black; }
|
||||
50% { color: #bbb; }
|
||||
100% { color: black; }
|
||||
}
|
||||
@-o-keyframes sf-blink {
|
||||
0% { color: black; }
|
||||
50% { color: #bbb; }
|
||||
100% { color: black; }
|
||||
}
|
||||
@keyframes sf-blink {
|
||||
0% { color: black; }
|
||||
50% { color: #bbb; }
|
||||
100% { color: black; }
|
||||
}
|
||||
|
||||
/***** Override the setting when the toolbar is on the top *****/
|
||||
{% if position == 'top' %}
|
||||
.sf-minitoolbar {
|
||||
|
@ -29,6 +29,8 @@
|
||||
document.getElementById('sfToolbarClearer-{{ token }}').style.display = 'block';
|
||||
document.getElementById('sfMiniToolbar-{{ token }}').style.display = 'none';
|
||||
}
|
||||
|
||||
Sfjs.renderAjaxRequests();
|
||||
},
|
||||
function(xhr) {
|
||||
if (xhr.status !== 0) {
|
||||
|
@ -31,11 +31,12 @@ class ConfigurationTest extends \PHPUnit_Framework_TestCase
|
||||
public function getDebugModes()
|
||||
{
|
||||
return array(
|
||||
array(array(), array('intercept_redirects' => false, 'toolbar' => false, 'position' => 'bottom')),
|
||||
array(array('intercept_redirects' => true), array('intercept_redirects' => true, 'toolbar' => false, 'position' => 'bottom')),
|
||||
array(array('intercept_redirects' => false), array('intercept_redirects' => false, 'toolbar' => false, 'position' => 'bottom')),
|
||||
array(array('toolbar' => true), array('intercept_redirects' => false, 'toolbar' => true, 'position' => 'bottom')),
|
||||
array(array('position' => 'top'), array('intercept_redirects' => false, 'toolbar' => false, 'position' => 'top')),
|
||||
array(array(), array('intercept_redirects' => false, 'toolbar' => false, 'position' => 'bottom', 'excluded_ajax_paths' => '^/bundles|^/_wdt')),
|
||||
array(array('intercept_redirects' => true), array('intercept_redirects' => true, 'toolbar' => false, 'position' => 'bottom', 'excluded_ajax_paths' => '^/bundles|^/_wdt')),
|
||||
array(array('intercept_redirects' => false), array('intercept_redirects' => false, 'toolbar' => false, 'position' => 'bottom', 'excluded_ajax_paths' => '^/bundles|^/_wdt')),
|
||||
array(array('toolbar' => true), array('intercept_redirects' => false, 'toolbar' => true, 'position' => 'bottom', 'excluded_ajax_paths' => '^/bundles|^/_wdt')),
|
||||
array(array('position' => 'top'), array('intercept_redirects' => false, 'toolbar' => false, 'position' => 'top', 'excluded_ajax_paths' => '^/bundles|^/_wdt')),
|
||||
array(array('excluded_ajax_paths' => 'test'), array('intercept_redirects' => false, 'toolbar' => false, 'position' => 'bottom', 'excluded_ajax_paths' => 'test')),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user