diff --git a/CHANGELOG.md b/CHANGELOG.md index 6d8cb9e95..1f3d95f0c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -17,6 +17,7 @@ Yii Framework 2 debug extension Change Log - Enh #143: Added application version display at `ConfigPanel` (klimov-paul) - Enh #145: The error and warning labels of the log section on the summary bar now link directly to the log page filtered by log level type (rhertogh) - Bug #150: Fixed "Cannot read property 'replaceChild' of null" error (BetsuNo) +- Enh #97: Added AJAX requests handling (bashkarev) 2.0.6 March 17, 2016 diff --git a/Module.php b/Module.php index 25bcd2cb3..316c515eb 100644 --- a/Module.php +++ b/Module.php @@ -10,6 +10,7 @@ use Yii; use yii\base\Application; use yii\base\BootstrapInterface; +use yii\web\Response; use yii\helpers\Html; use yii\helpers\Url; use yii\web\View; @@ -164,6 +165,7 @@ public function bootstrap($app) // delay attaching event handler to the view component after it is fully configured $app->on(Application::EVENT_BEFORE_REQUEST, function () use ($app) { $app->getView()->on(View::EVENT_END_BODY, [$this, 'renderToolbar']); + $app->getResponse()->on(Response::EVENT_AFTER_PREPARE, [$this, 'setDebugHeaders']); }); $app->getUrlManager()->addRules([ @@ -197,6 +199,7 @@ public function beforeAction($action) // do not display debug toolbar when in debug view mode Yii::$app->getView()->off(View::EVENT_END_BODY, [$this, 'renderToolbar']); + Yii::$app->getResponse()->off(Response::EVENT_AFTER_PREPARE, [$this, 'setDebugHeaders']); if ($this->checkAccess()) { $this->resetGlobalSettings(); @@ -209,6 +212,27 @@ public function beforeAction($action) } } + /** + * Setting headers to transfer debug data in AJAX requests + * without interfering with the request itself. + * + * @param \yii\base\Event $event + * @since 2.0.7 + */ + public function setDebugHeaders($event) + { + if (!$this->checkAccess() || !Yii::$app->getRequest()->getIsAjax()) { + return; + } + $url = Url::toRoute(['/' . $this->id . '/default/view', + 'tag' => $this->logTarget->tag, + ]); + $event->sender->getHeaders() + ->set('X-Debug-Tag', $this->logTarget->tag) + ->set('X-Debug-Duration', number_format((microtime(true) - YII_BEGIN_TIME) * 1000 + 1)) + ->set('X-Debug-Link', $url); + } + /** * Resets potentially incompatible global settings done in app config. */ diff --git a/assets/toolbar.css b/assets/toolbar.css index c788a0473..15d550bcf 100644 --- a/assets/toolbar.css +++ b/assets/toolbar.css @@ -50,6 +50,10 @@ direction: ltr; } +.yii-debug-toolbar.yii-debug-toolbar_active:not(.yii-debug-toolbar_animating) .yii-debug-toolbar__bar { + overflow: visible; +} + .yii-debug-toolbar__bar:after { content: ''; display: table; @@ -88,7 +92,8 @@ white-space: nowrap; } -.yii-debug-toolbar__block_active { +.yii-debug-toolbar__block_active, +.yii-debug-toolbar__ajax:hover { background: rgb(247, 247, 247); /* Old browsers */ background: -moz-linear-gradient(top, rgb(247, 247, 247) 0%, rgb(224, 224, 224) 100%); /* FF3.6-15 */ background: -webkit-linear-gradient(top, rgb(247, 247, 247) 0%, rgb(224, 224, 224) 100%); /* Chrome10-25,Safari5.1-6 */ @@ -133,7 +138,8 @@ a.yii-debug-toolbar__label:focus { cursor: pointer; } -.yii-debug-toolbar__label_important { +.yii-debug-toolbar__label_important, +.yii-debug-toolbar__label_error { background-color: #b94a48; } @@ -261,3 +267,62 @@ a.yii-debug-toolbar__label:focus { height: 14px; background-image: url('data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIxNSIgaGVpZ2h0PSIxNSIgdmlld0JveD0iMCAwIDUwIDUwIj48cGF0aCBmaWxsPSIjNDQ0IiBkPSJNMzkuNjQyIDkuNzIyYTEuMDEgMS4wMSAwIDAgMC0uMzgyLS4wNzdIMjguMTAzYTEgMSAwIDAgMCAwIDJoOC43NDNMMjEuNyAyNi43OWExIDEgMCAwIDAgMS40MTQgMS40MTVMMzguMjYgMTMuMDZ2OC43NDNhMSAxIDAgMCAwIDIgMFYxMC42NDZhMS4wMDUgMS4wMDUgMCAwIDAtLjYxOC0uOTI0eiIvPjxwYXRoIGQ9Ik0zOS4yNiAyNy45ODVhMSAxIDAgMCAwLTEgMXYxMC42NmgtMjh2LTI4aDEwLjY4M2ExIDEgMCAwIDAgMC0ySDkuMjZhMSAxIDAgMCAwLTEgMXYzMGExIDEgMCAwIDAgMSAxaDMwYTEgMSAwIDAgMCAxLTF2LTExLjY2YTEgMSAwIDAgMC0xLTF6Ii8+PC9zdmc+'); } + +.yii-debug-toolbar__ajax { + position: relative; +} + +.yii-debug-toolbar__ajax:hover .yii-debug-toolbar__ajax_info, +.yii-debug-toolbar__ajax:focus .yii-debug-toolbar__ajax_info { + visibility: visible; +} +.yii-debug-toolbar__ajax_info { + visibility: hidden; + transition: visibility .2s linear; + background-color: white; + box-shadow: inset 0 -10px 10px -10px #e1e1e1; + position: absolute; + bottom: 40px; + left: -1px; + padding: 10px; + max-width: 480px; + max-height: 480px; + word-wrap: break-word; + overflow: hidden; + overflow-y: auto; + box-sizing: border-box; + border: 1px solid rgba(0, 0, 0, 0.11); + z-index: 1000001; +} +.yii-debug-toolbar__ajax a { + color: #337ab7; +} +.yii-debug-toolbar__ajax table { + width: 100%; + table-layout: auto; + border-spacing: 0; + border-collapse: collapse; +} +.yii-debug-toolbar__ajax table td { + padding: 4px; + font-size: 12px; + line-height: normal; + vertical-align: top; + border-top: 1px solid #ddd; +} +.yii-debug-toolbar__ajax table th { + padding: 4px; + font-size: 11px; + line-height: normal; + vertical-align: bottom; + border-bottom: 2px solid #ddd; +} +.yii-debug-toolbar__ajax_request_status { + color: white; + padding: 2px 5px; +} +.yii-debug-toolbar__ajax_request_url { + max-width: 170px; + overflow: hidden; + text-overflow: ellipsis; +} \ No newline at end of file diff --git a/assets/toolbar.js b/assets/toolbar.js index 01bea670b..a0e86a04e 100644 --- a/assets/toolbar.js +++ b/assets/toolbar.js @@ -24,6 +24,7 @@ url, div, toolbarEl = findToolbar(), + toolbarAnimatingClass = 'yii-debug-toolbar_animating', barSelector = '.yii-debug-toolbar__bar', viewSelector = '.yii-debug-toolbar__view', blockSelector = '.yii-debug-toolbar__block', @@ -40,7 +41,8 @@ iframeAnimatingClass = 'yii-debug-toolbar_iframe_animating', titleClass = 'yii-debug-toolbar__title', blockClass = 'yii-debug-toolbar__block', - blockActiveClass = 'yii-debug-toolbar__block_active'; + blockActiveClass = 'yii-debug-toolbar__block_active', + requestStack = []; if (toolbarEl) { url = toolbarEl.getAttribute('data-url'); @@ -100,11 +102,15 @@ }); }, toggleToolbarClass = function (className) { + toolbarEl.classList.add(toolbarAnimatingClass); if (toolbarEl.classList.contains(className)) { toolbarEl.classList.remove(className); } else { toolbarEl.classList.add(className); } + setTimeout(function () { + toolbarEl.classList.remove(toolbarAnimatingClass); + }, animationTime); }, toggleStorageState = function (key, value) { if (window.localStorage) { @@ -170,4 +176,123 @@ while ((el = el.parentElement) && !el.classList.contains(cls)); return el; } -})(); + + function renderAjaxRequests() { + var requestCounter = document.getElementsByClassName('yii-debug-toolbar__ajax_counter'); + if (!requestCounter.length) { + return; + } + var ajaxToolbarPanel = document.querySelector('.yii-debug-toolbar__ajax'); + var tbodies = document.getElementsByClassName('yii-debug-toolbar__ajax_requests'); + 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 statusCodeCell = document.createElement('td'); + var statusCode = document.createElement('span'); + if (request.statusCode < 300) { + statusCode.setAttribute('class', 'yii-debug-toolbar__ajax_request_status yii-debug-toolbar__label_success'); + } else if (request.statusCode < 400) { + statusCode.setAttribute('class', 'yii-debug-toolbar__ajax_request_status yii-debug-toolbar__label_warning'); + } else { + statusCode.setAttribute('class', 'yii-debug-toolbar__ajax_request_status yii-debug-toolbar__label_error'); + } + statusCode.textContent = request.statusCode || '-'; + statusCodeCell.appendChild(statusCode); + row.appendChild(statusCodeCell); + + var pathCell = document.createElement('td'); + pathCell.className = 'yii-debug-toolbar__ajax_request_url'; + pathCell.innerHTML = request.url; + pathCell.setAttribute('title', request.url); + row.appendChild(pathCell); + + var durationCell = document.createElement('td'); + durationCell.className = 'yii-debug-toolbar__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); + + if (request.error) { + if (state !== "loading" && i > requestStack.length - 4) { + state = 'error'; + } + } else if (request.loading) { + state = 'loading' + } + row.className = 'yii-debug-toolbar__ajax_request'; + } + while (tbody.firstChild) { + tbody.removeChild(tbody.firstChild); + } + tbody.appendChild(rows); + } + ajaxToolbarPanel.style.display = 'block'; + } + requestCounter[0].innerText = requestStack.length; + var className = 'yii-debug-toolbar__label yii-debug-toolbar__ajax_counter'; + if (state == 'ok') { + className += ' yii-debug-toolbar__label_success'; + } else if (state == 'error') { + className += ' yii-debug-toolbar__label_error'; + } + 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 = self.getResponseHeader("X-Debug-Duration") || new Date() - stackElement.start; + stackElement.loading = false; + stackElement.statusCode = self.status; + stackElement.error = self.status < 200 || self.status >= 400; + stackElement.profile = self.getResponseHeader("X-Debug-Tag"); + stackElement.profilerUrl = self.getResponseHeader("X-Debug-Link"); + renderAjaxRequests(); + } + }, false); + renderAjaxRequests(); + } + proxied.apply(this, Array.prototype.slice.call(arguments)); + }; + +})(); \ No newline at end of file diff --git a/views/default/toolbar.php b/views/default/toolbar.php index 2eff94a7e..a4800484a 100644 --- a/views/default/toolbar.php +++ b/views/default/toolbar.php @@ -18,6 +18,24 @@ + + getSummary() ?>