@stacksjs/error-handling 0.74.25 → 0.74.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/views/500.html +38 -0
- package/dist/views/errors/layout.stx +38 -0
- package/dist/views/errors/partials/environment.stx +12 -0
- package/dist/views/errors/partials/frame.stx +42 -0
- package/dist/views/errors/partials/header.stx +56 -0
- package/dist/views/errors/partials/query.stx +40 -0
- package/dist/views/errors/partials/request-body.stx +21 -0
- package/dist/views/errors/partials/request-header.stx +12 -0
- package/dist/views/errors/partials/request-url.stx +43 -0
- package/dist/views/errors/partials/routing-parameters.stx +21 -0
- package/dist/views/errors/partials/routing.stx +12 -0
- package/dist/views/errors/partials/scripts.stx +75 -0
- package/dist/views/errors/partials/separator.stx +11 -0
- package/dist/views/errors/partials/topbar.stx +39 -0
- package/dist/views/errors/partials/trace.stx +20 -0
- package/dist/views/errors/partials/user.stx +12 -0
- package/dist/views/errors/partials/vendor-frames.stx +31 -0
- package/dist/views/errors/show.stx +61 -0
- package/package.json +6 -6
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Server Error</title>
|
|
7
|
+
<style>
|
|
8
|
+
body {
|
|
9
|
+
font-family: Arial, sans-serif;
|
|
10
|
+
background-color: #f4f4f4;
|
|
11
|
+
margin: 0;
|
|
12
|
+
padding: 20px;
|
|
13
|
+
}
|
|
14
|
+
.error-container {
|
|
15
|
+
background-color: #ffefef;
|
|
16
|
+
border: 1px solid #ff4d4d;
|
|
17
|
+
color: #d8000c;
|
|
18
|
+
padding: 20px;
|
|
19
|
+
border-radius: 5px;
|
|
20
|
+
}
|
|
21
|
+
.error-title {
|
|
22
|
+
font-size: 24px;
|
|
23
|
+
font-weight: bold;
|
|
24
|
+
margin-bottom: 10px;
|
|
25
|
+
}
|
|
26
|
+
.error-message {
|
|
27
|
+
font-size: 18px;
|
|
28
|
+
}
|
|
29
|
+
</style>
|
|
30
|
+
</head>
|
|
31
|
+
<body>
|
|
32
|
+
<div class="error-container">
|
|
33
|
+
<div class="error-title">An Error Occurred</div>
|
|
34
|
+
<p class="error-message">{{ERROR_MESSAGE}}</p>
|
|
35
|
+
<p class="error-message">{{STACK_TRACE}}</p>
|
|
36
|
+
</div>
|
|
37
|
+
</body>
|
|
38
|
+
</html>
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
<script server>
|
|
2
|
+
/*
|
|
3
|
+
* The context `renderErrorPage` spreads into this template, declared so the
|
|
4
|
+
* checker holds it to the same shape the renderer builds.
|
|
5
|
+
*
|
|
6
|
+
* Every name below is typed by indexing `ErrorPageViewModel` rather than
|
|
7
|
+
* restating it, so renaming a field there is a build error here instead of
|
|
8
|
+
* an `undefined` rendered onto the error page - the one page where a silent
|
|
9
|
+
* failure is most expensive, because you are already debugging something
|
|
10
|
+
* else when you read it.
|
|
11
|
+
*
|
|
12
|
+
* See `storage/framework/core/error-handling/src/error-page-view-model.ts`.
|
|
13
|
+
*/
|
|
14
|
+
import type { ErrorPageViewModel } from '../../error-page-view-model'
|
|
15
|
+
|
|
16
|
+
/** The rendered body of the page this layout wraps. */
|
|
17
|
+
declare const content: string
|
|
18
|
+
|
|
19
|
+
declare const pageTitle: ErrorPageViewModel['pageTitle']
|
|
20
|
+
declare const highlightCss: ErrorPageViewModel['highlightCss']
|
|
21
|
+
</script>
|
|
22
|
+
<!DOCTYPE html>
|
|
23
|
+
<html lang="en">
|
|
24
|
+
<head>
|
|
25
|
+
<meta charset="utf-8" />
|
|
26
|
+
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
|
|
27
|
+
<title>{{ pageTitle }}</title>
|
|
28
|
+
@if(highlightCss)
|
|
29
|
+
<style>{!! highlightCss !!}</style>
|
|
30
|
+
@endif
|
|
31
|
+
</head>
|
|
32
|
+
<body class="overflow-x-hidden antialiased font-sans dark:text-white bg-neutral-50 dark:bg-neutral-900 scheme-light-dark">
|
|
33
|
+
<div class="min-h-dvh">
|
|
34
|
+
{!! content !!}
|
|
35
|
+
</div>
|
|
36
|
+
@include('./partials/scripts')
|
|
37
|
+
</body>
|
|
38
|
+
</html>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<div class="flex flex-col gap-3">
|
|
2
|
+
<h2 class="font-semibold text-lg text-neutral-900 dark:text-white">Environment</h2>
|
|
3
|
+
<div class="flex flex-col">
|
|
4
|
+
@foreach(environmentRows as row)
|
|
5
|
+
<div class="flex gap-2 items-baseline h-10 max-w-full font-mono text-sm">
|
|
6
|
+
<div class="shrink-0 text-neutral-500 uppercase dark:text-neutral-400">{{ row.key }}</div>
|
|
7
|
+
<div class="grow h-3 min-w-6 border-b-2 border-dotted border-neutral-300 dark:border-white/20"></div>
|
|
8
|
+
<div class="text-neutral-900 truncate dark:text-white">{{ row.value }}</div>
|
|
9
|
+
</div>
|
|
10
|
+
@endforeach
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
<script server>
|
|
2
|
+
/*
|
|
3
|
+
* Included as `@include('./frame', { frame: frame })` from both loops in
|
|
4
|
+
* `trace.stx` and from `vendor-frames.stx`, so `frame` is an include prop.
|
|
5
|
+
*/
|
|
6
|
+
import type { FrameViewModel } from '../../../error-page-view-model'
|
|
7
|
+
|
|
8
|
+
declare const frame: FrameViewModel
|
|
9
|
+
</script>
|
|
10
|
+
<div
|
|
11
|
+
class="group rounded-lg border border-neutral-200 dark:border-white/10 overflow-hidden shadow-xs trace-frame {{ frame.expanded ? 'is-expanded' : '' }}"
|
|
12
|
+
data-trace-frame
|
|
13
|
+
data-has-code="{{ frame.hasCode ? '1' : '0' }}"
|
|
14
|
+
>
|
|
15
|
+
<div
|
|
16
|
+
class="flex h-11 items-center gap-3 bg-white pr-2.5 pl-4 overflow-x-auto dark:bg-white/3 trace-frame-header {{ frame.expanded ? 'dark:bg-white/5 rounded-t-lg' : 'rounded-lg' }} {{ frame.hasCode ? 'cursor-pointer hover:bg-white/50 dark:hover:bg-white/5' : '' }}"
|
|
17
|
+
data-trace-toggle
|
|
18
|
+
>
|
|
19
|
+
<div class="flex flex-shrink-0 items-center justify-center size-3">
|
|
20
|
+
<div class="size-2 rounded-full {{ frame.expanded ? 'bg-rose-500 dark:bg-neutral-400' : 'bg-rose-200 dark:bg-neutral-700' }}"></div>
|
|
21
|
+
</div>
|
|
22
|
+
|
|
23
|
+
<div class="flex flex-1 gap-6 items-center justify-between min-w-0">
|
|
24
|
+
<div class="font-mono text-neutral-900 text-xs truncate dark:text-neutral-200">{{ frame.function }}</div>
|
|
25
|
+
<div class="flex-shrink-0 font-mono text-neutral-500 text-xs dark:text-neutral-400">{{ frame.file }}:{{ frame.line }}</div>
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
@if(frame.hasCode)
|
|
29
|
+
<button type="button" class="flex flex-shrink-0 items-center justify-center h-6 w-6 text-neutral-500 dark:text-neutral-500 dark:bg-white/3 rounded-md dark:border dark:border-white/8 trace-chevron" data-trace-toggle>
|
|
30
|
+
<svg class="h-3 w-3" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m7 15 5 5 5-5"/><path d="m7 9 5-5 5 5"/></svg>
|
|
31
|
+
</button>
|
|
32
|
+
@endif
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
@if(frame.hasCode)
|
|
36
|
+
<div class="trace-frame-body {{ frame.expanded ? '' : 'hidden' }}">
|
|
37
|
+
<div class="overflow-x-auto text-sm bg-neutral-50 dark:bg-neutral-900 border-neutral-100 border-t rounded-b-lg dark:border-white/10">
|
|
38
|
+
{!! frame.codeHtml !!}
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
41
|
+
@endif
|
|
42
|
+
</div>
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
<script server>
|
|
2
|
+
/*
|
|
3
|
+
* The context `renderErrorPage` spreads into this template, declared so the
|
|
4
|
+
* checker holds it to the same shape the renderer builds.
|
|
5
|
+
*
|
|
6
|
+
* Every name below is typed by indexing `ErrorPageViewModel` rather than
|
|
7
|
+
* restating it, so renaming a field there is a build error here instead of
|
|
8
|
+
* an `undefined` rendered onto the error page - the one page where a silent
|
|
9
|
+
* failure is most expensive, because you are already debugging something
|
|
10
|
+
* else when you read it.
|
|
11
|
+
*
|
|
12
|
+
* See `storage/framework/core/error-handling/src/error-page-view-model.ts`.
|
|
13
|
+
*/
|
|
14
|
+
import type { ErrorPageViewModel } from '../../../error-page-view-model'
|
|
15
|
+
|
|
16
|
+
declare const exceptionClass: ErrorPageViewModel['exceptionClass']
|
|
17
|
+
declare const fileLine: ErrorPageViewModel['fileLine']
|
|
18
|
+
declare const message: ErrorPageViewModel['message']
|
|
19
|
+
declare const frameworkLabel: ErrorPageViewModel['frameworkLabel']
|
|
20
|
+
declare const frameworkVersion: ErrorPageViewModel['frameworkVersion']
|
|
21
|
+
declare const runtimeLabel: ErrorPageViewModel['runtimeLabel']
|
|
22
|
+
declare const runtimeVersion: ErrorPageViewModel['runtimeVersion']
|
|
23
|
+
declare const errorCode: ErrorPageViewModel['errorCode']
|
|
24
|
+
</script>
|
|
25
|
+
<div class="flex overflow-x-auto flex-col pt-8 sm:pt-16">
|
|
26
|
+
<div class="flex flex-col gap-5 mb-8">
|
|
27
|
+
<h1 class="font-semibold text-3xl text-neutral-950 dark:text-white">{{ exceptionClass }}</h1>
|
|
28
|
+
@if(fileLine)
|
|
29
|
+
<div class="-mt-3 font-mono text-rose-600 text-xs dark:text-rose-500">{{ fileLine }}</div>
|
|
30
|
+
@endif
|
|
31
|
+
<p class="font-light text-neutral-800 text-xl dark:text-neutral-300">{{ message }}</p>
|
|
32
|
+
</div>
|
|
33
|
+
|
|
34
|
+
<div class="flex flex-wrap gap-2 items-start mb-8 sm:mb-16">
|
|
35
|
+
<div class="flex gap-0.5 items-center bg-white dark:bg-white/[3%] border border-neutral-200 divide-neutral-200 divide-x rounded-md dark:border-white/10 dark:divide-white/10 shadow-xs">
|
|
36
|
+
<div class="flex gap-1.5 items-center px-[6px] h-6 font-mono text-[13px]">
|
|
37
|
+
<span class="text-neutral-400 dark:text-neutral-500">{{ frameworkLabel }}</span>
|
|
38
|
+
<span class="text-neutral-500 dark:text-neutral-300">{{ frameworkVersion }}</span>
|
|
39
|
+
</div>
|
|
40
|
+
<div class="flex gap-1.5 items-center px-[6px] h-6 font-mono text-[13px]">
|
|
41
|
+
<span class="text-neutral-400 dark:text-neutral-500">{{ runtimeLabel }}</span>
|
|
42
|
+
<span class="text-neutral-500 dark:text-neutral-300">{{ runtimeVersion }}</span>
|
|
43
|
+
</div>
|
|
44
|
+
</div>
|
|
45
|
+
<div class="inline-flex gap-1 items-center justify-center shrink-0 px-1.5 h-6 min-w-5 w-fit font-mono leading-3 text-rose-900 text-xs/none uppercase dark:text-rose-100 bg-rose-200 dark:bg-rose-950 rounded-md dark:border dark:border-rose-900">
|
|
46
|
+
UNHANDLED
|
|
47
|
+
</div>
|
|
48
|
+
<div class="inline-flex gap-1 items-center justify-center shrink-0 px-1.5 h-6 min-w-5 w-fit font-mono leading-3 text-white text-xs/none uppercase dark:text-white bg-rose-600 dark:bg-rose-600 rounded-md dark:border dark:border-rose-500">
|
|
49
|
+
CODE {{ errorCode }}
|
|
50
|
+
</div>
|
|
51
|
+
</div>
|
|
52
|
+
|
|
53
|
+
@if(hasRequest)
|
|
54
|
+
@include('./request-url')
|
|
55
|
+
@endif
|
|
56
|
+
</div>
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<script server>
|
|
2
|
+
/*
|
|
3
|
+
* The context `renderErrorPage` spreads into this template, declared so the
|
|
4
|
+
* checker holds it to the same shape the renderer builds.
|
|
5
|
+
*
|
|
6
|
+
* Every name below is typed by indexing `ErrorPageViewModel` rather than
|
|
7
|
+
* restating it, so renaming a field there is a build error here instead of
|
|
8
|
+
* an `undefined` rendered onto the error page - the one page where a silent
|
|
9
|
+
* failure is most expensive, because you are already debugging something
|
|
10
|
+
* else when you read it.
|
|
11
|
+
*
|
|
12
|
+
* See `storage/framework/core/error-handling/src/error-page-view-model.ts`.
|
|
13
|
+
*/
|
|
14
|
+
import type { ErrorPageViewModel } from '../../../error-page-view-model'
|
|
15
|
+
|
|
16
|
+
declare const queryCount: ErrorPageViewModel['queryCount']
|
|
17
|
+
</script>
|
|
18
|
+
<div class="flex flex-col gap-2.5 p-2.5 bg-neutral-50 dark:bg-white/1 border border-neutral-200 rounded-xl dark:border-neutral-800 shadow-xs" data-query-panel>
|
|
19
|
+
<div class="flex items-center justify-between p-2">
|
|
20
|
+
<div class="flex gap-2.5 items-center">
|
|
21
|
+
<div class="flex items-center justify-center p-1 h-6 w-6 bg-white dark:bg-neutral-800 border border-neutral-200 rounded-md dark:border-white/5">
|
|
22
|
+
<svg class="h-2.5 w-2.5 text-blue-500 dark:text-emerald-500" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5v14c0 1.66 4.03 3 9 3s9-1.34 9-3V5"/><path d="M3 12c0 1.66 4.03 3 9 3s9-1.34 9-3"/></svg>
|
|
23
|
+
</div>
|
|
24
|
+
<h3 class="font-semibold text-base text-neutral-900 dark:text-white">Queries</h3>
|
|
25
|
+
</div>
|
|
26
|
+
<div class="text-neutral-500 text-sm dark:text-neutral-400">{{ queryCount }} total</div>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
<div class="flex flex-col gap-1">
|
|
30
|
+
@foreach(queries as query)
|
|
31
|
+
<div class="flex gap-4 items-center justify-between px-4 py-2 min-h-10 font-mono text-xs bg-white dark:bg-white/[3%] border border-neutral-200 rounded-md dark:border-none shadow-xs">
|
|
32
|
+
<div class="flex gap-2 items-center min-w-0 truncate">
|
|
33
|
+
<span class="shrink-0 text-neutral-500 dark:text-neutral-400">{{ query.connection }}</span>
|
|
34
|
+
<span class="text-neutral-900 truncate dark:text-neutral-200">{{ query.sql }}</span>
|
|
35
|
+
</div>
|
|
36
|
+
<div class="flex-shrink-0 text-neutral-500 text-right dark:text-neutral-200">{{ query.time }}ms</div>
|
|
37
|
+
</div>
|
|
38
|
+
@endforeach
|
|
39
|
+
</div>
|
|
40
|
+
</div>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<script server>
|
|
2
|
+
/*
|
|
3
|
+
* The context `renderErrorPage` spreads into this template, declared so the
|
|
4
|
+
* checker holds it to the same shape the renderer builds.
|
|
5
|
+
*
|
|
6
|
+
* Every name below is typed by indexing `ErrorPageViewModel` rather than
|
|
7
|
+
* restating it, so renaming a field there is a build error here instead of
|
|
8
|
+
* an `undefined` rendered onto the error page - the one page where a silent
|
|
9
|
+
* failure is most expensive, because you are already debugging something
|
|
10
|
+
* else when you read it.
|
|
11
|
+
*
|
|
12
|
+
* See `storage/framework/core/error-handling/src/error-page-view-model.ts`.
|
|
13
|
+
*/
|
|
14
|
+
import type { ErrorPageViewModel } from '../../../error-page-view-model'
|
|
15
|
+
|
|
16
|
+
declare const bodyContent: ErrorPageViewModel['bodyContent']
|
|
17
|
+
</script>
|
|
18
|
+
<div class="flex flex-col gap-3">
|
|
19
|
+
<h2 class="font-semibold text-lg text-neutral-900 dark:text-white">Body</h2>
|
|
20
|
+
<pre class="overflow-x-auto p-4 font-mono text-neutral-800 text-sm dark:text-neutral-200 bg-neutral-100 dark:bg-white/5 rounded-lg">{{ bodyContent }}</pre>
|
|
21
|
+
</div>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<div class="flex flex-col gap-3">
|
|
2
|
+
<h2 class="font-semibold text-lg text-neutral-900 dark:text-white">Headers</h2>
|
|
3
|
+
<div class="flex flex-col">
|
|
4
|
+
@foreach(headers as row)
|
|
5
|
+
<div class="flex gap-2 items-baseline h-10 max-w-full font-mono text-sm">
|
|
6
|
+
<div class="shrink-0 text-neutral-500 uppercase dark:text-neutral-400">{{ row.key }}</div>
|
|
7
|
+
<div class="grow h-3 min-w-6 border-b-2 border-dotted border-neutral-300 dark:border-white/20"></div>
|
|
8
|
+
<div class="text-neutral-900 truncate dark:text-white" title="{{ row.value }}">{{ row.value }}</div>
|
|
9
|
+
</div>
|
|
10
|
+
@endforeach
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
<script server>
|
|
2
|
+
/*
|
|
3
|
+
* The context `renderErrorPage` spreads into this template, declared so the
|
|
4
|
+
* checker holds it to the same shape the renderer builds.
|
|
5
|
+
*
|
|
6
|
+
* Every name below is typed by indexing `ErrorPageViewModel` rather than
|
|
7
|
+
* restating it, so renaming a field there is a build error here instead of
|
|
8
|
+
* an `undefined` rendered onto the error page - the one page where a silent
|
|
9
|
+
* failure is most expensive, because you are already debugging something
|
|
10
|
+
* else when you read it.
|
|
11
|
+
*
|
|
12
|
+
* See `storage/framework/core/error-handling/src/error-page-view-model.ts`.
|
|
13
|
+
*/
|
|
14
|
+
import type { ErrorPageViewModel } from '../../../error-page-view-model'
|
|
15
|
+
|
|
16
|
+
declare const requestMethod: ErrorPageViewModel['requestMethod']
|
|
17
|
+
declare const requestUrl: ErrorPageViewModel['requestUrl']
|
|
18
|
+
declare const statusCode: ErrorPageViewModel['statusCode']
|
|
19
|
+
</script>
|
|
20
|
+
<div
|
|
21
|
+
data-copy-url-root
|
|
22
|
+
class="flex relative z-50 items-center justify-between px-2 h-10 bg-white dark:bg-[#1a1a1a] border border-neutral-200 rounded-lg dark:border-white/10 shadow-xs"
|
|
23
|
+
>
|
|
24
|
+
<div class="flex gap-3 items-center min-w-0 w-full">
|
|
25
|
+
<div class="inline-flex gap-1 items-center justify-center shrink-0 px-1.5 h-6 min-w-5 w-fit font-mono leading-3 text-white text-xs/none uppercase dark:text-white bg-rose-600 dark:bg-rose-600 rounded-md dark:border dark:border-rose-500">
|
|
26
|
+
{{ statusCode }}
|
|
27
|
+
</div>
|
|
28
|
+
<div class="inline-flex gap-1 items-center justify-center shrink-0 px-1.5 h-6 min-w-5 w-fit font-mono leading-3 text-neutral-900 text-xs/none uppercase dark:text-neutral-100 bg-black/8 dark:bg-white/10 rounded-md dark:border dark:border-neutral-700">
|
|
29
|
+
{{ requestMethod }}
|
|
30
|
+
</div>
|
|
31
|
+
<div class="flex-1 min-w-0 font-light text-neutral-950 text-sm truncate dark:text-white" title="{{ requestUrl }}">
|
|
32
|
+
{{ requestUrl }}
|
|
33
|
+
</div>
|
|
34
|
+
<button
|
|
35
|
+
type="button"
|
|
36
|
+
data-copy-url
|
|
37
|
+
class="flex flex-shrink-0 items-center justify-center h-6 w-6 bg-white/5 dark:bg-white/5 hover:bg-neutral-100 dark:hover:bg-white/10 border border-neutral-200 rounded-md dark:border-white/10 duration-200 ease-in-out transition-colors cursor-pointer"
|
|
38
|
+
title="Copy URL"
|
|
39
|
+
>
|
|
40
|
+
<svg class="h-3 w-3 text-neutral-400" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/></svg>
|
|
41
|
+
</button>
|
|
42
|
+
</div>
|
|
43
|
+
</div>
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
<script server>
|
|
2
|
+
/*
|
|
3
|
+
* The context `renderErrorPage` spreads into this template, declared so the
|
|
4
|
+
* checker holds it to the same shape the renderer builds.
|
|
5
|
+
*
|
|
6
|
+
* Every name below is typed by indexing `ErrorPageViewModel` rather than
|
|
7
|
+
* restating it, so renaming a field there is a build error here instead of
|
|
8
|
+
* an `undefined` rendered onto the error page - the one page where a silent
|
|
9
|
+
* failure is most expensive, because you are already debugging something
|
|
10
|
+
* else when you read it.
|
|
11
|
+
*
|
|
12
|
+
* See `storage/framework/core/error-handling/src/error-page-view-model.ts`.
|
|
13
|
+
*/
|
|
14
|
+
import type { ErrorPageViewModel } from '../../../error-page-view-model'
|
|
15
|
+
|
|
16
|
+
declare const routeParamsJson: ErrorPageViewModel['routeParamsJson']
|
|
17
|
+
</script>
|
|
18
|
+
<div class="flex flex-col gap-3">
|
|
19
|
+
<h2 class="font-semibold text-lg text-neutral-900 dark:text-white">Routing parameters</h2>
|
|
20
|
+
<pre class="overflow-x-auto p-4 font-mono text-neutral-800 text-sm dark:text-neutral-200 bg-neutral-100 dark:bg-white/5 rounded-lg">{{ routeParamsJson }}</pre>
|
|
21
|
+
</div>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<div class="flex flex-col gap-3">
|
|
2
|
+
<h2 class="font-semibold text-lg text-neutral-900 dark:text-white">Routing</h2>
|
|
3
|
+
<div class="flex flex-col">
|
|
4
|
+
@foreach(routing as row)
|
|
5
|
+
<div class="flex gap-2 items-baseline h-10 max-w-full font-mono text-sm">
|
|
6
|
+
<div class="shrink-0 text-neutral-500 uppercase dark:text-neutral-400">{{ row.key }}</div>
|
|
7
|
+
<div class="grow h-3 min-w-6 border-b-2 border-dotted border-neutral-300 dark:border-white/20"></div>
|
|
8
|
+
<div class="text-neutral-900 truncate dark:text-white" title="{{ row.value }}">{{ row.value }}</div>
|
|
9
|
+
</div>
|
|
10
|
+
@endforeach
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
<script>
|
|
2
|
+
function stacksCopy(text: string) {
|
|
3
|
+
if (navigator.clipboard) return navigator.clipboard.writeText(text);
|
|
4
|
+
const ta = document.createElement('textarea');
|
|
5
|
+
ta.value = text;
|
|
6
|
+
ta.style.position = 'fixed';
|
|
7
|
+
ta.style.opacity = '0';
|
|
8
|
+
document.body.appendChild(ta);
|
|
9
|
+
ta.select();
|
|
10
|
+
document.execCommand('copy');
|
|
11
|
+
document.body.removeChild(ta);
|
|
12
|
+
return Promise.resolve();
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
document.querySelectorAll('[data-copy-markdown]').forEach((btn) => {
|
|
16
|
+
const json = document.querySelector('[data-markdown]');
|
|
17
|
+
if (!json) return;
|
|
18
|
+
btn.addEventListener('click', async () => {
|
|
19
|
+
try {
|
|
20
|
+
const md = JSON.parse(json.textContent || '""');
|
|
21
|
+
await stacksCopy(md);
|
|
22
|
+
const label = btn.querySelector('[data-copy-label]');
|
|
23
|
+
if (label) {
|
|
24
|
+
label.textContent = 'Copied to clipboard';
|
|
25
|
+
setTimeout(() => { label.textContent = 'Copy as Markdown'; }, 2000);
|
|
26
|
+
}
|
|
27
|
+
} catch (e) { console.error(e); }
|
|
28
|
+
});
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
document.querySelectorAll('[data-copy-url]').forEach((btn) => {
|
|
32
|
+
btn.addEventListener('click', async () => {
|
|
33
|
+
const root = btn.closest('[data-copy-url-root]');
|
|
34
|
+
const url = root?.querySelector('[title]')?.getAttribute('title');
|
|
35
|
+
if (url) await stacksCopy(url);
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
document.querySelectorAll('[data-trace-toggle]').forEach((el) => {
|
|
40
|
+
el.addEventListener('click', () => {
|
|
41
|
+
const frame = el.closest<HTMLElement>('[data-trace-frame]');
|
|
42
|
+
if (!frame || frame.dataset.hasCode !== '1') return;
|
|
43
|
+
frame.classList.toggle('is-expanded');
|
|
44
|
+
const body = frame.querySelector('.trace-frame-body');
|
|
45
|
+
if (body) body.classList.toggle('hidden');
|
|
46
|
+
const header = frame.querySelector('.trace-frame-header');
|
|
47
|
+
if (header) {
|
|
48
|
+
header.classList.toggle('dark:bg-white/5');
|
|
49
|
+
header.classList.toggle('rounded-t-lg');
|
|
50
|
+
header.classList.toggle('rounded-lg');
|
|
51
|
+
}
|
|
52
|
+
const dot = frame.querySelector('.size-2.rounded-full');
|
|
53
|
+
if (dot) {
|
|
54
|
+
dot.classList.toggle('bg-rose-500');
|
|
55
|
+
dot.classList.toggle('dark:bg-neutral-400');
|
|
56
|
+
dot.classList.toggle('bg-rose-200');
|
|
57
|
+
dot.classList.toggle('dark:bg-neutral-700');
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
document.querySelectorAll('[data-vendor-toggle]').forEach((el) => {
|
|
63
|
+
el.addEventListener('click', () => {
|
|
64
|
+
const group = el.closest('[data-vendor-group]');
|
|
65
|
+
if (!group) return;
|
|
66
|
+
const frames = group.querySelector('.vendor-frames');
|
|
67
|
+
if (frames) frames.classList.toggle('hidden');
|
|
68
|
+
group.classList.toggle('bg-white');
|
|
69
|
+
group.classList.toggle('dark:bg-white/5');
|
|
70
|
+
group.classList.toggle('shadow-xs');
|
|
71
|
+
group.classList.toggle('border-solid');
|
|
72
|
+
group.classList.toggle('border-dashed');
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
</script>
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
<script server>
|
|
2
|
+
/*
|
|
3
|
+
* `extraClass`, not `class`: the latter is a reserved word, so `{{ class }}`
|
|
4
|
+
* cannot be parsed as an expression by anything that reads this template as
|
|
5
|
+
* TypeScript, however well the renderer coped with it.
|
|
6
|
+
*/
|
|
7
|
+
declare const extraClass: string | undefined
|
|
8
|
+
</script>
|
|
9
|
+
<div class="h-0 w-full relative {{ extraClass ?? '' }}">
|
|
10
|
+
<div class="absolute bottom-0 left-0 right-0 top-[-1px] border-dashed border-neutral-300 border-t dark:border-white/[9%]"></div>
|
|
11
|
+
</div>
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
<script server>
|
|
2
|
+
/*
|
|
3
|
+
* The context `renderErrorPage` spreads into this template, declared so the
|
|
4
|
+
* checker holds it to the same shape the renderer builds.
|
|
5
|
+
*
|
|
6
|
+
* Every name below is typed by indexing `ErrorPageViewModel` rather than
|
|
7
|
+
* restating it, so renaming a field there is a build error here instead of
|
|
8
|
+
* an `undefined` rendered onto the error page - the one page where a silent
|
|
9
|
+
* failure is most expensive, because you are already debugging something
|
|
10
|
+
* else when you read it.
|
|
11
|
+
*
|
|
12
|
+
* See `storage/framework/core/error-handling/src/error-page-view-model.ts`.
|
|
13
|
+
*/
|
|
14
|
+
import type { ErrorPageViewModel } from '../../../error-page-view-model'
|
|
15
|
+
|
|
16
|
+
declare const statusTitle: ErrorPageViewModel['statusTitle']
|
|
17
|
+
</script>
|
|
18
|
+
<div class="flex items-center justify-between" data-copy-root>
|
|
19
|
+
<div class="flex gap-2 items-center h-[56px]">
|
|
20
|
+
<div class="flex items-center justify-center h-[18px] w-[18px] bg-rose-500 rounded-md">
|
|
21
|
+
<svg width="2" height="10" class="text-white" viewBox="0 0 2 10" fill="none" xmlns="http://www.w3.org/2000/svg">
|
|
22
|
+
<path d="M1.00006 6.3188C1.41416 6.3188 1.75006 5.98295 1.75006 5.56885V1.43115C1.75006 1.01705 1.41416 0.681152 1.00006 0.681152C0.585961 0.681152 0.250061 1.01705 0.250061 1.43115V5.56885C0.250061 5.98295 0.585961 6.3188 1.00006 6.3188Z" fill="currentColor" />
|
|
23
|
+
<path d="M1.00006 9.41699C1.55235 9.41699 2.00007 8.96929 2.00007 8.41699C2.00007 7.86469 1.55235 7.41699 1.00006 7.41699C0.447781 7.41699 6.10352e-05 7.86469 6.10352e-05 8.41699C6.10352e-05 8.96929 0.447781 9.41699 1.00006 9.41699Z" fill="currentColor" />
|
|
24
|
+
</svg>
|
|
25
|
+
</div>
|
|
26
|
+
<div class="font-medium text-neutral-900 text-sm dark:text-white">{{ statusTitle }}</div>
|
|
27
|
+
</div>
|
|
28
|
+
|
|
29
|
+
@if(enableCopyMarkdown)
|
|
30
|
+
<button
|
|
31
|
+
type="button"
|
|
32
|
+
data-copy-markdown
|
|
33
|
+
class="flex gap-2 items-center px-3 h-8 text-neutral-600 text-sm dark:text-neutral-400 bg-white/5 dark:bg-white/5 hover:bg-neutral-100 dark:hover:bg-white/10 border border-neutral-200 rounded-md dark:border-white/10 shadow-xs duration-200 ease-in-out transition-colors cursor-pointer"
|
|
34
|
+
>
|
|
35
|
+
<span data-copy-label>Copy as Markdown</span>
|
|
36
|
+
</button>
|
|
37
|
+
<script type="application/json" data-markdown>{{ markdownJson }}</script>
|
|
38
|
+
@endif
|
|
39
|
+
</div>
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
<div class="flex flex-col gap-2.5 p-2.5 bg-neutral-50 dark:bg-white/1 border border-neutral-200 rounded-xl dark:border-neutral-800 shadow-xs">
|
|
2
|
+
<div class="flex gap-2.5 items-center p-2">
|
|
3
|
+
<div class="flex items-center justify-center p-1 h-6 w-6 bg-white dark:bg-neutral-800 border border-neutral-200 rounded-md dark:border-white/5">
|
|
4
|
+
<svg class="h-2.5 w-2.5 text-blue-500 dark:text-emerald-500" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M10.29 3.86 1.82 18a2 2 0 0 0 1.71 3h16.94a2 2 0 0 0 1.71-3L13.71 3.86a2 2 0 0 0-3.42 0z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/></svg>
|
|
5
|
+
</div>
|
|
6
|
+
<h3 class="font-semibold text-base text-neutral-900 dark:text-white">Exception trace</h3>
|
|
7
|
+
</div>
|
|
8
|
+
|
|
9
|
+
<div class="flex flex-col gap-1.5">
|
|
10
|
+
@foreach(traceGroups as group)
|
|
11
|
+
@if(group.type === 'vendor')
|
|
12
|
+
@include('./vendor-frames', { frames: group.frames, vendorCount: group.vendorCount })
|
|
13
|
+
@else
|
|
14
|
+
@foreach(group.frames as frame)
|
|
15
|
+
@include('./frame', { frame: frame })
|
|
16
|
+
@endforeach
|
|
17
|
+
@endif
|
|
18
|
+
@endforeach
|
|
19
|
+
</div>
|
|
20
|
+
</div>
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
<div class="flex flex-col gap-3">
|
|
2
|
+
<h2 class="font-semibold text-lg text-neutral-900 dark:text-white">User</h2>
|
|
3
|
+
<div class="flex flex-col">
|
|
4
|
+
@foreach(userRows as row)
|
|
5
|
+
<div class="flex gap-2 items-baseline h-10 max-w-full font-mono text-sm">
|
|
6
|
+
<div class="shrink-0 text-neutral-500 uppercase dark:text-neutral-400">{{ row.key }}</div>
|
|
7
|
+
<div class="grow h-3 min-w-6 border-b-2 border-dotted border-neutral-300 dark:border-white/20"></div>
|
|
8
|
+
<div class="text-neutral-900 truncate dark:text-white">{{ row.value }}</div>
|
|
9
|
+
</div>
|
|
10
|
+
@endforeach
|
|
11
|
+
</div>
|
|
12
|
+
</div>
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
<script server>
|
|
2
|
+
/*
|
|
3
|
+
* Included by `trace.stx` as
|
|
4
|
+
*
|
|
5
|
+
* @include('./vendor-frames', { frames: group.frames, vendorCount: group.vendorCount })
|
|
6
|
+
*
|
|
7
|
+
* so these are include props off one trace group, not page context. Typed
|
|
8
|
+
* from the group model rather than restated.
|
|
9
|
+
*/
|
|
10
|
+
import type { TraceGroupViewModel } from '../../../error-page-view-model'
|
|
11
|
+
|
|
12
|
+
declare const frames: TraceGroupViewModel['frames']
|
|
13
|
+
declare const vendorCount: TraceGroupViewModel['vendorCount']
|
|
14
|
+
</script>
|
|
15
|
+
<div class="bg-neutral-50 dark:bg-white/1 border border-dashed border-neutral-300 rounded-lg dark:border-white/10 opacity-90 vendor-group" data-vendor-group>
|
|
16
|
+
<div class="flex gap-3 items-center pl-4 pr-2.5 h-11 hover:bg-white/50 dark:hover:bg-white/2 rounded-lg cursor-pointer vendor-toggle" data-vendor-toggle>
|
|
17
|
+
<svg class="h-3 w-3 text-neutral-400 vendor-icon-closed" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 20a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.9a2 2 0 0 1-1.69-.9L9.6 3.9A2 2 0 0 0 7.93 3H4a2 2 0 0 0-2 2v13a2 2 0 0 0 2 2Z"/></svg>
|
|
18
|
+
<div class="flex-1 font-mono leading-3 text-neutral-900 text-xs dark:text-neutral-400">
|
|
19
|
+
{{ vendorCount }} framework {{ vendorCount === 1 ? 'frame' : 'frames' }}
|
|
20
|
+
</div>
|
|
21
|
+
<button type="button" class="flex items-center justify-center h-6 w-6 text-neutral-500 dark:bg-white/3 rounded-md dark:border dark:border-white/8 cursor-pointer" data-vendor-toggle>
|
|
22
|
+
<svg class="h-3 w-3" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="m7 15 5 5 5-5"/><path d="m7 9 5-5 5 5"/></svg>
|
|
23
|
+
</button>
|
|
24
|
+
</div>
|
|
25
|
+
|
|
26
|
+
<div class="flex hidden flex-col border-neutral-200 border-t divide-neutral-200 divide-y rounded-b-lg dark:border-white/5 dark:divide-white/5 vendor-frames">
|
|
27
|
+
@foreach(frames as frame)
|
|
28
|
+
@include('./frame', { frame: frame })
|
|
29
|
+
@endforeach
|
|
30
|
+
</div>
|
|
31
|
+
</div>
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
<script server>
|
|
2
|
+
/*
|
|
3
|
+
* The context `renderErrorPage` spreads into this template, declared so the
|
|
4
|
+
* checker holds it to the same shape the renderer builds.
|
|
5
|
+
*
|
|
6
|
+
* Every name below is typed by indexing `ErrorPageViewModel` rather than
|
|
7
|
+
* restating it, so renaming a field there is a build error here instead of
|
|
8
|
+
* an `undefined` rendered onto the error page - the one page where a silent
|
|
9
|
+
* failure is most expensive, because you are already debugging something
|
|
10
|
+
* else when you read it.
|
|
11
|
+
*
|
|
12
|
+
* See `storage/framework/core/error-handling/src/error-page-view-model.ts`.
|
|
13
|
+
*/
|
|
14
|
+
import type { ErrorPageViewModel } from '../../error-page-view-model'
|
|
15
|
+
|
|
16
|
+
declare const hintsHtml: ErrorPageViewModel['hintsHtml']
|
|
17
|
+
</script>
|
|
18
|
+
<div class="mx-auto px-6 py-0 sm:py-0 max-w-7xl">
|
|
19
|
+
@include('./partials/topbar')
|
|
20
|
+
</div>
|
|
21
|
+
|
|
22
|
+
@include('./partials/separator')
|
|
23
|
+
|
|
24
|
+
<div class="flex flex-col gap-8 mx-auto px-6 py-0 sm:py-0 max-w-7xl">
|
|
25
|
+
@include('./partials/header')
|
|
26
|
+
</div>
|
|
27
|
+
|
|
28
|
+
@include('./partials/separator', { extraClass: '-mt-5 -z-10' })
|
|
29
|
+
|
|
30
|
+
<div class="flex flex-col gap-8 mx-auto pt-14 px-6 max-w-7xl">
|
|
31
|
+
@if(hintsHtml)
|
|
32
|
+
<div>{!! hintsHtml !!}</div>
|
|
33
|
+
@endif
|
|
34
|
+
@include('./partials/trace')
|
|
35
|
+
@if(hasQueries)
|
|
36
|
+
@include('./partials/query')
|
|
37
|
+
@endif
|
|
38
|
+
</div>
|
|
39
|
+
|
|
40
|
+
@include('./partials/separator')
|
|
41
|
+
|
|
42
|
+
<div class="flex flex-col gap-12 mx-auto pb-14 px-6 max-w-7xl">
|
|
43
|
+
@if(hasHeaders)
|
|
44
|
+
@include('./partials/request-header')
|
|
45
|
+
@endif
|
|
46
|
+
@if(hasBody)
|
|
47
|
+
@include('./partials/request-body')
|
|
48
|
+
@endif
|
|
49
|
+
@if(hasRouting)
|
|
50
|
+
@include('./partials/routing')
|
|
51
|
+
@endif
|
|
52
|
+
@if(hasRouteParams)
|
|
53
|
+
@include('./partials/routing-parameters')
|
|
54
|
+
@endif
|
|
55
|
+
@if(hasUser)
|
|
56
|
+
@include('./partials/user')
|
|
57
|
+
@endif
|
|
58
|
+
@if(hasEnvironment)
|
|
59
|
+
@include('./partials/environment')
|
|
60
|
+
@endif
|
|
61
|
+
</div>
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stacksjs/error-handling",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "0.74.
|
|
4
|
+
"version": "0.74.27",
|
|
5
5
|
"description": "Type safe error handling.",
|
|
6
6
|
"author": "Chris Breuer",
|
|
7
7
|
"contributors": [
|
|
@@ -57,16 +57,16 @@
|
|
|
57
57
|
"prepublishOnly": "bun run build"
|
|
58
58
|
},
|
|
59
59
|
"dependencies": {
|
|
60
|
-
"@stacksjs/path": "0.74.
|
|
60
|
+
"@stacksjs/path": "0.74.27",
|
|
61
61
|
"@stacksjs/stx": "^0.2.274",
|
|
62
|
-
"@stacksjs/types": "0.74.
|
|
62
|
+
"@stacksjs/types": "0.74.27",
|
|
63
63
|
"ts-error-handling": "^0.1.1",
|
|
64
64
|
"ts-syntax-highlighter": "^0.2.1"
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
|
-
"@stacksjs/cli": "0.74.
|
|
68
|
-
"@stacksjs/config": "0.74.
|
|
69
|
-
"@stacksjs/validation": "0.74.
|
|
67
|
+
"@stacksjs/cli": "0.74.27",
|
|
68
|
+
"@stacksjs/config": "0.74.27",
|
|
69
|
+
"@stacksjs/validation": "0.74.27",
|
|
70
70
|
"better-dx": "^0.2.24"
|
|
71
71
|
},
|
|
72
72
|
"sideEffects": false
|