├── .gitignore ├── LICENSE.md ├── README.md ├── docs └── custom_panels.md ├── index.js ├── lib ├── assets │ ├── js.js │ └── style.css ├── index.js ├── panels │ ├── index.js │ ├── locals │ │ ├── index.js │ │ └── template.jade │ ├── nav │ │ ├── index.js │ │ └── template.jade │ ├── other_requests │ │ ├── index.js │ │ └── template.jade │ ├── profile │ │ ├── index.js │ │ ├── inject.js │ │ └── template.jade │ ├── request │ │ ├── index.js │ │ └── template.jade │ ├── session │ │ ├── index.js │ │ └── template.jade │ ├── software_info │ │ ├── index.js │ │ └── template.jade │ └── template │ │ ├── index.js │ │ └── template.jade ├── request.js ├── response.js ├── templates │ ├── mixins.jade │ ├── page.jade │ └── toolbar.jade └── utils.js └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | npm-debug.log 3 | .idea/ 4 | *.iml 5 | .DS_Store 6 | demo/ -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | ### License - MIT 2 | Copyright (c) 2013 Tom Hunkapiller and contributors 3 | 4 | Permission is hereby granted, free of charge, to any person obtaining a copy of 5 | this software and associated documentation files (the "Software"), to deal in 6 | the Software without restriction, including without limitation the rights to 7 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 8 | of the Software, and to permit persons to whom the Software is furnished to do 9 | so, subject to the following conditions: 10 | 11 | The above copyright notice and this permission notice shall be included in all 12 | copies or substantial portions of the Software. 13 | 14 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 15 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 16 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 17 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 18 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 19 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # express-debug 2 | express-debug is a development tool for [express](https://github.com/visionmedia/express). It's simple middleware that 3 | injects useful debugging output into your html, in a non-obstructive way. 4 | 5 | It adds an 'EDT' tab to the right side of pages that, when clicked, displays info 6 | such as template variables (locals), current session, useful request data, and 7 | current template. 8 | 9 | If your application doesn't serve HTML, no worries, a standalone express-debug 10 | panel is mounted at `/express-debug`. See settings section for more information. 11 | 12 | express-debug should **NOT** be used in production environments. 13 | 14 | **Compatible with express 3 and 4, node 0.8+** 15 | 16 | 17 | **[Screenshot of the tool in action](http://i.imgur.com/rz3WgSp.png)** 18 | 19 | ### Usage 20 | 21 | #### Install 22 | `npm install express-debug --save-dev` 23 | 24 | #### Use 25 | ```js 26 | var express = require('express'); 27 | var app = express(); 28 | 29 | require('express-debug')(app, {/* settings */}); 30 | 31 | /* ... application logic ... */ 32 | ``` 33 | 34 | 35 | ### Settings 36 | 37 | `depth` - How deep to recurse through printed objects. This is the default unless the print_obj function is passed an options object with a 'depth' property. 38 | (Default: `4`) 39 | 40 | `theme` - Absolute path to a css file to include and override EDT's default css. 41 | 42 | `extra_panels` - additional panels to show. [See docs for custom panels](https://github.com/devoidfury/express-debug/blob/master/docs/custom_panels.md) and 43 | [included panels](https://github.com/devoidfury/express-debug/tree/master/lib/panels) 44 | for proper structure, each panel is an object 45 | (Default: `[]`) 46 | 47 | `panels` - allows changing the default panels (ex: remove a panel) 48 | (Default: `['locals', 'request', 'session', 'template', 'software_info', 'profile']`) 49 | 50 | `path` - path to render standalone express-debug \[set to `null` or `false` to disable\] 51 | (Default: `/express-debug`) 52 | 53 | `extra_attrs` - If you need to add arbitrary attributes to the containing element of EDT, this allows you to. (For example, you may want to use "ng-non-bindable" if you're using angular) 54 | (Default: `''`) 55 | 56 | `sort` - Global option to determine sort order of printed object values. `false` for default order, `true` for basic default sort, or a function to use for sort. [See MDN Array sort](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/sort) 57 | (Default: `false`) 58 | 59 | ### Panels 60 | 61 | `locals` - app.locals, res.locals, and options passed to the template (merged into res.locals) 62 | 63 | `request` - req info. ip, body, query, files, route info, cookies, headers 64 | 65 | `session` - everything in req.session 66 | 67 | `template` - view name, template file 68 | 69 | `software_info` - shows current versions of node and libraries installed locally (not globally installed packages!) 70 | 71 | `profile` - total req processing time. middleware, param, and route timings. (not a default panel; does not work with express 4.x yet) 72 | 73 | `other_requests` - shows details on non-page requests made to the server (not a default panel, use extra_panels setting to invoke. `{extra_panels: ['other_requests']}`) 74 | 75 | `nav` - links to every GET route in your app. (not a default panel) 76 | 77 | 78 | ### Future 79 | * profile panel: express 4.x compatibility 80 | * nav panel: express 4.x - check multiple router instances 81 | * optional error page that prints better stacks 82 | * save more information about non-injected requests 83 | * improve styling 84 | * show session panel on standalone mount 85 | 86 | 87 | ### Changelog 88 | * **1.1.1** 89 | * add `extra_attrs` option to add html attributes to the rendered EDT container 90 | * add `sort` option (thanks to vaughan99) 91 | * fix an issue that caused the panel to be rendered multiple times in some circumstances 92 | 93 | * **1.1.0** 94 | * basic express 4.x support 95 | * profile panel is no longer a default panel 96 | * update connectr 97 | 98 | * **1.0.3** 99 | * usability fix: middleware order no longer matters, thanks to [connectr](https://github.com/olalonde/connectr) 100 | * style tweaks 101 | 102 | * **1.0.2** 103 | * add basic nav panel (not a default panel) 104 | * style and usability improvements 105 | * bugfixes 106 | 107 | 108 | * **1.0.1** 109 | * sidebar moved to top and fixed position for better UX 110 | * fix: render error no longer crashes application 111 | 112 | 113 | * **1.0.0** API changes 114 | * no longer used as a regular middleware, invoke with `edt(app[, settings])` instead 115 | * profile panel now acts like a regular panel 116 | * add standalone express-debug page mounted at `path` setting 117 | * add `standalone` panel setting 118 | * add `pre-render` and `post-render` panel hooks 119 | * profile panel now additionally profiles rendering 120 | 121 | 122 | * **0.2.4** 123 | * no longer breaks error handling middleware 124 | 125 | 126 | * **0.2.3** 127 | * add software info panel 128 | * clean up 129 | 130 | 131 | * **0.2.2** 132 | * finalize panel api 133 | 134 | 135 | * **0.2.1** 136 | * add profiler panel 137 | * modified style 138 | 139 | 140 | * **0.2.0** 141 | * pluggable panels 142 | * theme addition and bugfix by jaketrent 143 | 144 | 145 | * **0.1.2** 146 | * objects can now be collapsed 147 | * functions are now collapsed by default, showing only # of formal args and name, but can be expanded 148 | * separated css and js from main toolbar template 149 | 150 | 151 | * **0.1.1** 152 | * remove environment checks 153 | * fix "view engine" directive, make template reading safer 154 | 155 | 156 | ### License - MIT 157 | Copyright (c) 2014 Tom Hunkapiller and contributors 158 | 159 | Permission is hereby granted, free of charge, to any person obtaining a copy of 160 | this software and associated documentation files (the "Software"), to deal in 161 | the Software without restriction, including without limitation the rights to 162 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 163 | of the Software, and to permit persons to whom the Software is furnished to do 164 | so, subject to the following conditions: 165 | 166 | The above copyright notice and this permission notice shall be included in all 167 | copies or substantial portions of the Software. 168 | 169 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 170 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 171 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 172 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, 173 | WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN 174 | CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 175 | -------------------------------------------------------------------------------- /docs/custom_panels.md: -------------------------------------------------------------------------------- 1 | #### Custom Panels 2 | Each panel is an object, in the form: 3 | 4 | ```js 5 | my_panel = { 6 | name: 'panel_name', 7 | template: '/absolute/path/to/template.jade', 8 | process: function(locals){ 9 | // locals: {app, req, res, view, locals} 10 | 11 | // ... logic here ... 12 | return { locals: {/* my template locals */}}; 13 | } 14 | } 15 | ``` 16 | Optionally, panels supports the following additional properties: 17 | 18 | standalone: set this property to `true` to display this panel on the standalone express-debug mount 19 | 20 | initialize: 21 | ```js 22 | my_panel.initialize = function(app) { 23 | // perform initialization here, when the application loads 24 | } 25 | ``` 26 | 27 | request: 28 | ```js 29 | my_panel.request = function(req) { 30 | // perform initialization here, for every request 31 | } 32 | ``` 33 | 34 | finalize: 35 | ```js 36 | my_panel.finalize = function(req) { 37 | // finish up here, as soon as render is called 38 | } 39 | ``` 40 | 41 | pre_render: 42 | ```js 43 | my_panel.pre_render = function(req) { 44 | // just before rendering 45 | } 46 | 47 | ``` 48 | post_render: 49 | ```js 50 | my_panel.post_render = function(req) { 51 | // just after rendering 52 | } 53 | ``` 54 | 55 | The panels are provided two mixins: print_val, and print_obj. See express-debug/lib/templates/mixins.jade 56 | 57 | The `options` parameter is optional, and allows you to pass in a custom sort option or depth (the max-depth to print recursively). If omitted, the default global setting will be used. 58 | The `depth` parameter is the current depth level of the call, so should be initially omitted, or if used with the options parameter, it should be falsey (0, null, undefined, false, ''). -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = require('./lib'); -------------------------------------------------------------------------------- /lib/assets/js.js: -------------------------------------------------------------------------------- 1 | (function () { 2 | "use strict"; 3 | var $EDT = document.getElementById('EDT'), 4 | $menu = document.querySelector('#EDT-menu ul'), 5 | $close = document.getElementById('EDT-close'), 6 | $show = document.getElementById('EDT-show'), 7 | mount_path = document.getElementById('express-debug').attributes['data-path'].value || null; 8 | 9 | var util = { 10 | getTarget: function(event) { 11 | return event.originalTarget || event.srcElement; 12 | }, 13 | 14 | getHref: function (event) { 15 | var el = util.getTarget(event); 16 | 17 | return el && el.attributes && (el.attributes.hash || el.attributes.href); 18 | }, 19 | 20 | hasClass: function(el, klass) { 21 | return el.className && el.className.indexOf(klass) !== -1; 22 | }, 23 | 24 | nextSibling: function(el) { 25 | var next = el.nextSibling; 26 | while (next && next.nodeType != 1) { 27 | next = next.nextSibling 28 | } 29 | return next; 30 | }, 31 | 32 | getParentWithClass: function(el, klass) { 33 | var next = el.parentNode; 34 | while (next && !util.hasClass(next, klass)) { 35 | next = next.parentNode 36 | } 37 | return next; 38 | } 39 | }; 40 | 41 | // panel selector 42 | $menu.addEventListener('click', function (e) { 43 | var tab_id = util.getHref(e); 44 | if (tab_id) { 45 | var $active = document.querySelectorAll('#EDT-menu .active'); 46 | for (var i = 0; i < $active.length; i++) { 47 | $active[i].className = ''; 48 | } 49 | util.getTarget(e).className = 'active'; 50 | 51 | e.preventDefault(); 52 | var $actives = document.querySelectorAll('#EDT .tab.active'); 53 | for (var i = 0; i < $actives.length; i++) { 54 | $actives[i].className = 'tab'; 55 | } 56 | 57 | tab_id = tab_id.value.substring(1); 58 | document.getElementById(tab_id).className = 'tab active'; 59 | } 60 | }); 61 | 62 | // EDT close button 63 | $close.addEventListener('click', function (e) { 64 | e.preventDefault(); 65 | $EDT.style.display = 'none'; 66 | $show.style.display = 'block'; 67 | }); 68 | 69 | // EDT show button 70 | $show.addEventListener('click', function (e) { 71 | e.preventDefault(); 72 | $EDT.style.display = 'block'; 73 | $show.style.display = 'none'; 74 | }); 75 | 76 | // expand/collapse catchall 77 | $EDT.addEventListener('click', function (e) { 78 | var el = util.getTarget(e), 79 | $fn; 80 | 81 | // show/hide functions 82 | if (util.hasClass(el, 'showFn')) { 83 | $fn = util.nextSibling(el); 84 | if ($fn) { 85 | $fn.style.display = 'block'; 86 | } 87 | el.className = 'hideFn'; 88 | e.preventDefault(); 89 | 90 | } else if (util.hasClass(el, 'hideFn')) { 91 | $fn = util.nextSibling(el); 92 | if ($fn) { 93 | $fn.style.display = 'none'; 94 | } 95 | el.className = 'showFn'; 96 | e.preventDefault(); 97 | 98 | // show/hide objects 99 | } else if (util.getParentWithClass(el, 'collapse')) { 100 | var $object = util.getParentWithClass(el, 'object'); 101 | $object.children[1].style.display = 'none'; 102 | util.getParentWithClass(el, 'collapse').className = 'expand'; 103 | e.preventDefault(); 104 | 105 | } else if (util.getParentWithClass(el, 'expand')) { 106 | var $object = util.getParentWithClass(el, 'object'); 107 | $object.children[1].style.display = 'table-row-group'; 108 | util.getParentWithClass(el, 'expand').className = 'collapse'; 109 | e.preventDefault(); 110 | } 111 | }); 112 | 113 | if (mount_path) { 114 | // TODO: init polling for non-html requests 115 | } 116 | })(); 117 | -------------------------------------------------------------------------------- /lib/assets/style.css: -------------------------------------------------------------------------------- 1 | /* make standalone page show express-debug by default and disable show/hide buttons */ 2 | .express-debug.standalone #EDT #EDT-close { display: none; } 3 | .express-debug.standalone #EDT-show { display: none; } 4 | .express-debug.standalone #EDT { display: block; } 5 | 6 | /* core styling */ 7 | .express-debug { position: static !important; } 8 | 9 | .express-debug * { 10 | font-size: inherit; 11 | font-style: inherit; 12 | font-weight: inherit; 13 | color: inherit; 14 | background: none repeat transparent; 15 | font-family: Consolas, Monaco, monospace, sans-serif; 16 | vertical-align: baseline; 17 | margin: 0; 18 | padding: 0; 19 | box-sizing: border-box; -moz-box-sizing: border-box; -webkit-box-sizing: border-box; 20 | } 21 | .express-debug a { cursor: pointer !important; } 22 | 23 | /* SHOW/HIDE buttons */ 24 | #EDT-show { 25 | position: fixed; 26 | top: 30%; 27 | right: 0; 28 | z-index: 1000; 29 | 30 | background-color: rgba(255, 255, 255, 0.55); 31 | border: 2px solid rgba(0, 0, 0, 0.4); 32 | padding: 10px; 33 | 34 | font-size: 20px; 35 | font-weight: bold; 36 | 37 | cursor: pointer; 38 | color: #444; 39 | } 40 | #EDT-show:hover, #EDT-show:focus { color: #af2b2b; outline: none } 41 | 42 | 43 | #EDT #EDT-close { 44 | font-weight: bold; 45 | display: block; 46 | float: right; 47 | margin-right: 20px; 48 | border-left: 1px solid #717171; 49 | border-right: 1px solid #717171; 50 | } 51 | 52 | /* main layout */ 53 | #EDT { 54 | display: none; 55 | font-size: 14px; 56 | 57 | z-index: 1000; 58 | position: absolute; 59 | top: 0; 60 | right: 0; 61 | left: 0; 62 | bottom: 0; 63 | } 64 | 65 | #EDT td, #EDT th { vertical-align: top; } 66 | 67 | #EDT #EDT-main { 68 | background-color: rgba(255, 255, 255, 0.95); 69 | margin-top: 34px; 70 | padding: 20px 20px 20px 20px; 71 | width: 100%; 72 | min-height: 100%; 73 | } 74 | 75 | 76 | #EDT .tab { display: none; } 77 | #EDT .tab.active { display: block; } 78 | 79 | #EDT h2, #EDT h4, #EDT-menu { font-size: 18px } 80 | #EDT h2 { font-weight: bold; color: #CCC; margin-bottom: 14px; } 81 | #EDT h4 { 82 | display: inline-block; 83 | padding-left: 20px; 84 | font-weight: bold; 85 | color: #000; 86 | margin-bottom: 12px; 87 | } 88 | 89 | #EDT-menu { 90 | width: 100%; 91 | text-align: center; 92 | position: fixed; 93 | top: 0; 94 | 95 | background-color: rgba(33, 33, 33, 0.95); 96 | color: #CCC; 97 | line-height: 32px; 98 | border-bottom: 1px solid rgba(132, 132, 132, 0.93); 99 | outline: 1px solid rgba(33, 33, 33, 0.95); 100 | } 101 | 102 | #EDT-menu span.text, #EDT-menu a { 103 | display: inline-block; 104 | padding: 0 10px 0 10px; 105 | } 106 | 107 | #EDT-menu div.sep { 108 | border-left: 1px solid #717171; 109 | height: 32px; 110 | vertical-align: top; 111 | width: 1px; 112 | display: inline-block; 113 | } 114 | 115 | #EDT-menu ul, #EDT-menu li, #EDT-menu a { 116 | display: inline-block; 117 | list-style: none; 118 | color: #df3333; 119 | text-decoration: none; 120 | } 121 | 122 | #EDT-menu a:hover, #EDT-menu a:focus, #EDT-menu a.active { 123 | color: #ff2f2f; 124 | background-color: rgba(255, 255, 255, 0.15); 125 | outline: none; 126 | } 127 | #EDT-menu a:hover, #EDT-menu a:focus { 128 | text-decoration: underline; 129 | } 130 | 131 | #EDT h4 span { padding-left: 20px; font-weight: normal; font-size: 14px; } 132 | 133 | #EDT .edt-nav li { 134 | display: block; 135 | list-style: none; 136 | padding: 5px; 137 | } 138 | #EDT .edt-nav a { 139 | text-decoration: none; 140 | font-size: 16px; 141 | line-height: 26px; 142 | } 143 | 144 | #EDT .edt-nav a:hover { 145 | text-decoration: underline; 146 | } 147 | 148 | #EDT .profile th:nth-child(1) { width: 10%; min-width: 130px} 149 | #EDT .profile th:nth-child(2) { width: 18%; min-width: 100px} 150 | #EDT .profile th:nth-child(4) { width: 10%; min-width: 100px} 151 | 152 | #EDT table.profile, #EDT table.object { 153 | border: 1px solid #DDD; 154 | width: 100%; 155 | border-collapse: collapse; 156 | margin-bottom: 12px; 157 | } 158 | #EDT table.object { table-layout:fixed; } 159 | 160 | #EDT th { font-weight: bold; } 161 | #EDT .object tr:nth-child(2n), #EDT table.profile tr:nth-child(2n) { background-color: rgba(0, 0, 0, 0.03); } 162 | 163 | #EDT .object th, 164 | #EDT .profile th, 165 | #EDT .object td, 166 | #EDT .profile td { padding: 8px; word-wrap: break-word; color: #000; text-align: left; } 167 | 168 | #EDT .object th:first-child { min-width: 126px; width: 13%; } 169 | #EDT .object th.arr:first-child { width: 60px; min-width: 60px; } 170 | 171 | #EDT .object .collapse, #EDT .object .expand { cursor: pointer; } 172 | #EDT .object .indicator { text-align: right; font-size: 16px; color: #000; } 173 | #EDT .collapse .indicator:after { content: '(-)'; } 174 | #EDT .expand .indicator:after { content: '(+)'; } 175 | 176 | #EDT table .right { text-align: right; } 177 | 178 | /* value output styling */ 179 | #EDT .string { color: #179900 } 180 | #EDT .string:before, #EDT .string:after { content: '"'; color: #000; } 181 | #EDT .number { color: #af2b2b; font-weight: bold; } 182 | #EDT .boolean { color: #00F; } 183 | #EDT .undefined { font-style: italic; } 184 | #EDT .undefined, #EDT .null { color: #a500a0; } 185 | #EDT .date { color: #179900; } 186 | #EDT .date:before { content: 'Date("'; color: #000; } 187 | #EDT .date:after { content: '")'; color: #000; } 188 | #EDT .exceeded { color: #666; font-style: italic; } 189 | /* template code and functions */ 190 | #EDT pre { background-color: #F5F5F5; color: #444; border: 1px solid #DDD; padding: 8px; } 191 | #EDT pre.fn { display: none; margin-top: 6px; } 192 | #EDT .showFn, #EDT .hideFn { cursor: pointer; } 193 | #EDT .showFn:before, #EDT .showFn:after, #EDT .hideFn:before, #EDT .hideFn:after { color: #666; font-weight: bold; } 194 | #EDT .showFn:before, #EDT .hideFn:before { content: 'fn () { '; } 195 | #EDT .showFn:after { content: ' } (+)'; } 196 | #EDT .hideFn:after { content: ' } (-)'; } 197 | 198 | /* fix text selection when trying to use expand/collapse */ 199 | #EDT .indicator, #EDT.showFn, #EDT.hideFn { 200 | -webkit-user-select: none;-moz-user-select: none;-khtml-user-select: none;-ms-user-select: none;user-select: none; 201 | } 202 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var path = require('path'), 3 | xtend = require('xtend'), 4 | panels = require('./panels'), 5 | request = require('./request'), 6 | response = require('./response'); 7 | 8 | // default express-debug settings 9 | var defaults = { 10 | panels: ['locals', 'request', 'session', 'template', 'software_info', 'profile'], 11 | depth: 4, 12 | extra_panels: [], 13 | path: '/express-debug', 14 | extra_attrs: '', 15 | sort: false 16 | }; 17 | 18 | 19 | module.exports = function (app, settings) { 20 | // initialize settings 21 | settings = xtend(defaults, settings || {}); 22 | 23 | 24 | response.init(app, settings); 25 | 26 | var handle = app.handle; 27 | app.handle = function (req) { 28 | req.EDT = {}; 29 | panels.handle(req); 30 | handle.apply(this, arguments); 31 | }; 32 | // we need to carefully insert EDT at the correct location in the 33 | // middleware stack to maintain all functionality 34 | 35 | // express 3 36 | var connectr; 37 | if (app.stack) { 38 | connectr = require('connectr')(app); 39 | 40 | } else { 41 | // express 4 42 | 43 | if (!app.lazyrouter) { 44 | throw new Error('this version of express is not supported. ' + 45 | 'Please raise an issue on express-debug github page') 46 | } 47 | app.lazyrouter(); 48 | 49 | if (!app._router) { 50 | throw new Error('this version of express is not supported. ' + 51 | 'Please raise an issue on express-debug github page') 52 | } 53 | 54 | connectr = require('connectr')(app); 55 | connectr.stack = app._router.stack; 56 | } 57 | 58 | // load and initialize panels 59 | panels.load(app, settings.panels.concat(settings.extra_panels), settings); 60 | 61 | connectr.index(1).as('express'); 62 | 63 | connectr.after('express').use(function EDT(req, res, next) { 64 | panels.request(req); 65 | response.patch(res); 66 | 67 | if (settings.path === req.path) { 68 | // standalone express-debug page 69 | res.render(); 70 | } else { 71 | next(); 72 | } 73 | }).as('express-debug'); 74 | 75 | 76 | if (panels.use_requests) { 77 | // grab raw request body in case of non-JSON/form-data 78 | connectr.after('express-debug').use(request.rawBody); 79 | } 80 | }; 81 | -------------------------------------------------------------------------------- /lib/panels/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var panels = module.exports, 3 | path = require('path'), 4 | jade = require('jade'), 5 | fs = require('fs'), 6 | loaded_panels = [], 7 | 8 | hooks = { 9 | handle: [], // need to run a request initializer per request (beginning of request, minimal functionality) 10 | request: [], // need to run a request initializer per request (EDT middleware time, more functionality) 11 | finalize: [], // need to run a request finalizer per request 12 | pre_render: [], // pre-render per request (after finalize) 13 | post_render: [] // post-render per request 14 | }, 15 | 16 | // compiled jade templates 17 | cached_panels = {}; 18 | 19 | panels.use_requests = false; 20 | 21 | panels.load = function(app, _panels, settings) { 22 | settings.mixin_path = path.resolve(path.join(__dirname, '..', 'templates', 'mixins.jade')); 23 | 24 | _panels.forEach(function (panel) { 25 | // builtins 26 | if (typeof panel === 'string') { 27 | try { 28 | var tmp = require('./' + panel); 29 | loaded_panels.push(tmp) 30 | } catch (e) { 31 | console.error('EDT: Error loading builtin panel ' + panel, e); 32 | } 33 | 34 | // custom panels 35 | } else if (typeof panel === 'object') { 36 | loaded_panels.push(panel); 37 | } 38 | }); 39 | 40 | loaded_panels.forEach(function (panel) { 41 | // initialize panels if they need it 42 | panel.initialize && panel.initialize(app); 43 | 44 | // keep track of requests if we need to 45 | panels.use_requests = panels.use_requests || panel.use_requests; 46 | 47 | // compile the templates with mixins injected 48 | var tmpl = fs.readFileSync(panel.template, 'utf-8'); 49 | tmpl = 'include ' + path.relative(path.dirname(panel.template), settings.mixin_path) + '\n\n' + tmpl; 50 | cached_panels[panel.template] = jade.compile(tmpl, {filename: panel.template}); 51 | 52 | // prepare request hooks 53 | Object.keys(hooks).forEach(function (key) { 54 | panel[key] && hooks[key].push(panel); 55 | }); 56 | }); 57 | }; 58 | 59 | // hook runner factory 60 | var hook_handle = function(key) { 61 | var hook = hooks[key]; 62 | return function(req) { 63 | for (var i = 0; i < hook.length; i++) { 64 | hook[i][key](req); 65 | } 66 | } 67 | }; 68 | 69 | panels.handle = hook_handle('handle'); 70 | panels.request = hook_handle('request'); 71 | panels.finalize = hook_handle('finalize'); 72 | panels.pre_render = hook_handle('pre_render'); 73 | panels.post_render = hook_handle('post_render'); 74 | 75 | panels.render = function(locals, settings, standalone) { 76 | var rendered = []; 77 | 78 | for (var i = 0; i < loaded_panels.length; i++) { 79 | var panel = loaded_panels[i]; 80 | 81 | // if standalone page, skip some irrelevant panels 82 | if (!standalone || panel.standalone === true) { 83 | 84 | // main panel function 85 | var result = panel.process(locals); 86 | 87 | // tack on settings for settings like object depth 88 | result.locals.EDTsettings = settings; 89 | 90 | rendered.push({ 91 | html: cached_panels[panel.template](result.locals), 92 | name: panel.name 93 | }); 94 | } 95 | } 96 | 97 | return rendered; 98 | }; 99 | -------------------------------------------------------------------------------- /lib/panels/locals/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var path = require('path'); 3 | 4 | module.exports = { 5 | name: 'locals', 6 | template: path.join(__dirname, 'template.jade'), 7 | standalone: true, 8 | 9 | process: function(params) { 10 | return { 11 | locals: { 12 | res_locals: params.locals, 13 | app_locals: params.app.locals 14 | } 15 | }; 16 | } 17 | }; -------------------------------------------------------------------------------- /lib/panels/locals/template.jade: -------------------------------------------------------------------------------- 1 | h4 res.locals 2 | mixin print_obj(res_locals) 3 | 4 | h4 app.locals 5 | mixin print_obj(app_locals) -------------------------------------------------------------------------------- /lib/panels/nav/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var path = require('path'); 3 | 4 | module.exports = { 5 | name: 'nav', 6 | template: path.join(__dirname, 'template.jade'), 7 | standalone: true, 8 | 9 | process: function(params) { 10 | var links = []; 11 | 12 | if (params.app.routes) { // express 3.x 13 | params.app.routes.get.forEach(function(route) { 14 | links.push(route.path); 15 | }); 16 | 17 | } else { // express 4.x 18 | var stack = params.app._router.stack; 19 | 20 | // TODO: this probably needs work for multiple routers 21 | stack.forEach(function(item) { 22 | var meths; 23 | if (item.route) { 24 | meths = (item.route || {}).methods || {}; 25 | 26 | if (meths.get) 27 | links.push(item.route.path); 28 | } 29 | }); 30 | } 31 | 32 | return { 33 | locals: { 34 | nav: links 35 | } 36 | }; 37 | } 38 | }; 39 | -------------------------------------------------------------------------------- /lib/panels/nav/template.jade: -------------------------------------------------------------------------------- 1 | h4 Nav 2 | 3 | ul.edt-nav 4 | - nav.forEach(function(link) { 5 | li: a(href=link) #{link} 6 | - }) 7 | -------------------------------------------------------------------------------- /lib/panels/other_requests/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var path = require('path'), 3 | requests = require('../../request.js'); 4 | 5 | 6 | module.exports = { 7 | name: 'other requests', 8 | template: path.join(__dirname, 'template.jade'), 9 | standalone: true, 10 | use_requests: true, 11 | 12 | process: function(params) { 13 | return { 14 | locals: { 15 | requests: requests.list() 16 | 17 | } 18 | }; 19 | } 20 | }; -------------------------------------------------------------------------------- /lib/panels/other_requests/template.jade: -------------------------------------------------------------------------------- 1 | h4 requests 2 | 3 | - requests.forEach(function(request){ 4 | mixin print_obj(request) 5 | - }) 6 | -------------------------------------------------------------------------------- /lib/panels/profile/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var path = require('path'), 3 | inject = require('./inject'), 4 | utils = require('../../utils'); 5 | 6 | 7 | module.exports = { 8 | name: 'profile', 9 | template: path.join(__dirname, 'template.jade'), 10 | 11 | initialize: function(app) { 12 | // hijacks early middleware 13 | // on the first request, these middleware would have already executed 14 | // and wouldn't be caught on the first request 15 | inject.middleware_profiler(app); 16 | }, 17 | 18 | handle: function(req) { 19 | req.EDT.profile = { global: process.hrtime() }; 20 | }, 21 | 22 | request: function(req) { 23 | // run these per request, as some may do dynamic route changes 24 | // because the route internals are public 25 | // the injector marks hijacked functions, so anything already wrapped 26 | // will be skipped quickly 27 | inject.middleware_profiler(req.app); 28 | inject.param_handlers(req.app); 29 | inject.route_profiler(req.app); 30 | }, 31 | 32 | // ends all open req and fn timers (done before rendering for accuracy) 33 | finalize: inject.finalize, 34 | 35 | pre_render: function(req) { 36 | req.EDT.profile.render = { 37 | hr_start: process.hrtime() 38 | }; 39 | }, 40 | 41 | post_render: function(req) { 42 | var time = req.EDT.profile.render.hr_start; 43 | if (time) { 44 | var diff = process.hrtime(time); 45 | req.EDT.profile.render = { 46 | seconds: diff[0], 47 | milliseconds: utils.get_ms_from_ns(diff[1]) 48 | }; 49 | } 50 | }, 51 | 52 | process: function (params) { 53 | var times = params.req.EDT.profile, 54 | EDT_times; 55 | 56 | var middleware = times.middleware, 57 | filtered_middleware = []; 58 | 59 | middleware.forEach(function (item) { 60 | delete item.action.EDT; 61 | if (item.action.handler.name === 'EDT') { 62 | EDT_times = item; 63 | } else { 64 | filtered_middleware.push(item); 65 | } 66 | }); 67 | 68 | if (EDT_times) { // remove EDT toolbar time from total 69 | times.global[0] -= EDT_times[0]; 70 | times.global[1] -= EDT_times[1]; 71 | } 72 | 73 | return { 74 | locals: { 75 | middleware: filtered_middleware, 76 | params: times.param || [], 77 | routes: times.route || [], 78 | global_time: times.global, 79 | render: times.render 80 | } 81 | }; 82 | } 83 | }; 84 | -------------------------------------------------------------------------------- /lib/panels/profile/inject.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var injections = module.exports, 3 | utils = require('../../utils'), 4 | _EDT_HIDDEN = { EDT_hidden: true }; 5 | 6 | var ProfileTime = function(action, name) { 7 | this.action = action; 8 | this.name = name; 9 | }; 10 | ProfileTime.prototype.toJSON = function() { 11 | var str = this.name + ' fn() { args: ' + this.action.handler.length; 12 | str += ', name: ' + this.action.handler.name + ' }'; 13 | str += ' Time: ' + (this.time.seconds * 1000 + this.time.milliseconds).toFixed(2) + 'ms'; 14 | return str; 15 | }; 16 | 17 | var hijack = function(original, action, time_name) { 18 | // returns a wrapper that will replace any middleware/route function 19 | // following the pattern of 'function(req, x, next[, ...])' 20 | return function() { 21 | var args = Array.prototype.slice.apply(arguments), 22 | next = args[2], 23 | req = args[0], 24 | 25 | times = req.EDT.profile[time_name] = req.EDT.profile[time_name] || [], 26 | time = new ProfileTime(action, time_name + ' #' + times.length); 27 | 28 | times[times.length] = time; 29 | 30 | args[2] = function (err) { 31 | if (!time.time) { 32 | // end profiling 33 | var diff = process.hrtime(time.hr_start); 34 | time.time = { 35 | seconds: diff[0], 36 | milliseconds: utils.get_ms_from_ns(diff[1]) 37 | }; 38 | delete time.hr_start; 39 | } 40 | next.apply(this, arguments); 41 | }; 42 | 43 | // begin profiling 44 | time.hr_start = process.hrtime(); 45 | original.apply(this, args); 46 | }; 47 | }; 48 | 49 | // global request timer 50 | 51 | injections.middleware_profiler = function (app) { 52 | var stack = app.stack, 53 | original, 54 | type; 55 | 56 | //for express 4 57 | if(!app.stack){ 58 | stack = app._router.stack; 59 | } 60 | 61 | for (var i = 0; i < stack.length; i++) { 62 | // skip middleware already covered, also 63 | // middleware with 4 params is error handling, do not wrap 64 | if (!stack[i].handler && stack[i].handle.length < 4) { 65 | 66 | original = stack[i].handle; 67 | stack[i].handler = original; 68 | if(stack[i].route) { 69 | type = 'route'; 70 | } 71 | else{ 72 | type = 'middleware'; 73 | } 74 | stack[i].handle = hijack(original, stack[i], type); 75 | stack[i].handle.EDT_hidden = true; 76 | } 77 | } 78 | }; 79 | 80 | injections.param_handlers = function (app) { 81 | var params = app._router.params, 82 | flat_params = []; 83 | 84 | Object.keys(params).forEach(function (key) { 85 | var key_params = utils.flatten(params[key]).map(function (item) { 86 | item.key = key; 87 | return item; 88 | }); 89 | flat_params = flat_params.concat(key_params); 90 | }); 91 | flat_params.forEach(function (fn) { 92 | var parent = fn.EDT_parent, 93 | i = fn.EDT_index; 94 | // skip already hijacked params 95 | if (!parent[i].EDT) { 96 | parent[i].EDT_hidden = true; 97 | parent[i] = hijack(fn, { param: fn.key, action: fn }, 'param'); 98 | parent[i].EDT = _EDT_HIDDEN; 99 | } 100 | }); 101 | }; 102 | 103 | injections.route_profiler = function(app) { 104 | //for express 4 105 | if(!app.routes) { 106 | return; 107 | } 108 | Object.keys(app.routes).forEach(function(method) { 109 | var routes = app.routes[method]; 110 | 111 | routes.forEach(function(rt) { 112 | var current = utils.flatten(rt.callbacks); 113 | 114 | current.forEach(function (cb) { 115 | var parent = cb.EDT_parent, 116 | i = cb.EDT_index; 117 | 118 | if (!parent[i].EDT) { 119 | parent[i].EDT_hidden = true; 120 | 121 | var action = { method: method, route: rt.path, handler: cb }; 122 | 123 | parent[i] = hijack(cb, action, 'route'); 124 | parent[i].EDT = _EDT_HIDDEN; 125 | } 126 | }); 127 | }); 128 | }); 129 | }; 130 | 131 | // close any open 'timers' 132 | injections.finalize = function(req) { 133 | var now = process.hrtime(), 134 | times = req.EDT.profile; 135 | times.global = { 136 | seconds: now[0] - times.global[0], 137 | milliseconds: utils.get_ms_from_ns(now[1] - times.global[1]) 138 | }; 139 | times.global.toJSON = function() { 140 | return 'Time: ' + (this.seconds * 1000 + this.milliseconds).toFixed(2) + 'ms'; 141 | }; 142 | 143 | Object.keys(times).forEach(function(type) { 144 | if (times[type] instanceof Array) { 145 | times[type].forEach(function (profile) { 146 | if (!profile.time) { 147 | profile.time = { 148 | seconds: now[0] - profile.hr_start[0], 149 | milliseconds: utils.get_ms_from_ns(now[1] - profile.hr_start[1]) 150 | }; 151 | delete profile.hr_start; 152 | } 153 | }); 154 | } 155 | }); 156 | }; 157 | -------------------------------------------------------------------------------- /lib/panels/profile/template.jade: -------------------------------------------------------------------------------- 1 | h4 profile 2 | table.profile 3 | thead: tr.collapse 4 | th name 5 | th route 6 | th handler 7 | th.right time 8 | tbody 9 | tr 10 | td req total 11 | td(colspan=2) * 12 | td.right #{(global_time.seconds * 1000 + global_time.milliseconds).toFixed(2)}ms 13 | 14 | - middleware.forEach(function(val, i){ 15 | tr 16 | td middleware ##{i} 17 | td 18 | - if (val.action.route === '') 19 | span * 20 | - else 21 | span.string #{val.action.route} 22 | td: mixin print_val(val.action.handler, 0) 23 | td.right #{(val.time.seconds * 1000 + val.time.milliseconds).toFixed(2)}ms 24 | - }) 25 | 26 | - params.forEach(function(val, i){ 27 | tr 28 | td param ##{i} 29 | td: span */:#{val.action.param}/* 30 | td: mixin print_val(val.action.action, 0) 31 | td.right #{(val.time.seconds * 1000 + val.time.milliseconds).toFixed(2)}ms 32 | - }) 33 | 34 | - routes.forEach(function(val, i){ 35 | tr 36 | td route ##{i} 37 | td: span #{val.action.method} #{val.action.route} 38 | td: mixin print_val(val.action.handler, 0) 39 | td.right #{(val.time.seconds * 1000 + val.time.milliseconds).toFixed(2)}ms 40 | - }) 41 | - if (render) 42 | tr 43 | td(colspan=3) render 44 | td.right #{(render.seconds * 1000 + render.milliseconds).toFixed(2)}ms 45 | 46 | h4 notes: 47 | p params and routes are executed inside the router middleware 48 | p shown times are based on process.hrtime() 49 | p to show a function's name, use a function name instead of an anonymous definition. ex: routes.users.login = function userLogin(req, res, next) { ... 50 | -------------------------------------------------------------------------------- /lib/panels/request/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var path = require('path'); 3 | 4 | var EXCLUDE = ['_readableState', 'socket', 'connection', 'client', 'res', 5 | 'session', 'sessionCookies', '_events', '_maxListeners', '_pendings', 6 | '_pendingIndex', '_consuming', '_dumped']; 7 | 8 | module.exports = { 9 | name: 'request', 10 | template: path.join(__dirname, 'template.jade'), 11 | 12 | process: function(params) { 13 | var req = params.req; 14 | 15 | var clean_req = {}; 16 | 17 | Object.keys(req).forEach(function(key) { 18 | if (EXCLUDE.indexOf(key) === -1 && typeof req[key] !== 'function') 19 | clean_req[key] = req[key]; 20 | }); 21 | 22 | return { 23 | locals: { 24 | req: clean_req 25 | } 26 | }; 27 | } 28 | }; -------------------------------------------------------------------------------- /lib/panels/request/template.jade: -------------------------------------------------------------------------------- 1 | h4 req 2 | mixin print_obj(req) 3 | -------------------------------------------------------------------------------- /lib/panels/session/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var path = require('path'), 3 | xtend = require('xtend'); 4 | 5 | module.exports = { 6 | name: 'session', 7 | template: path.join(__dirname, 'template.jade'), 8 | 9 | process: function(params) { 10 | 11 | var sess = params.req.session || {}, 12 | clean_sess = {}; 13 | 14 | if (sess._ctx && sess._ctx._readableState) { 15 | clean_sess = xtend(sess); 16 | delete clean_sess._ctx; 17 | } else { 18 | clean_sess = sess; 19 | } 20 | 21 | return { 22 | locals: { 23 | session: clean_sess 24 | } 25 | }; 26 | } 27 | }; -------------------------------------------------------------------------------- /lib/panels/session/template.jade: -------------------------------------------------------------------------------- 1 | h4 session 2 | mixin print_obj(session) 3 | -------------------------------------------------------------------------------- /lib/panels/software_info/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var path = require('path'), 3 | fs = require('fs'), 4 | 5 | version_info = {}; 6 | 7 | // get parent dir of express-debug (should be node_modules) 8 | var node_modules = path.resolve(path.join(__dirname, '..', '..', '..', '..')); 9 | 10 | fs.readdirSync(node_modules).forEach(function(dir) { 11 | try { 12 | var str = fs.readFileSync(path.join(node_modules, dir, 'package.json')), 13 | tmp = JSON.parse(str), 14 | info = { version: tmp.version }; 15 | 16 | // doing this to keep undefined keys off of the list 17 | if (tmp.author) { info.author = tmp.author; } 18 | if (tmp.dependencies) { info.dependencies = tmp.dependencies } 19 | if (tmp.devDependencies) { info.devDependencies = tmp.devDependencies } 20 | if (tmp.repository) { info.repository = tmp.repository } 21 | if (tmp.homepage) { info.homepage = tmp.homepage } 22 | if (tmp.bugs) { info.bugs = tmp.bugs } 23 | if (tmp.license) { info.license = tmp.license } 24 | if (tmp.licenses) { info.licenses = tmp.licenses } 25 | if (tmp.bin) { info.bin = tmp.bin } 26 | 27 | version_info[dir] = info; 28 | } catch(e) {} 29 | }); 30 | 31 | module.exports = { 32 | name: 'software info', 33 | template: path.join(__dirname, 'template.jade'), 34 | standalone: true, 35 | 36 | process: function() { 37 | return { 38 | locals: { 39 | version_info: version_info, 40 | node_info: process.versions 41 | } 42 | }; 43 | } 44 | }; -------------------------------------------------------------------------------- /lib/panels/software_info/template.jade: -------------------------------------------------------------------------------- 1 | 2 | 3 | h4 info 4 | mixin print_obj(version_info) 5 | 6 | h4 node 7 | mixin print_obj(node_info) -------------------------------------------------------------------------------- /lib/panels/template/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var path = require('path'), 3 | fs = require('fs'); 4 | 5 | var isAbsolute = function (path) { 6 | return ('/' === path[0]) || (':' === path[1] && '\\' === path[2]); 7 | }; 8 | 9 | // handle 'view engine' express directive 10 | var getPath = function (view, root, default_engine) { 11 | if (!isAbsolute(view)) { 12 | view = path.join(root, view); 13 | } 14 | 15 | var ext = path.extname(view); 16 | if (!ext) { 17 | view += (default_engine[0] !== '.' ? '.' : '') + default_engine; 18 | } 19 | return view; 20 | }; 21 | 22 | module.exports = { 23 | name: 'template', 24 | template: path.join(__dirname, 'template.jade'), 25 | 26 | process: function(params) { 27 | var view_template; 28 | 29 | var view_template_path = getPath( 30 | params.view, 31 | params.app.locals.settings.views, 32 | params.app.locals.settings['view engine'] || '' 33 | ); 34 | 35 | try { 36 | view_template = fs.readFileSync(view_template_path, 'utf-8'); 37 | } catch (e) { 38 | console.error('EDT: error loading ' + params.view + ' template: ', e); 39 | view_template = 'Could not load template.'; 40 | } 41 | 42 | return { 43 | locals: { 44 | template_name: params.view, 45 | template: view_template 46 | } 47 | }; 48 | } 49 | }; -------------------------------------------------------------------------------- /lib/panels/template/template.jade: -------------------------------------------------------------------------------- 1 | h4 Template 2 | span !{template_name} 3 | pre #{template} 4 | -------------------------------------------------------------------------------- /lib/request.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var panels = require('./panels'), 3 | requests = [], 4 | request = module.exports = {}; 5 | 6 | request.add = function(req, args) { 7 | panels.finalize(req); 8 | 9 | if (panels.use_requests) { 10 | var data = { 11 | body: Object.keys(req.body).length ? req.body : req.rawBody, 12 | query: Object.keys(req.query).length ? req.query : undefined, 13 | method: req.method, 14 | path: req.path, 15 | locals: req.res.locals, 16 | send_args: args, 17 | req_headers: req.headers, 18 | res_headers: req.res._headers, 19 | panels: req.EDT 20 | }; 21 | // break any references 22 | requests.push(JSON.parse(JSON.stringify(data))); 23 | data = null; 24 | } 25 | }; 26 | 27 | request.list = function(index) { 28 | index = index || 0; 29 | 30 | return requests.slice(index, requests.length); 31 | }; 32 | 33 | request.clear = function(index) { 34 | requests = requests.slice(index, requests.length) 35 | }; 36 | 37 | request.rawBody = function (req, res, next) { 38 | var data = ''; 39 | req.setEncoding('utf8'); 40 | req.on('data', function (chunk) { data += chunk; }); 41 | req.on('end', function () { req.rawBody = data || undefined; }); 42 | next(); 43 | }; 44 | -------------------------------------------------------------------------------- /lib/response.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var fs = require('fs'), 3 | path = require('path'), 4 | jade = require('jade'), 5 | 6 | utils = require('./utils'), 7 | panels = require('./panels'), 8 | request = require('./request'), 9 | 10 | response = module.exports = {}, 11 | 12 | template = path.join(__dirname, 'templates', 'toolbar.jade'), 13 | fullpage = path.join(__dirname, 'templates', 'page.jade'); 14 | 15 | 16 | 17 | response.init = function(app, settings) { 18 | var theme = null; 19 | 20 | // user-supplied css 21 | if (settings.theme) { 22 | try { 23 | theme = fs.readFileSync(settings.theme, 'utf-8'); 24 | } catch (e) { 25 | console.error('EDT: error loading css file at ' + settings.theme); 26 | console.error('please check that the path is correct. Err: ', e); 27 | } 28 | } 29 | 30 | // replaces res.render and injects express-debug toolbar 31 | var render = function (view, options, fn) { 32 | options = options || {}; 33 | 34 | var res = this, 35 | req = this.req, 36 | app = this.app, 37 | accept = req.headers.accept || ''; 38 | 39 | 40 | var finalize = function (err, str) { 41 | // keep existing callback if one was passed 42 | if (typeof fn === 'function') { 43 | fn(err, str); 44 | } else if (err) { 45 | req.next(err); 46 | } else { 47 | res.send(str); 48 | } 49 | }; 50 | 51 | panels.finalize(req); 52 | 53 | // support callback function as second arg 54 | if (typeof options === 'function') { 55 | fn = options; 56 | options = {}; 57 | } 58 | 59 | // merge res.locals 60 | options._locals = res.locals; 61 | 62 | var render_toolbar = function (str, callback) { 63 | var standalone = settings.path === req.path; 64 | var opts = { 65 | EDTsettings: settings, 66 | theme: theme, 67 | req: req, 68 | standalone: standalone, 69 | extra_attrs: settings.extra_attrs, 70 | panels: panels.render({ 71 | locals: options, 72 | app: app, 73 | res: res, 74 | req: req, 75 | view: view 76 | }, settings, standalone) 77 | }; 78 | 79 | jade.renderFile(template, opts, function (err, toolbar) { 80 | callback(err, err ? undefined : utils.inject_toolbar(str, toolbar)); 81 | }); 82 | }; 83 | 84 | var toolbar_callback = function (err, str) { 85 | panels.post_render(req); 86 | 87 | if (err) { 88 | console.log(err); 89 | req.next(err); 90 | 91 | // skip if this client req isn't expecting html or is ajax 92 | } else if (accept.indexOf('html') === -1 || req.xhr) { 93 | res.send(str); 94 | 95 | } else if (res.EDT_rendered) { 96 | // if callback method was used, more than one template may be rendered. 97 | // in this care, do not render another copy 98 | // TODO: see if we can catch this on the last render, and attach it in .send instead 99 | finalize(err, str); 100 | 101 | } else { 102 | res.EDT_rendered = true; 103 | render_toolbar(str, finalize); 104 | } 105 | }; 106 | 107 | panels.pre_render(req); 108 | if (req.path.indexOf(settings.path) === 0) { 109 | // standalone mode 110 | jade.renderFile(fullpage, function (err, str) { 111 | toolbar_callback(err, str); 112 | }); 113 | 114 | } else { 115 | // inject toolbar callback into render callback 116 | res._EDT_orig_render.call(res, view, options, toolbar_callback); 117 | } 118 | }; 119 | 120 | var send = function() { 121 | if (this.EDT_rendered !== true) { 122 | request.add(this.req, arguments); 123 | } 124 | this._EDT_orig_send.apply(this, arguments); 125 | }; 126 | 127 | response.patch = function(res) { 128 | res._EDT_orig_render = res.render; 129 | res.render = render; 130 | res._EDT_orig_send = res.send; 131 | res.send = send; 132 | } 133 | }; -------------------------------------------------------------------------------- /lib/templates/mixins.jade: -------------------------------------------------------------------------------- 1 | mixin print_val(val, depth, options) 2 | - if (val === undefined) 3 | span.undefined undefined 4 | - if (val === null) 5 | span.null null 6 | - else if (val instanceof Date) 7 | span.date #{val.toString()} 8 | - else if (typeof val === 'object') 9 | - if (Object.keys(val).length === 0) 10 | - if (val instanceof Array) 11 | span.array [ ] 12 | - else 13 | span.object { } 14 | - else 15 | mixin print_obj(val, depth + 1, options) 16 | - else if (typeof val === 'function') 17 | a.showFn args: #{val.length}, fn name: #{val.name} 18 | pre.fn !{val} 19 | - if (Object.keys(val).length) 20 | mixin print_obj(val, depth + 1, options) 21 | - else 22 | - _type = typeof val; 23 | span(class="#{_type}") #{val} 24 | 25 | mixin print_obj(obj, depth, options) 26 | - depth = depth || 0; 27 | - options = options || {}; 28 | - options.depth = options.depth || EDTsettings.depth; 29 | - options.sort = (options.sort === undefined) ? EDTsettings.sort : options.sort; 30 | - if (!obj.EDT_hidden) 31 | - if (depth < options.depth) 32 | - var keys = Object.keys(obj); 33 | - if (typeof options.sort === 'function') 34 | - keys = keys.sort(options.sort); 35 | - else if (options.sort) 36 | - keys = keys.sort(); 37 | table.object 38 | thead: tr.collapse 39 | - if (obj instanceof Array) 40 | th.arr index 41 | - else 42 | th name 43 | th value 44 | th.indicator 45 | tbody 46 | - keys.forEach(function(prop){ 47 | - var val = obj[prop] 48 | - if (!val || val.EDT_hidden === undefined) 49 | tr 50 | td !{prop} 51 | td(colspan=2): mixin print_val(val, depth, options) 52 | - }) 53 | - else 54 | span.exceeded (Depth exceeded; max depth: #{options.depth}) 55 | -------------------------------------------------------------------------------- /lib/templates/page.jade: -------------------------------------------------------------------------------- 1 | doctype html 2 | head 3 | meta(charset="utf-8") 4 | title express-debug 5 | meta(http-equiv="X-UA-Compatible", content="IE=edge") 6 | body 7 | -------------------------------------------------------------------------------- /lib/templates/toolbar.jade: -------------------------------------------------------------------------------- 1 | div
2 | include ../assets/style.css 3 | - if (theme) 4 | style(type='text/css')!=theme 5 | a(id="EDT-show", href="javascript:void(0);", title="Open express-debug toolbar") EDT 6 | #EDT 7 | - var tmp_p = '' 8 | div#EDT-menu 9 | span.text express-debug 10 | div.sep 11 | - if (req.path !== EDTsettings.path) 12 | span.text #{req.method} #{req.path} 13 | - else 14 | span.text Standalone Panel 15 | ul 16 | - panels.forEach(function(panel){ 17 | div.sep 18 | li: a(href='#EDT-' + panel.name, title=panel.name + ' panel')= panel.name 19 | - }) 20 | a(id="EDT-close", title="close express-debug toolbar", href="javascript:void(0);") CLOSE 21 | #EDT-main 22 | - edt_first = ' active' 23 | - panels.forEach(function(panel){ 24 | div(class='tab' + edt_first, id='EDT-' + panel.name)!= panel.html 25 | - if (edt_first) edt_first = '' 26 | - }) 27 | 28 | include ../assets/js.js 29 | -------------------------------------------------------------------------------- /lib/utils.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | 3 | var utils = module.exports = { 4 | // inject toolbar into html output in the semantically proper place 5 | inject_toolbar: function(str, toolbar) { 6 | var location = str.lastIndexOf('