From 25b9774dfbf07f9f342222586d3d225de9a5b15e Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Sat, 7 Nov 2026 20:23:15 -0600 Subject: [PATCH 1/2] [#102] - keeping host CSS out of the bar, timing out history requests, showing row errors Assisted-by: Claude --- resources/assets/debugbar.css | 29 ++++++++ resources/assets/debugbar.js | 30 +++++++++ resources/assets/debugbar.min.css | 2 +- resources/assets/debugbar.min.js | 14 ++-- tests/JavaScript/debugbar.test.js | 107 ++++++++++++++++++++++++++++++ 5 files changed, 175 insertions(+), 7 deletions(-) diff --git a/resources/assets/debugbar.css b/resources/assets/debugbar.css index 085229c..3478e60 100644 --- a/resources/assets/debugbar.css +++ b/resources/assets/debugbar.css @@ -21,6 +21,15 @@ color: #e2e2ef; } +/* Host rules must not change the buttons of the bar. A rule of the bar with a + class has a higher specificity, so it still sets its own values. */ +#phalcon-debugbar button { + margin: 0; + border-radius: 0; + box-shadow: none; + text-transform: none; +} + /* Collapsed: shrink to the bottom-right handle so the host page's own bottom-left controls (logout, etc.) are never covered. */ #phalcon-debugbar.is-collapsed { @@ -309,6 +318,16 @@ box-shadow: inset 3px 0 #7c3aed; } +#phalcon-debugbar .phalcon-debugbar-history-request-error { + display: none; + grid-column: 1 / -1; + color: #ff6b81; +} + +#phalcon-debugbar .phalcon-debugbar-history-request.is-error .phalcon-debugbar-history-request-error { + display: block; +} + #phalcon-debugbar .phalcon-debugbar-history-method { color: #a78bfa; font-weight: 700; @@ -355,6 +374,16 @@ } } +/* Host rules must not set the colors of the panel tables and code blocks. */ +#phalcon-debugbar table, +#phalcon-debugbar tr, +#phalcon-debugbar th, +#phalcon-debugbar td, +#phalcon-debugbar pre { + background: transparent; + color: inherit; +} + #phalcon-debugbar table { width: 100%; border-collapse: collapse; diff --git a/resources/assets/debugbar.js b/resources/assets/debugbar.js index 40162b8..866e5e9 100644 --- a/resources/assets/debugbar.js +++ b/resources/assets/debugbar.js @@ -18,6 +18,7 @@ var STORAGE_KEY = 'phalcon-debugbar-collapsed'; var HISTORY_VERSION = 1; + var REQUEST_TIMEOUT_MS = 10000; function ready(fn) { if (document.readyState !== 'loading') { @@ -369,12 +370,34 @@ options.headers = options.headers || {}; options.headers.Accept = 'application/json'; + // A request that does not end must not keep the history controls disabled. + var timer = null; + if (window.AbortController && window.setTimeout && window.clearTimeout) { + var controller = new window.AbortController(); + options.signal = controller.signal; + timer = window.setTimeout(function () { + controller.abort(); + }, REQUEST_TIMEOUT_MS); + } + + function stopTimer() { + if (timer !== null) { + window.clearTimeout(timer); + } + } + return window.fetch(url, options).then(function (response) { if (!response.ok) { throw new Error('HTTP ' + response.status); } return response.json(); + }).then(function (data) { + stopTimer(); + return data; + }, function (error) { + stopTimer(); + throw error; }); } @@ -505,6 +528,7 @@ button.appendChild(el('span', 'phalcon-debugbar-history-status', scalar(request.status))); button.appendChild(el('time', 'phalcon-debugbar-history-time', scalar(request.requested_at))); + var rowError = null; button.addEventListener('click', function () { var selection = requestGuard.startDetail(); if (selection === null) { @@ -512,6 +536,7 @@ } button.disabled = true; + button.classList.remove('is-error'); loadJson(historyUrl(url, id)).then(function (detail) { if (!requestGuard.isDetailCurrent(selection)) { button.disabled = false; @@ -536,6 +561,11 @@ button.disabled = false; }).catch(function () { button.disabled = false; + button.classList.add('is-error'); + if (rowError === null) { + rowError = el('span', 'phalcon-debugbar-history-request-error', 'Unable to load'); + button.appendChild(rowError); + } }); }); diff --git a/resources/assets/debugbar.min.css b/resources/assets/debugbar.min.css index 2d975f3..6b6d4c9 100644 --- a/resources/assets/debugbar.min.css +++ b/resources/assets/debugbar.min.css @@ -1 +1 @@ -#phalcon-debugbar{position:fixed;left:0;right:0;bottom:0;z-index:2147483000;font-family:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace;font-size:12px;line-height:1.5;color:#e2e2ef}#phalcon-debugbar.is-collapsed{left:auto}#phalcon-debugbar.is-collapsed .phalcon-debugbar-body,#phalcon-debugbar.is-collapsed .phalcon-debugbar-history-browser,#phalcon-debugbar.is-collapsed .phalcon-debugbar-tabs,#phalcon-debugbar.is-collapsed .phalcon-debugbar-indicators{display:none}#phalcon-debugbar *,#phalcon-debugbar *::before,#phalcon-debugbar *::after{box-sizing:border-box}#phalcon-debugbar .phalcon-debugbar-bar{display:flex;flex-direction:column;background:#1c1c28;border-top:2px solid #7c3aed;box-shadow:0 -2px 14px rgb(0 0 0 / .45)}#phalcon-debugbar .phalcon-debugbar-row{display:flex;align-items:stretch}#phalcon-debugbar .phalcon-debugbar-tabs{display:flex;flex:1 1 auto;align-items:stretch;overflow-x:auto}#phalcon-debugbar .phalcon-debugbar-tab{display:inline-flex;align-items:center;gap:7px;padding:7px 14px;background:#fff0;border:0;border-right:1px solid #2b2b40;color:#b9b9d4;font:inherit;white-space:nowrap;cursor:pointer}#phalcon-debugbar .phalcon-debugbar-tab:hover{background:#26263a;color:#fff}#phalcon-debugbar .phalcon-debugbar-tab.is-active{background:#7c3aed;color:#fff}#phalcon-debugbar .phalcon-debugbar-badge{padding:1px 7px;border-radius:9px;background:rgb(255 255 255 / .16);font-size:11px}#phalcon-debugbar .phalcon-debugbar-tab.is-active .phalcon-debugbar-badge{background:rgb(0 0 0 / .25)}#phalcon-debugbar .phalcon-debugbar-indicators{display:flex;flex:0 1 auto;min-width:0;align-items:stretch}#phalcon-debugbar .phalcon-debugbar-indicator{display:inline-flex;min-width:0;align-items:center;gap:6px;padding:7px 10px;border-left:1px solid #2b2b40;color:#b9b9d4;white-space:nowrap}#phalcon-debugbar .phalcon-debugbar-indicator-icon{width:15px;height:15px;flex:0 0 auto;fill:none;stroke:#a78bfa;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round}#phalcon-debugbar .phalcon-debugbar-indicator-value{color:#fff;font-weight:700}#phalcon-debugbar .phalcon-debugbar-request-control{max-width:25vw}#phalcon-debugbar button.phalcon-debugbar-request-control,#phalcon-debugbar button.phalcon-debugbar-metric{background:#fff0;border:0;border-left:1px solid #2b2b40;font:inherit;cursor:pointer}#phalcon-debugbar button.phalcon-debugbar-request-control:hover,#phalcon-debugbar button.phalcon-debugbar-request-control.is-active,#phalcon-debugbar button.phalcon-debugbar-metric:hover{background:#26263a}#phalcon-debugbar button.phalcon-debugbar-metric.is-active{background:#7c3aed}#phalcon-debugbar button.phalcon-debugbar-metric.is-active .phalcon-debugbar-indicator-icon{stroke:#fff}#phalcon-debugbar .phalcon-debugbar-request-method{color:#a78bfa;font-weight:700}#phalcon-debugbar .phalcon-debugbar-request-uri{overflow:hidden;text-overflow:ellipsis}#phalcon-debugbar .phalcon-debugbar-toggle{flex:0 0 auto;padding:7px 12px;background:#7c3aed;border:0;color:#fff;font:inherit;font-size:14px;line-height:1;cursor:pointer}#phalcon-debugbar .phalcon-debugbar-toggle:hover{background:#8b4ff5}#phalcon-debugbar .phalcon-debugbar-body{max-height:42vh;overflow:auto;padding:10px 14px;background:#14141d;border-bottom:1px solid #2b2b40}#phalcon-debugbar .phalcon-debugbar-summary{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:10px}#phalcon-debugbar .phalcon-debugbar-summary-metric{display:flex;min-width:130px;align-items:baseline;justify-content:space-between;gap:14px;padding:7px 10px;border:1px solid #303047;border-radius:4px;background:#20202e}#phalcon-debugbar .phalcon-debugbar-summary-label{color:#9a9ac8}#phalcon-debugbar .phalcon-debugbar-summary-value{color:#fff;font-size:13px}#phalcon-debugbar .phalcon-debugbar-history-browser{display:none;max-height:210px;overflow:auto;background:#101018;border-bottom:1px solid #2b2b40}#phalcon-debugbar .phalcon-debugbar-history-toolbar{position:sticky;top:0;z-index:1;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:7px 14px;background:#181824;border-bottom:1px solid #2b2b40}#phalcon-debugbar .phalcon-debugbar-history-title{color:#d5d5e8;font-size:12px}#phalcon-debugbar .phalcon-debugbar-history-actions{display:flex;gap:6px}#phalcon-debugbar .phalcon-debugbar-history-action{padding:3px 9px;border:1px solid #454563;border-radius:3px;background:#29293d;color:#d5d5e8;font:inherit;cursor:pointer}#phalcon-debugbar .phalcon-debugbar-history-action:hover{background:#353550;color:#fff}#phalcon-debugbar .phalcon-debugbar-history-action.is-danger{border-color:#7f3445;color:#ff9aac}#phalcon-debugbar .phalcon-debugbar-history-action.is-danger:hover{background:#542532}#phalcon-debugbar .phalcon-debugbar-history-action:disabled{opacity:.5;cursor:default}#phalcon-debugbar .phalcon-debugbar-history-list{display:flex;flex-direction:column}#phalcon-debugbar .phalcon-debugbar-history-request{display:grid;grid-template-columns:58px minmax(180px,1fr) 48px 210px;gap:10px;align-items:center;padding:5px 14px;background:#fff0;border:0;border-bottom:1px solid #232334;color:#b9b9d4;font:inherit;text-align:left;cursor:pointer}#phalcon-debugbar .phalcon-debugbar-history-request:hover{background:#26263a;color:#fff}#phalcon-debugbar .phalcon-debugbar-history-request.is-selected{background:#26263a;color:#fff;box-shadow:inset 3px 0 #7c3aed}#phalcon-debugbar .phalcon-debugbar-history-method{color:#a78bfa;font-weight:700}#phalcon-debugbar .phalcon-debugbar-history-uri{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}#phalcon-debugbar .phalcon-debugbar-history-status,#phalcon-debugbar .phalcon-debugbar-history-time{color:#8f8faa}#phalcon-debugbar .phalcon-debugbar-history-loading,#phalcon-debugbar .phalcon-debugbar-history-empty{padding:8px 14px;color:#8f8faa}#phalcon-debugbar .phalcon-debugbar-history-error{padding:8px 14px;color:#ff6b81}@media (max-width:760px){#phalcon-debugbar .phalcon-debugbar-indicator{padding-right:7px;padding-left:7px}#phalcon-debugbar .phalcon-debugbar-request-control{max-width:32vw}#phalcon-debugbar .phalcon-debugbar-history-request{grid-template-columns:52px minmax(120px,1fr) 42px}#phalcon-debugbar .phalcon-debugbar-history-time{display:none}}#phalcon-debugbar table{width:100%;border-collapse:collapse}#phalcon-debugbar th,#phalcon-debugbar td{padding:4px 10px;text-align:left;vertical-align:top;border-bottom:1px solid #23233440;word-break:break-word}#phalcon-debugbar .phalcon-debugbar-grid th,#phalcon-debugbar .phalcon-debugbar-key{width:1%;color:#9a9ac8;font-weight:600;white-space:nowrap}#phalcon-debugbar .phalcon-debugbar-value{color:#e2e2ef}#phalcon-debugbar .phalcon-debugbar-list tr.is-duplicate{background:#2a2118}#phalcon-debugbar .phalcon-debugbar-duplicate-count{display:inline-block;margin-left:10px;padding:1px 6px;border-radius:999px;background:#8a4b08;color:#fff1d6;font-size:11px;font-weight:700;white-space:nowrap}#phalcon-debugbar .phalcon-debugbar-code{margin:0;white-space:pre-wrap;word-break:break-word}#phalcon-debugbar .phalcon-debugbar-empty{padding:6px 2px;color:#6b6b8c;font-style:italic}#phalcon-debugbar .phalcon-debugbar-exception{padding:3px 0;border-bottom:1px solid #23233440}#phalcon-debugbar .phalcon-debugbar-exception-summary{padding:4px 0;cursor:pointer}#phalcon-debugbar .phalcon-debugbar-exception-label{margin-right:8px;color:#ff6b81;font-weight:600}#phalcon-debugbar .phalcon-debugbar-exception-trace{margin:6px 0 10px 16px;padding:8px 10px;background:#0f0f17;border-radius:4px;color:#b9b9d4;font-size:11px;white-space:pre-wrap;word-break:break-word}#phalcon-debugbar .phalcon-debugbar-log{padding:3px 0;border-bottom:1px solid #23233440}#phalcon-debugbar .phalcon-debugbar-log-summary{padding:4px 0;cursor:pointer}#phalcon-debugbar .phalcon-debugbar-log-label{margin-right:8px;color:#7cc4ff;font-weight:600}#phalcon-debugbar .phalcon-debugbar-log-context{margin:6px 0 10px 16px;padding:8px 10px;background:#0f0f17;border-radius:4px;color:#b9b9d4;font-size:11px;white-space:pre-wrap;word-break:break-word} \ No newline at end of file +#phalcon-debugbar{position:fixed;left:0;right:0;bottom:0;z-index:2147483000;font-family:ui-monospace,SFMono-Regular,"SF Mono",Menlo,Consolas,monospace;font-size:12px;line-height:1.5;color:#e2e2ef}#phalcon-debugbar button{margin:0;border-radius:0;box-shadow:none;text-transform:none}#phalcon-debugbar.is-collapsed{left:auto}#phalcon-debugbar.is-collapsed .phalcon-debugbar-body,#phalcon-debugbar.is-collapsed .phalcon-debugbar-history-browser,#phalcon-debugbar.is-collapsed .phalcon-debugbar-tabs,#phalcon-debugbar.is-collapsed .phalcon-debugbar-indicators{display:none}#phalcon-debugbar *,#phalcon-debugbar *::before,#phalcon-debugbar *::after{box-sizing:border-box}#phalcon-debugbar .phalcon-debugbar-bar{display:flex;flex-direction:column;background:#1c1c28;border-top:2px solid #7c3aed;box-shadow:0 -2px 14px rgb(0 0 0 / .45)}#phalcon-debugbar .phalcon-debugbar-row{display:flex;align-items:stretch}#phalcon-debugbar .phalcon-debugbar-tabs{display:flex;flex:1 1 auto;align-items:stretch;overflow-x:auto}#phalcon-debugbar .phalcon-debugbar-tab{display:inline-flex;align-items:center;gap:7px;padding:7px 14px;background:#fff0;border:0;border-right:1px solid #2b2b40;color:#b9b9d4;font:inherit;white-space:nowrap;cursor:pointer}#phalcon-debugbar .phalcon-debugbar-tab:hover{background:#26263a;color:#fff}#phalcon-debugbar .phalcon-debugbar-tab.is-active{background:#7c3aed;color:#fff}#phalcon-debugbar .phalcon-debugbar-badge{padding:1px 7px;border-radius:9px;background:rgb(255 255 255 / .16);font-size:11px}#phalcon-debugbar .phalcon-debugbar-tab.is-active .phalcon-debugbar-badge{background:rgb(0 0 0 / .25)}#phalcon-debugbar .phalcon-debugbar-indicators{display:flex;flex:0 1 auto;min-width:0;align-items:stretch}#phalcon-debugbar .phalcon-debugbar-indicator{display:inline-flex;min-width:0;align-items:center;gap:6px;padding:7px 10px;border-left:1px solid #2b2b40;color:#b9b9d4;white-space:nowrap}#phalcon-debugbar .phalcon-debugbar-indicator-icon{width:15px;height:15px;flex:0 0 auto;fill:none;stroke:#a78bfa;stroke-width:1.5;stroke-linecap:round;stroke-linejoin:round}#phalcon-debugbar .phalcon-debugbar-indicator-value{color:#fff;font-weight:700}#phalcon-debugbar .phalcon-debugbar-request-control{max-width:25vw}#phalcon-debugbar button.phalcon-debugbar-request-control,#phalcon-debugbar button.phalcon-debugbar-metric{background:#fff0;border:0;border-left:1px solid #2b2b40;font:inherit;cursor:pointer}#phalcon-debugbar button.phalcon-debugbar-request-control:hover,#phalcon-debugbar button.phalcon-debugbar-request-control.is-active,#phalcon-debugbar button.phalcon-debugbar-metric:hover{background:#26263a}#phalcon-debugbar button.phalcon-debugbar-metric.is-active{background:#7c3aed}#phalcon-debugbar button.phalcon-debugbar-metric.is-active .phalcon-debugbar-indicator-icon{stroke:#fff}#phalcon-debugbar .phalcon-debugbar-request-method{color:#a78bfa;font-weight:700}#phalcon-debugbar .phalcon-debugbar-request-uri{overflow:hidden;text-overflow:ellipsis}#phalcon-debugbar .phalcon-debugbar-toggle{flex:0 0 auto;padding:7px 12px;background:#7c3aed;border:0;color:#fff;font:inherit;font-size:14px;line-height:1;cursor:pointer}#phalcon-debugbar .phalcon-debugbar-toggle:hover{background:#8b4ff5}#phalcon-debugbar .phalcon-debugbar-body{max-height:42vh;overflow:auto;padding:10px 14px;background:#14141d;border-bottom:1px solid #2b2b40}#phalcon-debugbar .phalcon-debugbar-summary{display:flex;flex-wrap:wrap;gap:8px;margin-bottom:10px}#phalcon-debugbar .phalcon-debugbar-summary-metric{display:flex;min-width:130px;align-items:baseline;justify-content:space-between;gap:14px;padding:7px 10px;border:1px solid #303047;border-radius:4px;background:#20202e}#phalcon-debugbar .phalcon-debugbar-summary-label{color:#9a9ac8}#phalcon-debugbar .phalcon-debugbar-summary-value{color:#fff;font-size:13px}#phalcon-debugbar .phalcon-debugbar-history-browser{display:none;max-height:210px;overflow:auto;background:#101018;border-bottom:1px solid #2b2b40}#phalcon-debugbar .phalcon-debugbar-history-toolbar{position:sticky;top:0;z-index:1;display:flex;align-items:center;justify-content:space-between;gap:12px;padding:7px 14px;background:#181824;border-bottom:1px solid #2b2b40}#phalcon-debugbar .phalcon-debugbar-history-title{color:#d5d5e8;font-size:12px}#phalcon-debugbar .phalcon-debugbar-history-actions{display:flex;gap:6px}#phalcon-debugbar .phalcon-debugbar-history-action{padding:3px 9px;border:1px solid #454563;border-radius:3px;background:#29293d;color:#d5d5e8;font:inherit;cursor:pointer}#phalcon-debugbar .phalcon-debugbar-history-action:hover{background:#353550;color:#fff}#phalcon-debugbar .phalcon-debugbar-history-action.is-danger{border-color:#7f3445;color:#ff9aac}#phalcon-debugbar .phalcon-debugbar-history-action.is-danger:hover{background:#542532}#phalcon-debugbar .phalcon-debugbar-history-action:disabled{opacity:.5;cursor:default}#phalcon-debugbar .phalcon-debugbar-history-list{display:flex;flex-direction:column}#phalcon-debugbar .phalcon-debugbar-history-request{display:grid;grid-template-columns:58px minmax(180px,1fr) 48px 210px;gap:10px;align-items:center;padding:5px 14px;background:#fff0;border:0;border-bottom:1px solid #232334;color:#b9b9d4;font:inherit;text-align:left;cursor:pointer}#phalcon-debugbar .phalcon-debugbar-history-request:hover{background:#26263a;color:#fff}#phalcon-debugbar .phalcon-debugbar-history-request.is-selected{background:#26263a;color:#fff;box-shadow:inset 3px 0 #7c3aed}#phalcon-debugbar .phalcon-debugbar-history-request-error{display:none;grid-column:1 / -1;color:#ff6b81}#phalcon-debugbar .phalcon-debugbar-history-request.is-error .phalcon-debugbar-history-request-error{display:block}#phalcon-debugbar .phalcon-debugbar-history-method{color:#a78bfa;font-weight:700}#phalcon-debugbar .phalcon-debugbar-history-uri{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}#phalcon-debugbar .phalcon-debugbar-history-status,#phalcon-debugbar .phalcon-debugbar-history-time{color:#8f8faa}#phalcon-debugbar .phalcon-debugbar-history-loading,#phalcon-debugbar .phalcon-debugbar-history-empty{padding:8px 14px;color:#8f8faa}#phalcon-debugbar .phalcon-debugbar-history-error{padding:8px 14px;color:#ff6b81}@media (max-width:760px){#phalcon-debugbar .phalcon-debugbar-indicator{padding-right:7px;padding-left:7px}#phalcon-debugbar .phalcon-debugbar-request-control{max-width:32vw}#phalcon-debugbar .phalcon-debugbar-history-request{grid-template-columns:52px minmax(120px,1fr) 42px}#phalcon-debugbar .phalcon-debugbar-history-time{display:none}}#phalcon-debugbar table,#phalcon-debugbar tr,#phalcon-debugbar th,#phalcon-debugbar td,#phalcon-debugbar pre{background:#fff0;color:inherit}#phalcon-debugbar table{width:100%;border-collapse:collapse}#phalcon-debugbar th,#phalcon-debugbar td{padding:4px 10px;text-align:left;vertical-align:top;border-bottom:1px solid #23233440;word-break:break-word}#phalcon-debugbar .phalcon-debugbar-grid th,#phalcon-debugbar .phalcon-debugbar-key{width:1%;color:#9a9ac8;font-weight:600;white-space:nowrap}#phalcon-debugbar .phalcon-debugbar-value{color:#e2e2ef}#phalcon-debugbar .phalcon-debugbar-list tr.is-duplicate{background:#2a2118}#phalcon-debugbar .phalcon-debugbar-duplicate-count{display:inline-block;margin-left:10px;padding:1px 6px;border-radius:999px;background:#8a4b08;color:#fff1d6;font-size:11px;font-weight:700;white-space:nowrap}#phalcon-debugbar .phalcon-debugbar-code{margin:0;white-space:pre-wrap;word-break:break-word}#phalcon-debugbar .phalcon-debugbar-empty{padding:6px 2px;color:#6b6b8c;font-style:italic}#phalcon-debugbar .phalcon-debugbar-exception{padding:3px 0;border-bottom:1px solid #23233440}#phalcon-debugbar .phalcon-debugbar-exception-summary{padding:4px 0;cursor:pointer}#phalcon-debugbar .phalcon-debugbar-exception-label{margin-right:8px;color:#ff6b81;font-weight:600}#phalcon-debugbar .phalcon-debugbar-exception-trace{margin:6px 0 10px 16px;padding:8px 10px;background:#0f0f17;border-radius:4px;color:#b9b9d4;font-size:11px;white-space:pre-wrap;word-break:break-word}#phalcon-debugbar .phalcon-debugbar-log{padding:3px 0;border-bottom:1px solid #23233440}#phalcon-debugbar .phalcon-debugbar-log-summary{padding:4px 0;cursor:pointer}#phalcon-debugbar .phalcon-debugbar-log-label{margin-right:8px;color:#7cc4ff;font-weight:600}#phalcon-debugbar .phalcon-debugbar-log-context{margin:6px 0 10px 16px;padding:8px 10px;background:#0f0f17;border-radius:4px;color:#b9b9d4;font-size:11px;white-space:pre-wrap;word-break:break-word} \ No newline at end of file diff --git a/resources/assets/debugbar.min.js b/resources/assets/debugbar.min.js index 30f14eb..3e9f608 100644 --- a/resources/assets/debugbar.min.js +++ b/resources/assets/debugbar.min.js @@ -1,4 +1,4 @@ -(function(){'use strict';var STORAGE_KEY='phalcon-debugbar-collapsed';var HISTORY_VERSION=1;function ready(fn){if(document.readyState!=='loading'){fn()}else{document.addEventListener('DOMContentLoaded',fn)}} +(function(){'use strict';var STORAGE_KEY='phalcon-debugbar-collapsed';var HISTORY_VERSION=1;var REQUEST_TIMEOUT_MS=10000;function ready(fn){if(document.readyState!=='loading'){fn()}else{document.addEventListener('DOMContentLoaded',fn)}} function el(tag,className,text){var node=document.createElement(tag);if(className){node.className=className} if(text!==undefined&&text!==null){node.textContent=text} return node} @@ -45,8 +45,10 @@ var wrap=el('div','phalcon-debugbar-summary');summary.forEach(function(item){ite function hasBadge(badge){return badge!==null&&badge!==undefined&&badge!==''&&badge!==0} function historyUrl(url,id){if(!id){return url} return url+(url.indexOf('?')===-1?'?':'&')+'id='+encodeURIComponent(id)} -function requestJson(url,options){options=options||{};options.credentials='same-origin';options.headers=options.headers||{};options.headers.Accept='application/json';return window.fetch(url,options).then(function(response){if(!response.ok){throw new Error('HTTP '+response.status)} -return response.json()})} +function requestJson(url,options){options=options||{};options.credentials='same-origin';options.headers=options.headers||{};options.headers.Accept='application/json';var timer=null;if(window.AbortController&&window.setTimeout&&window.clearTimeout){var controller=new window.AbortController();options.signal=controller.signal;timer=window.setTimeout(function(){controller.abort()},REQUEST_TIMEOUT_MS)} +function stopTimer(){if(timer!==null){window.clearTimeout(timer)}} +return window.fetch(url,options).then(function(response){if(!response.ok){throw new Error('HTTP '+response.status)} +return response.json()}).then(function(data){stopTimer();return data},function(error){stopTimer();throw error})} function loadJson(url){return requestJson(url,{})} function createHistoryRequestGuard(isCurrent){var clearInProgress=!1;var listGeneration=0;var detailGeneration=0;return{startList:function(){if(clearInProgress){return null} detailGeneration++;return++listGeneration},startDetail:function(){if(clearInProgress){return null} @@ -59,10 +61,10 @@ function refresh(){var generation=requestGuard.startList();if(generation===null) refreshButton.disabled=!0;message('phalcon-debugbar-history-loading','Loading request history...');loadJson(url).then(function(result){if(!requestGuard.isListCurrent(generation)){return} content.innerHTML='';var requests=result&&Array.isArray(result.requests)?result.requests:[];clearButton.disabled=!requests.length;if(!requests.length){content.appendChild(el('div','phalcon-debugbar-history-empty','No stored requests'));return} var list=el('div','phalcon-debugbar-history-list');requests.forEach(function(request){request=request||{};var id=scalar(request.id);var button=el('button','phalcon-debugbar-history-request');button.type='button';if(id===options.selectedId){button.classList.add('is-selected')} -button.appendChild(el('span','phalcon-debugbar-history-method method-'+scalar(request.method).toLowerCase(),scalar(request.method)));button.appendChild(el('span','phalcon-debugbar-history-uri',scalar(request.uri)));button.appendChild(el('span','phalcon-debugbar-history-status',scalar(request.status)));button.appendChild(el('time','phalcon-debugbar-history-time',scalar(request.requested_at)));button.addEventListener('click',function(){var selection=requestGuard.startDetail();if(selection===null){return} -button.disabled=!0;loadJson(historyUrl(url,id)).then(function(detail){if(!requestGuard.isDetailCurrent(selection)){button.disabled=!1;return} +button.appendChild(el('span','phalcon-debugbar-history-method method-'+scalar(request.method).toLowerCase(),scalar(request.method)));button.appendChild(el('span','phalcon-debugbar-history-uri',scalar(request.uri)));button.appendChild(el('span','phalcon-debugbar-history-status',scalar(request.status)));button.appendChild(el('time','phalcon-debugbar-history-time',scalar(request.requested_at)));var rowError=null;button.addEventListener('click',function(){var selection=requestGuard.startDetail();if(selection===null){return} +button.disabled=!0;button.classList.remove('is-error');loadJson(historyUrl(url,id)).then(function(detail){if(!requestGuard.isDetailCurrent(selection)){button.disabled=!1;return} if(detail&&detail.request&&detail.request.version===HISTORY_VERSION&&detail.request.payload){Array.prototype.forEach.call(list.querySelectorAll('.phalcon-debugbar-history-request'),function(requestButton){requestButton.classList.remove('is-selected')});button.classList.add('is-selected');options.onSelect(detail.request.payload,detail.request.meta||{},id)} -button.disabled=!1}).catch(function(){button.disabled=!1})});list.appendChild(button)});content.appendChild(list)}).catch(function(){if(requestGuard.isListCurrent(generation)){message('phalcon-debugbar-history-error','Unable to load request history')}}).then(function(){if(requestGuard.isListCurrent(generation)){refreshButton.disabled=!1}})} +button.disabled=!1}).catch(function(){button.disabled=!1;button.classList.add('is-error');if(rowError===null){rowError=el('span','phalcon-debugbar-history-request-error','Unable to load');button.appendChild(rowError)}})});list.appendChild(button)});content.appendChild(list)}).catch(function(){if(requestGuard.isListCurrent(generation)){message('phalcon-debugbar-history-error','Unable to load request history')}}).then(function(){if(requestGuard.isListCurrent(generation)){refreshButton.disabled=!1}})} refreshButton.addEventListener('click',refresh);clearButton.addEventListener('click',function(){if(!window.confirm('Clear request history?')){return} var generation=requestGuard.startClear();if(generation===null){return} refreshButton.disabled=!0;clearButton.disabled=!0;message('phalcon-debugbar-history-loading','Clearing request history...');requestJson(url,{method:'DELETE'}).then(function(){if(!requestGuard.finishClear(generation)){return} diff --git a/tests/JavaScript/debugbar.test.js b/tests/JavaScript/debugbar.test.js index b9b4f07..230df23 100644 --- a/tests/JavaScript/debugbar.test.js +++ b/tests/JavaScript/debugbar.test.js @@ -458,6 +458,113 @@ test('request history restores a row after a detail failure', async function () assert.equal(request.classList.contains('is-selected'), false); }); +test('a history request that does not end times out after 10 seconds', async function () { + var timers = []; + global.document = dom.createDocument(); + global.window = { + AbortController: AbortController, + setTimeout: function (callback, delay) { + timers.push({callback: callback, delay: delay}); + return timers.length; + }, + clearTimeout: function () {}, + fetch: function (url, options) { + return new Promise(function (resolve, reject) { + if (options.signal) { + options.signal.addEventListener('abort', function () { + reject(new Error('aborted')); + }); + } + }); + } + }; + var mount = new dom.TestElement('div'); + + renderHistoryBrowser(mount, historyOptions()); + await dom.flushPromises(); + + assert.equal(dom.findByClass(mount, 'phalcon-debugbar-history-action').disabled, true); + assert.deepEqual(timers.map(function (timer) { + return timer.delay; + }), [10000]); + + timers[0].callback(); + await dom.flushPromises(); + + assert.equal( + dom.findByClass(mount, 'phalcon-debugbar-history-error').textContent, + 'Unable to load request history' + ); + assert.equal(dom.findByClass(mount, 'phalcon-debugbar-history-action').disabled, false); +}); + +test('a history request that ends clears its timeout', async function () { + var results = [response({requests: []}), errorResponse(503)]; + var cleared = []; + global.document = dom.createDocument(); + + for (var index = 0; index < results.length; index++) { + global.window = { + AbortController: AbortController, + setTimeout: function () { + return 'timer'; + }, + clearTimeout: function (id) { + cleared.push(id); + }, + fetch: function () { + return Promise.resolve(results[index]); + } + }; + renderHistoryBrowser(new dom.TestElement('div'), historyOptions()); + await dom.flushPromises(); + } + + assert.deepEqual(cleared, ['timer', 'timer']); +}); + +test('a detail failure shows "Unable to load" in its row until the next click', async function () { + var details = []; + global.document = dom.createDocument(); + global.window = { + fetch: function (url) { + if (url.includes('?id=first')) { + var detail = deferred(); + details.push(detail); + + return detail.promise; + } + + return Promise.resolve(response({ + requests: [{id: 'first', method: 'GET', uri: '/first', status: 200}] + })); + } + }; + var mount = new dom.TestElement('div'); + + renderHistoryBrowser(mount, historyOptions()); + await dom.flushPromises(); + + var row = dom.findByClass(mount, 'phalcon-debugbar-history-request'); + row.click(); + details[0].resolve(errorResponse(404)); + await dom.flushPromises(); + + assert.equal(row.classList.contains('is-error'), true); + assert.equal( + dom.findByClass(row, 'phalcon-debugbar-history-request-error').textContent, + 'Unable to load' + ); + + row.click(); + assert.equal(row.classList.contains('is-error'), false); + details[1].reject(new Error('network')); + await dom.flushPromises(); + + assert.equal(row.classList.contains('is-error'), true); + assert.equal(row.querySelectorAll('.phalcon-debugbar-history-request-error').length, 1); +}); + test('request history restores a superseded row after its detail fails', async function () { var failFirst = null; global.document = dom.createDocument(); From 58bb924eb0a60c2a8637b46d52df7f21bb2f4c66 Mon Sep 17 00:00:00 2001 From: Nikolaos Dimopoulos Date: Sat, 7 Nov 2026 20:23:15 -0600 Subject: [PATCH 2/2] [#102] - updating changelog/docs Assisted-by: Claude --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 47e2ec9..bc956c9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -84,6 +84,7 @@ All notable changes to `phalcon/debugbar` are documented here. The format is bas - Show `true` and `false` for booleans in messages, show `[]`, `null` and the class name of an object in the grid, and time each query from its own start (0 when there is no start). [#99](https://github.com/phalcon/debugbar/issues/99) - Debug page: show the previous exceptions, fix the API links, read each file one time, drop the arguments from the `debugVar()` backtrace, match `\vendor\`, and end the line of a skipped `Dump` key. [#100](https://github.com/phalcon/debugbar/issues/100) - Debug page: copy on `http://` pages, show "Copy failed" when a copy fails, and encode the paths in the PhpStorm and VS Code links. [#101](https://github.com/phalcon/debugbar/issues/101) +- Keep host CSS out of the bar tables, code blocks and buttons, stop history requests after 10 seconds, and show "Unable to load" in a history row that fails. [#102](https://github.com/phalcon/debugbar/issues/102) - Test HTML writes, the collapse state and `localStorage` errors in the JS tests, and keep every listener in the fake DOM. [#103](https://github.com/phalcon/debugbar/issues/103) ## [0.4.0](https://github.com/phalcon/debugbar/releases/tag/v0.4.0) (2026-07-14)