|
28 | 28 | </div> |
29 | 29 | </cfif> |
30 | 30 |
|
31 | | - <!--- Source Code Context ---> |
| 31 | + <!--- Classify all stack frames ---> |
32 | 32 | <cfset local.path = GetDirectoryFromPath(GetBaseTemplatePath())> |
33 | | - <cfset local.errorPos = 0> |
34 | | - <cfloop array="#arguments.wheelsError.tagContext#" index="local.i"> |
35 | | - <cfset local.errorPos = local.errorPos + 1> |
36 | | - <cfif |
37 | | - local.i.template Does Not Contain local.path & "wheels" |
38 | | - AND local.i.template IS NOT local.path & "index.cfm" |
39 | | - AND IsDefined("application.wheels.rewriteFile") |
40 | | - AND local.i.template IS NOT local.path & application.wheels.rewriteFile |
41 | | - AND local.i.template IS NOT local.path & "Application.cfc" |
42 | | - AND local.i.template Does Not Contain local.path & "plugins" |
43 | | - > |
44 | | - <cfset local.lookupWorked = true> |
45 | | - <cftry> |
46 | | - <cfset local.errorLine = arguments.wheelsError.tagContext[local.errorPos].line> |
47 | | - <cfset local.errorFile = Replace(arguments.wheelsError.tagContext[local.errorPos].template, local.path, "")> |
48 | | - <cfsavecontent variable="local.fileContents"> |
49 | | - <cfset local.pos = 0> |
50 | | - <cfset local.startLine = Max(1, local.errorLine - 5)> |
51 | | - <cfset local.endLine = local.errorLine + 5> |
52 | | - <div style="background:##181825;border:1px solid ##45475a;border-radius:6px;overflow:hidden;margin-top:12px;"> |
53 | | - <div style="display:flex;align-items:center;justify-content:space-between;padding:8px 16px;background:##11111b;border-bottom:1px solid ##45475a;"> |
54 | | - <span style="font-family:monospace;font-size:12px;color:##a6adc8;">#EncodeForHTML(local.errorFile)#</span> |
55 | | - <span style="font-size:11px;color:##6c7086;">line #local.errorLine#</span> |
56 | | - </div> |
57 | | - <pre style="margin:0;padding:0;background:##181825 !important;border:none !important;"><code style="border:none !important;background:none !important;"><cfloop file="#arguments.wheelsError.tagContext[local.errorPos].template#" index="local.i"><cfset local.pos = local.pos + 1><cfif local.pos GTE local.startLine AND local.pos LTE local.endLine><cfif local.pos IS local.errorLine><span style="display:block;background:rgba(243,139,168,.12);border-left:3px solid ##f38ba8;padding:1px 12px 1px 9px;"><span style="display:inline-block;width:40px;color:##f38ba8;font-weight:700;text-align:right;margin-right:12px;user-select:none;">#local.pos#</span>#HtmlEditFormat(local.i)#</span><cfelse><span style="display:block;padding:1px 12px 1px 12px;"><span style="display:inline-block;width:40px;color:##6c7086;text-align:right;margin-right:12px;user-select:none;">#local.pos#</span>#HtmlEditFormat(local.i)#</span></cfif></cfif></cfloop></code></pre> |
| 33 | + <!--- Derive the app root (parent of public/) for reliable path display ---> |
| 34 | + <cfset local.appRoot = local.path> |
| 35 | + <cfif local.path Contains "/public/" OR local.path Contains "\public\"> |
| 36 | + <cfset local.appRoot = REReplaceNoCase(local.path, "[/\\]public[/\\]?$", "/")> |
| 37 | + </cfif> |
| 38 | + <cfset local.frames = []> |
| 39 | + <cfloop from="2" to="#ArrayLen(arguments.wheelsError.tagContext)#" index="local.i"> |
| 40 | + <cfset local.tpl = arguments.wheelsError.tagContext[local.i].template> |
| 41 | + <cfset local.frameFile = Replace(local.tpl, local.appRoot, "")> |
| 42 | + <!--- |
| 43 | + Classification uses path segments (not base path math) for reliability. |
| 44 | + Framework: vendor/wheels/, index.cfm & Application.cfc in public/ |
| 45 | + Plugin: plugins/ |
| 46 | + Library: any other vendor/ path (testbox, wirebox, coldbox, etc.) |
| 47 | + App: app/, config/, public/, tests/, and anything else |
| 48 | + ---> |
| 49 | + <cfset local.frameType = "app"> |
| 50 | + <cfif local.tpl Contains "vendor/wheels/" OR local.tpl Contains "vendor\wheels\"> |
| 51 | + <cfset local.frameType = "framework"> |
| 52 | + <cfelseif local.tpl Contains "/vendor/" OR local.tpl Contains "\vendor\"> |
| 53 | + <cfset local.frameType = "library"> |
| 54 | + <cfelseif local.tpl Contains "/plugins/" OR local.tpl Contains "\plugins\"> |
| 55 | + <cfset local.frameType = "plugin"> |
| 56 | + <cfelseif GetFileFromPath(local.tpl) IS "index.cfm" AND local.tpl Contains "public"> |
| 57 | + <cfset local.frameType = "framework"> |
| 58 | + <cfelseif GetFileFromPath(local.tpl) IS "Application.cfc" AND local.tpl Contains "public"> |
| 59 | + <cfset local.frameType = "framework"> |
| 60 | + </cfif> |
| 61 | + <cfset ArrayAppend(local.frames, { |
| 62 | + template = local.tpl, |
| 63 | + file = local.frameFile, |
| 64 | + line = arguments.wheelsError.tagContext[local.i].line, |
| 65 | + type = local.frameType |
| 66 | + })> |
| 67 | + </cfloop> |
| 68 | + |
| 69 | + <!--- Count frames by type ---> |
| 70 | + <cfset local.appCount = 0> |
| 71 | + <cfset local.frameworkCount = 0> |
| 72 | + <cfloop array="#local.frames#" index="local.f"> |
| 73 | + <cfif local.f.type IS "app"> |
| 74 | + <cfset local.appCount++> |
| 75 | + <cfelse> |
| 76 | + <cfset local.frameworkCount++> |
| 77 | + </cfif> |
| 78 | + </cfloop> |
| 79 | + |
| 80 | + <!--- Source Code Context: show first app frame, or first framework frame if no app code ---> |
| 81 | + <cfset local.contextFrame = ""> |
| 82 | + <cfloop array="#local.frames#" index="local.f"> |
| 83 | + <cfif local.f.type IS "app"> |
| 84 | + <cfset local.contextFrame = local.f> |
| 85 | + <cfbreak> |
| 86 | + </cfif> |
| 87 | + </cfloop> |
| 88 | + <cfif IsSimpleValue(local.contextFrame) AND ArrayLen(local.frames)> |
| 89 | + <cfset local.contextFrame = local.frames[1]> |
| 90 | + </cfif> |
| 91 | + <cfif NOT IsSimpleValue(local.contextFrame)> |
| 92 | + <cfset local.lookupWorked = true> |
| 93 | + <cftry> |
| 94 | + <cfset local.errorLine = local.contextFrame.line> |
| 95 | + <cfset local.errorFile = local.contextFrame.file> |
| 96 | + <cfset local.errorType = local.contextFrame.type> |
| 97 | + <cfsavecontent variable="local.fileContents"> |
| 98 | + <cfset local.pos = 0> |
| 99 | + <cfset local.startLine = Max(1, local.errorLine - 5)> |
| 100 | + <cfset local.endLine = local.errorLine + 5> |
| 101 | + <div style="background:##181825;border:1px solid ##45475a;border-radius:6px;overflow:hidden;margin-top:12px;"> |
| 102 | + <div style="display:flex;align-items:center;justify-content:space-between;padding:8px 16px;background:##11111b;border-bottom:1px solid ##45475a;"> |
| 103 | + <span style="font-family:monospace;font-size:12px;color:##a6adc8;">#EncodeForHTML(local.errorFile)#</span> |
| 104 | + <span style="font-size:11px;color:##6c7086;">line #local.errorLine#</span> |
58 | 105 | </div> |
59 | | - </cfsavecontent> |
60 | | - <cfcatch> |
61 | | - <cfset local.lookupWorked = false> |
62 | | - </cfcatch> |
63 | | - </cftry> |
64 | | - <cfif local.lookupWorked> |
65 | | - <div style="margin-top:1.5em;"> |
66 | | - <div style="font-size:11px;font-weight:700;color:##a6adc8;text-transform:uppercase;letter-spacing:.5px;margin-bottom:4px;">Error Location</div> |
67 | | - <div style="font-size:13px;color:##cdd6f4;"> |
68 | | - Line <strong style="color:##f38ba8;">#local.errorLine#</strong> in <code style="background:##313244;color:##94e2d5;padding:2px 6px;border-radius:3px;font-size:.85em;">#EncodeForHTML(local.errorFile)#</code> |
| 106 | + <pre style="margin:0;padding:0;background:##181825 !important;border:none !important;"><code style="border:none !important;background:none !important;"><cfloop file="#local.contextFrame.template#" index="local.ln"><cfset local.pos = local.pos + 1><cfif local.pos GTE local.startLine AND local.pos LTE local.endLine><cfif local.pos IS local.errorLine><span style="display:block;background:rgba(243,139,168,.12);border-left:3px solid ##f38ba8;padding:1px 12px 1px 9px;"><span style="display:inline-block;width:40px;color:##f38ba8;font-weight:700;text-align:right;margin-right:12px;user-select:none;">#local.pos#</span>#HtmlEditFormat(local.ln)#</span><cfelse><span style="display:block;padding:1px 12px 1px 12px;"><span style="display:inline-block;width:40px;color:##6c7086;text-align:right;margin-right:12px;user-select:none;">#local.pos#</span>#HtmlEditFormat(local.ln)#</span></cfif></cfif></cfloop></code></pre> |
| 107 | + </div> |
| 108 | + </cfsavecontent> |
| 109 | + <cfcatch> |
| 110 | + <cfset local.lookupWorked = false> |
| 111 | + </cfcatch> |
| 112 | + </cftry> |
| 113 | + <cfif local.lookupWorked> |
| 114 | + <div style="margin-top:1.5em;"> |
| 115 | + <div style="display:flex;align-items:center;gap:8px;margin-bottom:4px;"> |
| 116 | + <span style="font-size:11px;font-weight:700;color:##a6adc8;text-transform:uppercase;letter-spacing:.5px;">Error Location</span> |
| 117 | + <cfif local.errorType IS NOT "app"> |
| 118 | + <span style="font-size:10px;padding:2px 8px;border-radius:3px;background:rgba(249,226,175,.12);color:##f9e2af;font-weight:600;">Framework Code</span> |
| 119 | + </cfif> |
| 120 | + </div> |
| 121 | + <cfif local.errorType IS NOT "app"> |
| 122 | + <div style="font-size:12px;color:##f9e2af;margin-bottom:8px;line-height:1.5;"> |
| 123 | + No application code found in the stack trace. The error originated in the framework, likely triggered by a missing file, route, or configuration in your app. |
69 | 124 | </div> |
70 | | - #local.fileContents# |
| 125 | + </cfif> |
| 126 | + <div style="font-size:13px;color:##cdd6f4;"> |
| 127 | + Line <strong style="color:##f38ba8;">#local.errorLine#</strong> in <code style="background:##313244;color:##94e2d5;padding:2px 6px;border-radius:3px;font-size:.85em;">#EncodeForHTML(local.errorFile)#</code> |
71 | 128 | </div> |
72 | | - </cfif> |
73 | | - <cfbreak> |
| 129 | + #local.fileContents# |
| 130 | + </div> |
74 | 131 | </cfif> |
75 | | - </cfloop> |
| 132 | + </cfif> |
76 | 133 |
|
77 | | - <!--- Stack Trace (Collapsible) ---> |
78 | | - <cfif ArrayLen(arguments.wheelsError.tagContext) GTE 2> |
| 134 | + <!--- Stack Trace with App/Framework Toggle ---> |
| 135 | + <cfif ArrayLen(local.frames)> |
79 | 136 | <div style="margin-top:1.5em;"> |
80 | | - <div onclick="var el=document.getElementById('wheels-stacktrace');el.style.display=el.style.display==='none'?'block':'none';this.querySelector('svg').style.transform=el.style.display==='none'?'':'rotate(90deg)';" style="cursor:pointer;display:flex;align-items:center;gap:6px;font-size:11px;font-weight:700;color:##a6adc8;text-transform:uppercase;letter-spacing:.5px;margin-bottom:8px;user-select:none;"> |
81 | | - <svg style="width:10px;height:10px;fill:##a6adc8;transition:transform .15s;" viewBox="0 0 320 512"><path d="M310.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-192 192c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L242.7 256 73.4 86.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l192 192z"/></svg> |
82 | | - Stack Trace (#ArrayLen(arguments.wheelsError.tagContext) - 1# frames) |
| 137 | + <!--- Header with toggle buttons ---> |
| 138 | + <div style="display:flex;align-items:center;justify-content:space-between;margin-bottom:8px;"> |
| 139 | + <div onclick="var el=document.getElementById('wheels-stacktrace');el.style.display=el.style.display==='none'?'block':'none';this.querySelector('svg').style.transform=el.style.display==='none'?'':'rotate(90deg)';" style="cursor:pointer;display:flex;align-items:center;gap:6px;font-size:11px;font-weight:700;color:##a6adc8;text-transform:uppercase;letter-spacing:.5px;user-select:none;"> |
| 140 | + <svg style="width:10px;height:10px;fill:##a6adc8;transition:transform .15s;" viewBox="0 0 320 512"><path d="M310.6 233.4c12.5 12.5 12.5 32.8 0 45.3l-192 192c-12.5 12.5-32.8 12.5-45.3 0s-12.5-32.8 0-45.3L242.7 256 73.4 86.6c-12.5-12.5-12.5-32.8 0-45.3s32.8-12.5 45.3 0l192 192z"/></svg> |
| 141 | + Stack Trace (#ArrayLen(local.frames)# frames) |
| 142 | + </div> |
| 143 | + <div id="wheels-trace-filters" style="display:none;gap:4px;"> |
| 144 | + <cfif local.appCount GT 0> |
| 145 | + <button onclick="wheelsFilterTrace('app')" id="wheels-filter-app" style="font-size:11px;padding:3px 10px;border-radius:4px;border:1px solid ##89b4fa;background:rgba(137,180,250,.15);color:##89b4fa;cursor:pointer;font-weight:600;">App #local.appCount#</button> |
| 146 | + </cfif> |
| 147 | + <button onclick="wheelsFilterTrace('framework')" id="wheels-filter-framework" style="font-size:11px;padding:3px 10px;border-radius:4px;border:1px solid ##45475a;background:transparent;color:##6c7086;cursor:pointer;font-weight:600;">Framework #local.frameworkCount#</button> |
| 148 | + <button onclick="wheelsFilterTrace('all')" id="wheels-filter-all" style="font-size:11px;padding:3px 10px;border-radius:4px;border:1px solid ##45475a;background:transparent;color:##6c7086;cursor:pointer;font-weight:600;">All #ArrayLen(local.frames)#</button> |
| 149 | + </div> |
83 | 150 | </div> |
84 | 151 | <div id="wheels-stacktrace" style="display:none;"> |
85 | 152 | <div style="background:##181825;border:1px solid ##45475a;border-radius:6px;overflow:hidden;"> |
86 | 153 | <cfset local.frameNum = 0> |
87 | | - <!--- skip the first item in the array as this is always the Throw() method ---> |
88 | | - <cfloop from="2" to="#ArrayLen(arguments.wheelsError.tagContext)#" index="local.i"> |
89 | | - <cfset local.frameNum = local.frameNum + 1> |
90 | | - <cfset local.frameFile = Replace(arguments.wheelsError.tagContext[local.i].template, local.path, "")> |
91 | | - <div style="display:flex;align-items:center;padding:8px 16px;border-bottom:1px solid ##313244;font-size:12px;gap:10px;<cfif local.i EQ 2>background:rgba(137,180,250,.05);</cfif>"> |
| 154 | + <cfloop array="#local.frames#" index="local.f"> |
| 155 | + <cfset local.frameNum++> |
| 156 | + <cfset local.isApp = local.f.type IS "app"> |
| 157 | + <div data-frame-type="#local.f.type#" style="display:flex;align-items:center;padding:8px 16px;border-bottom:1px solid ##313244;font-size:12px;gap:10px;<cfif NOT local.isApp>opacity:.45;</cfif>"> |
92 | 158 | <span style="color:##6c7086;font-weight:600;min-width:24px;">###local.frameNum#</span> |
93 | | - <span style="font-family:monospace;color:##cdd6f4;flex:1;">#EncodeForHTML(local.frameFile)#</span> |
94 | | - <span style="color:##f9e2af;font-family:monospace;font-size:11px;">line #arguments.wheelsError.tagContext[local.i].line#</span> |
| 159 | + <cfif local.isApp> |
| 160 | + <span style="font-size:9px;padding:1px 6px;border-radius:3px;background:rgba(166,227,161,.12);color:##a6e3a1;font-weight:700;min-width:32px;text-align:center;">APP</span> |
| 161 | + <cfelse> |
| 162 | + <span style="font-size:9px;padding:1px 6px;border-radius:3px;background:rgba(108,112,134,.15);color:##6c7086;font-weight:700;min-width:32px;text-align:center;">#UCase(local.f.type)#</span> |
| 163 | + </cfif> |
| 164 | + <span style="font-family:monospace;color:<cfif local.isApp>##cdd6f4<cfelse>##6c7086</cfif>;flex:1;">#EncodeForHTML(local.f.file)#</span> |
| 165 | + <span style="color:<cfif local.isApp>##f9e2af<cfelse>##45475a</cfif>;font-family:monospace;font-size:11px;">line #local.f.line#</span> |
95 | 166 | </div> |
96 | 167 | </cfloop> |
97 | 168 | </div> |
98 | 169 | </div> |
99 | 170 | </div> |
100 | 171 | </cfif> |
101 | 172 | </div> |
| 173 | + </cfoutput> |
| 174 | + <script> |
| 175 | + (function(){ |
| 176 | + // Show filter buttons when trace is expanded |
| 177 | + var traceEl = document.getElementById('wheels-stacktrace'); |
| 178 | + var filtersEl = document.getElementById('wheels-trace-filters'); |
| 179 | + var observer = new MutationObserver(function(){ |
| 180 | + filtersEl.style.display = traceEl.style.display === 'none' ? 'none' : 'flex'; |
| 181 | + }); |
| 182 | + observer.observe(traceEl, {attributes: true, attributeFilter: ['style']}); |
| 183 | +
|
| 184 | + // Default to app filter if app frames exist |
| 185 | + var hasApp = document.querySelector('[data-frame-type="app"]'); |
| 186 | + if (hasApp) { |
| 187 | + wheelsFilterTrace('app'); |
| 188 | + } |
| 189 | + })(); |
| 190 | +
|
| 191 | + function wheelsFilterTrace(filter) { |
| 192 | + var frames = document.querySelectorAll('[data-frame-type]'); |
| 193 | + var buttons = { |
| 194 | + app: document.getElementById('wheels-filter-app'), |
| 195 | + framework: document.getElementById('wheels-filter-framework'), |
| 196 | + all: document.getElementById('wheels-filter-all') |
| 197 | + }; |
| 198 | + // Reset button styles |
| 199 | + for (var key in buttons) { |
| 200 | + if (buttons[key]) { |
| 201 | + buttons[key].style.background = 'transparent'; |
| 202 | + buttons[key].style.borderColor = '#45475a'; |
| 203 | + buttons[key].style.color = '#6c7086'; |
| 204 | + } |
| 205 | + } |
| 206 | + // Highlight active button |
| 207 | + var active = buttons[filter]; |
| 208 | + if (active) { |
| 209 | + active.style.borderColor = '#89b4fa'; |
| 210 | + active.style.background = 'rgba(137,180,250,.15)'; |
| 211 | + active.style.color = '#89b4fa'; |
| 212 | + } |
| 213 | + // Filter frames |
| 214 | + for (var i = 0; i < frames.length; i++) { |
| 215 | + var type = frames[i].getAttribute('data-frame-type'); |
| 216 | + if (filter === 'all') { |
| 217 | + frames[i].style.display = 'flex'; |
| 218 | + frames[i].style.opacity = type === 'app' ? '1' : '.45'; |
| 219 | + } else if (filter === 'app') { |
| 220 | + frames[i].style.display = type === 'app' ? 'flex' : 'none'; |
| 221 | + frames[i].style.opacity = '1'; |
| 222 | + } else { |
| 223 | + frames[i].style.display = type !== 'app' ? 'flex' : 'none'; |
| 224 | + frames[i].style.opacity = '1'; |
| 225 | + } |
| 226 | + } |
| 227 | + } |
| 228 | + </script> |
| 229 | + <cfoutput> |
102 | 230 | <!--- cfformat-ignore-end ---> |
103 | 231 | </cfoutput> |
0 commit comments