effect-start 0.26.0 → 0.27.0

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.
Files changed (58) hide show
  1. package/package.json +4 -2
  2. package/src/Entity.ts +6 -6
  3. package/src/FileRouterCodegen.ts +4 -4
  4. package/src/FileSystem.ts +4 -8
  5. package/src/RouteHook.ts +1 -1
  6. package/src/RouteSse.ts +3 -3
  7. package/src/SqlIntrospect.ts +2 -2
  8. package/src/Start.ts +102 -2
  9. package/src/Values.ts +11 -0
  10. package/src/bun/BunRoute.ts +1 -1
  11. package/src/bun/BunRuntime.ts +5 -5
  12. package/src/hyper/HyperHtml.ts +11 -7
  13. package/src/hyper/jsx.d.ts +1 -1
  14. package/src/lint/plugin.js +174 -4
  15. package/src/sql/SqlClient.ts +355 -0
  16. package/src/sql/bun/index.ts +117 -50
  17. package/src/sql/index.ts +1 -1
  18. package/src/sql/libsql/index.ts +91 -77
  19. package/src/sql/libsql/libsql.d.ts +4 -1
  20. package/src/sql/mssql/index.ts +141 -108
  21. package/src/sql/mssql/mssql.d.ts +1 -0
  22. package/src/testing/TestLogger.ts +4 -4
  23. package/src/x/tailwind/compile.ts +6 -14
  24. package/src/console/Console.ts +0 -42
  25. package/src/console/ConsoleErrors.ts +0 -213
  26. package/src/console/ConsoleLogger.ts +0 -56
  27. package/src/console/ConsoleMetrics.ts +0 -72
  28. package/src/console/ConsoleProcess.ts +0 -59
  29. package/src/console/ConsoleStore.ts +0 -187
  30. package/src/console/ConsoleTracer.ts +0 -107
  31. package/src/console/Simulation.ts +0 -814
  32. package/src/console/console.html +0 -340
  33. package/src/console/index.ts +0 -3
  34. package/src/console/routes/errors/route.tsx +0 -97
  35. package/src/console/routes/fiberDetail.tsx +0 -54
  36. package/src/console/routes/fibers/route.tsx +0 -45
  37. package/src/console/routes/git/route.tsx +0 -64
  38. package/src/console/routes/layout.tsx +0 -4
  39. package/src/console/routes/logs/route.tsx +0 -77
  40. package/src/console/routes/metrics/route.tsx +0 -36
  41. package/src/console/routes/route.tsx +0 -8
  42. package/src/console/routes/routes/route.tsx +0 -30
  43. package/src/console/routes/services/route.tsx +0 -21
  44. package/src/console/routes/system/route.tsx +0 -43
  45. package/src/console/routes/traceDetail.tsx +0 -22
  46. package/src/console/routes/traces/route.tsx +0 -81
  47. package/src/console/routes/tree.ts +0 -30
  48. package/src/console/ui/Errors.tsx +0 -76
  49. package/src/console/ui/Fibers.tsx +0 -321
  50. package/src/console/ui/Git.tsx +0 -182
  51. package/src/console/ui/Logs.tsx +0 -46
  52. package/src/console/ui/Metrics.tsx +0 -78
  53. package/src/console/ui/Routes.tsx +0 -125
  54. package/src/console/ui/Services.tsx +0 -273
  55. package/src/console/ui/Shell.tsx +0 -62
  56. package/src/console/ui/System.tsx +0 -131
  57. package/src/console/ui/Traces.tsx +0 -426
  58. package/src/sql/Sql.ts +0 -51
@@ -1,340 +0,0 @@
1
- <!doctype html>
2
- <html style="height: 100%">
3
- <head>
4
- <title>Effect Console</title>
5
- <meta name="viewport" content="width=device-width, initial-scale=1" />
6
- <script type="module" src="effect-start/datastar"></script>
7
- <style>
8
- * {
9
- margin: 0;
10
- padding: 0;
11
- box-sizing: border-box;
12
- }
13
- body {
14
- height: 100%;
15
- font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
16
- background: #0f172a;
17
- color: #e5e7eb;
18
- }
19
- .shell {
20
- display: flex;
21
- height: 100%;
22
- }
23
- .sidebar {
24
- width: 180px;
25
- background: #1e293b;
26
- border-right: 1px solid #334155;
27
- padding: 16px 0;
28
- flex-shrink: 0;
29
- display: flex;
30
- flex-direction: column;
31
- }
32
- .sidebar-title {
33
- font-size: 14px;
34
- font-weight: 700;
35
- color: #f1f5f9;
36
- padding: 0 16px 16px;
37
- border-bottom: 1px solid #334155;
38
- margin-bottom: 8px;
39
- }
40
- .nav-link {
41
- display: block;
42
- padding: 8px 16px;
43
- color: #94a3b8;
44
- text-decoration: none;
45
- font-size: 13px;
46
- }
47
- .nav-link:hover {
48
- color: #e2e8f0;
49
- background: #334155;
50
- }
51
- .nav-link.active {
52
- color: #38bdf8;
53
- background: #0f172a;
54
- border-right: 2px solid #38bdf8;
55
- }
56
- .content {
57
- flex: 1;
58
- display: flex;
59
- flex-direction: column;
60
- overflow: hidden;
61
- }
62
- .tab-header {
63
- font-size: 15px;
64
- font-weight: 600;
65
- padding: 12px 16px;
66
- border-bottom: 1px solid #1e293b;
67
- }
68
- .tab-body {
69
- flex: 1;
70
- overflow-y: auto;
71
- padding: 8px;
72
- }
73
- .filter-bar {
74
- display: flex;
75
- gap: 8px;
76
- padding: 8px 16px;
77
- border-bottom: 1px solid #1e293b;
78
- }
79
- .filter-bar select,
80
- .filter-bar input {
81
- background: #1e293b;
82
- border: 1px solid #334155;
83
- color: #e5e7eb;
84
- padding: 4px 8px;
85
- border-radius: 4px;
86
- font-size: 12px;
87
- }
88
- .filter-bar input {
89
- flex: 1;
90
- }
91
- .metrics-grid {
92
- display: grid;
93
- grid-template-columns: repeat(auto-fill, minmax(240px, 1fr));
94
- gap: 12px;
95
- }
96
- .empty {
97
- color: #64748b;
98
- font-size: 13px;
99
- text-align: center;
100
- padding: 48px 0;
101
- }
102
-
103
- /* Waterfall grid */
104
- .wf-grid {
105
- display: grid;
106
- grid-template-columns: minmax(200px, 30%) 1fr;
107
- font-size: 12px;
108
- }
109
- .wf-row {
110
- display: contents;
111
- }
112
- .wf-row:hover > .wf-name,
113
- .wf-row:hover > .wf-bar-cell {
114
- background: #1e293b;
115
- }
116
- .wf-name {
117
- padding: 4px 8px;
118
- font-family: monospace;
119
- color: #d1d5db;
120
- white-space: nowrap;
121
- overflow: hidden;
122
- text-overflow: ellipsis;
123
- border-bottom: 1px solid #1e293b;
124
- position: relative;
125
- display: flex;
126
- align-items: center;
127
- gap: 4px;
128
- }
129
- .wf-bar-cell {
130
- padding: 4px 8px;
131
- position: relative;
132
- border-bottom: 1px solid #1e293b;
133
- display: flex;
134
- align-items: center;
135
- }
136
- .wf-bar {
137
- height: 14px;
138
- border-radius: 2px;
139
- position: absolute;
140
- min-width: 2px;
141
- top: 50%;
142
- transform: translateY(-50%);
143
- }
144
- .wf-dur {
145
- position: absolute;
146
- color: #9ca3af;
147
- font-family: monospace;
148
- font-size: 11px;
149
- white-space: nowrap;
150
- top: 50%;
151
- transform: translateY(-50%);
152
- }
153
- .wf-axis {
154
- display: grid;
155
- grid-template-columns: minmax(200px, 30%) 1fr;
156
- border-bottom: 1px solid #334155;
157
- font-size: 11px;
158
- color: #64748b;
159
- font-family: monospace;
160
- }
161
- .wf-axis-ticks {
162
- display: flex;
163
- justify-content: space-between;
164
- padding: 4px 8px;
165
- }
166
-
167
- /* Tree connectors */
168
- .wf-tree {
169
- position: relative;
170
- display: flex;
171
- align-items: center;
172
- gap: 4px;
173
- flex-shrink: 0;
174
- }
175
- .wf-vline {
176
- position: absolute;
177
- width: 1px;
178
- background: #475569;
179
- top: -4px;
180
- bottom: -4px;
181
- }
182
- .wf-branch {
183
- position: absolute;
184
- height: 1px;
185
- background: #475569;
186
- width: 8px;
187
- }
188
- .wf-elbow {
189
- position: absolute;
190
- width: 1px;
191
- background: #475569;
192
- top: -4px;
193
- bottom: 50%;
194
- }
195
- .wf-hline {
196
- position: absolute;
197
- height: 1px;
198
- background: #475569;
199
- width: 8px;
200
- }
201
-
202
- /* Child count badge */
203
- .wf-badge {
204
- font-size: 10px;
205
- padding: 0 5px;
206
- border-radius: 8px;
207
- background: #1e293b;
208
- color: #64748b;
209
- flex-shrink: 0;
210
- }
211
-
212
- /* Mini waterfall */
213
- .mini-wf {
214
- height: 20px;
215
- background: #1f2937;
216
- border-radius: 3px;
217
- position: relative;
218
- overflow: hidden;
219
- }
220
- .mini-wf-bar {
221
- position: absolute;
222
- height: 6px;
223
- border-radius: 1px;
224
- min-width: 2px;
225
- top: 50%;
226
- transform: translateY(-50%);
227
- }
228
-
229
- /* Trace cards */
230
- .trace-card {
231
- display: block;
232
- margin-bottom: 8px;
233
- background: #111827;
234
- border: 1px solid #1e293b;
235
- border-radius: 6px;
236
- overflow: hidden;
237
- text-decoration: none;
238
- color: inherit;
239
- transition: border-color 0.15s;
240
- }
241
- .trace-card:hover {
242
- border-color: #475569;
243
- }
244
-
245
- /* Trace list */
246
- .tl-grid {
247
- font-size: 12px;
248
- }
249
- .tl-cols {
250
- display: grid;
251
- grid-template-columns: 24px 1fr 48px 72px 90px;
252
- }
253
- .tl-header {
254
- position: sticky;
255
- top: 0;
256
- z-index: 1;
257
- background: #0f172a;
258
- border-bottom: 1px solid #334155;
259
- color: #64748b;
260
- font-size: 11px;
261
- }
262
- .tl-row {
263
- border-bottom: 1px solid #1e293b;
264
- }
265
- .tl-row[open] {
266
- background: #111827;
267
- }
268
- .tl-summary {
269
- cursor: pointer;
270
- user-select: none;
271
- list-style: none;
272
- }
273
- .tl-summary::-webkit-details-marker {
274
- display: none;
275
- }
276
- .tl-summary:hover {
277
- background: #1e293b;
278
- }
279
- .tl-cell {
280
- padding: 6px 8px;
281
- display: flex;
282
- align-items: center;
283
- overflow: hidden;
284
- }
285
- .tl-cell-name {
286
- font-family: monospace;
287
- color: #e2e8f0;
288
- font-weight: 600;
289
- white-space: nowrap;
290
- text-overflow: ellipsis;
291
- }
292
- .tl-cell-spans {
293
- color: #64748b;
294
- font-size: 11px;
295
- justify-content: flex-end;
296
- }
297
- .tl-cell-dur {
298
- color: #64748b;
299
- font-family: monospace;
300
- font-size: 11px;
301
- justify-content: flex-end;
302
- }
303
- .tl-cell-id {
304
- color: #475569;
305
- font-family: monospace;
306
- font-size: 10px;
307
- }
308
- .tl-body {
309
- padding: 8px 12px 12px;
310
- border-top: 1px solid #1e293b;
311
- }
312
-
313
- /* Span detail panel */
314
- .span-panel {
315
- background: #111827;
316
- border: 1px solid #1e293b;
317
- border-radius: 6px;
318
- overflow: hidden;
319
- margin: 4px 0;
320
- }
321
- .span-panel-header {
322
- padding: 6px 12px;
323
- display: flex;
324
- align-items: center;
325
- gap: 8px;
326
- cursor: pointer;
327
- user-select: none;
328
- }
329
- .span-panel-header:hover {
330
- background: #1e293b;
331
- }
332
- .span-panel-body {
333
- padding: 0 12px 8px;
334
- }
335
- </style>
336
- </head>
337
- <body>
338
- %children%
339
- </body>
340
- </html>
@@ -1,3 +0,0 @@
1
- export * as Console from "./Console.ts"
2
- export * as ConsoleStore from "./ConsoleStore.ts"
3
- export * as Simulation from "./Simulation.ts"
@@ -1,97 +0,0 @@
1
- import * as Stream from "effect/Stream"
2
- import * as Route from "../../../Route.ts"
3
- import * as HyperHtml from "../../../hyper/HyperHtml.ts"
4
- import * as HyperRoute from "../../../hyper/HyperRoute.ts"
5
- import * as ConsoleStore from "../../ConsoleStore.ts"
6
- import * as Errors from "../../ui/Errors.tsx"
7
- import * as Shell from "../../ui/Shell.tsx"
8
-
9
- const prefix = ConsoleStore.store.prefix
10
-
11
- function collectTags(): Array<string> {
12
- const tags = new Set<string>()
13
- for (const error of ConsoleStore.store.errors.toArray()) {
14
- for (const d of error.details) {
15
- if (d.tag) tags.add(d.tag)
16
- }
17
- }
18
- return Array.from(tags).sort()
19
- }
20
-
21
- function filterErrors(url: URL): Array<ConsoleStore.ConsoleError> {
22
- const search = url.searchParams.get("errorSearch") || ""
23
- const tag = url.searchParams.get("errorTag") || ""
24
- let errors = ConsoleStore.store.errors.toArray()
25
- if (tag) {
26
- errors = errors.filter((e) => e.details.some((d) => d.tag && d.tag.startsWith(tag)))
27
- }
28
- if (search) {
29
- const lower = search.toLowerCase()
30
- errors = errors.filter((e) => {
31
- const firstLine = e.prettyPrint.split("\n")[0] ?? ""
32
- return firstLine.toLowerCase().includes(lower)
33
- })
34
- }
35
- return errors.reverse()
36
- }
37
-
38
- export default Route.get(
39
- HyperRoute.html(function* (ctx) {
40
- const url = new URL(ctx.request.url)
41
- const errors = filterErrors(url)
42
- const tags = collectTags()
43
-
44
- return (
45
- <Shell.Shell prefix={prefix} active="errors">
46
- <form
47
- data-signals={{ errorSearch: "", errorTag: "" }}
48
- style="display:flex;flex-direction:column;flex:1;overflow:hidden"
49
- >
50
- <div class="tab-header">Errors</div>
51
- <div class="filter-bar">
52
- <input
53
- type="text"
54
- name="errorSearch"
55
- placeholder="Search..."
56
- data-bind:errorSearch
57
- data-on:input={(c) => c.actions.get(location.href, { contentType: "form" })}
58
- />
59
- <input
60
- type="text"
61
- name="errorTag"
62
- placeholder="Tag..."
63
- list="error-tags"
64
- data-bind:errorTag
65
- data-on:input={(c) => c.actions.get(location.href, { contentType: "form" })}
66
- />
67
- <datalist id="error-tags">
68
- {tags.map((t) => (
69
- <option value={t} />
70
- ))}
71
- </datalist>
72
- </div>
73
- <div id="errors-list" class="tab-body">
74
- {errors.map((e) => (
75
- <Errors.ErrorLine error={e} />
76
- ))}
77
- </div>
78
- </form>
79
- </Shell.Shell>
80
- )
81
- }),
82
- Route.sse(
83
- Stream.fromPubSub(ConsoleStore.store.events).pipe(
84
- Stream.filter((e) => e._tag === "Error"),
85
- Stream.map((e) => {
86
- const html = HyperHtml.renderToString(<Errors.ErrorLine error={e.error} />).replace(
87
- /\n/g,
88
- "",
89
- )
90
- return {
91
- event: "datastar-patch-elements",
92
- data: `selector #errors-list\nmode prepend\nelements ${html}`,
93
- }
94
- }),
95
- ),
96
- ),
97
- )
@@ -1,54 +0,0 @@
1
- import * as Schema from "effect/Schema"
2
- import * as Route from "../../Route.ts"
3
- import * as RouteSchema from "../../RouteSchema.ts"
4
- import * as HyperRoute from "../../hyper/HyperRoute.ts"
5
- import * as ConsoleStore from "../ConsoleStore.ts"
6
- import * as Fibers from "../ui/Fibers.tsx"
7
- import * as Shell from "../ui/Shell.tsx"
8
-
9
- export default Route.get(
10
- RouteSchema.schemaPathParams(Schema.Struct({ id: Schema.String })),
11
- HyperRoute.html(function* (ctx) {
12
- const fiberId = ctx.pathParams.id
13
- const fiberName = fiberId.startsWith("#") ? fiberId : `#${fiberId}`
14
-
15
- const allLogs = ConsoleStore.store.logs.toArray()
16
- const fiberLogs = allLogs.filter((l) => l.fiberId === fiberName)
17
-
18
- const allSpans = ConsoleStore.store.spans.toArray()
19
- const fiberSpans = allSpans.filter((s) => {
20
- const spanFiber = s.attributes["fiber.id"] as string | undefined
21
- return spanFiber === fiberName
22
- })
23
-
24
- const fiberNum = parseInt(fiberName.slice(1), 10)
25
- const counter = ConsoleStore.fiberIdCounter()
26
- let alive: "alive" | "dead" | "unknown" = "unknown"
27
- if (!isNaN(fiberNum)) {
28
- if (fiberNum < counter) alive = "dead"
29
- else alive = "unknown"
30
- }
31
- if (fiberLogs.length > 0 || fiberSpans.length > 0) {
32
- const hasRecent = fiberLogs.some((l) => Date.now() - l.date.getTime() < 5000)
33
- if (hasRecent) alive = "alive"
34
- else if (fiberNum < counter) alive = "dead"
35
- }
36
-
37
- const parents = Fibers.getParentChain(fiberName, ConsoleStore.store.fiberParents)
38
- const fiberContext = ConsoleStore.store.fiberContexts.get(fiberName)
39
-
40
- return (
41
- <Shell.Shell prefix={ConsoleStore.store.prefix} active="fibers">
42
- <Fibers.FiberDetail
43
- prefix={ConsoleStore.store.prefix}
44
- fiberId={fiberName}
45
- logs={fiberLogs}
46
- spans={fiberSpans}
47
- alive={alive}
48
- parents={parents}
49
- context={fiberContext}
50
- />
51
- </Shell.Shell>
52
- )
53
- }),
54
- )
@@ -1,45 +0,0 @@
1
- import * as Stream from "effect/Stream"
2
- import * as Route from "../../../Route.ts"
3
- import * as HyperHtml from "../../../hyper/HyperHtml.ts"
4
- import * as HyperRoute from "../../../hyper/HyperRoute.ts"
5
- import * as ConsoleStore from "../../ConsoleStore.ts"
6
- import * as Fibers from "../../ui/Fibers.tsx"
7
- import * as Shell from "../../ui/Shell.tsx"
8
-
9
- export default Route.get(
10
- HyperRoute.html(function* () {
11
- const fibers = Fibers.collectFibers(
12
- ConsoleStore.store.logs.toArray(),
13
- ConsoleStore.store.spans.toArray(),
14
- )
15
- return (
16
- <Shell.Shell prefix={ConsoleStore.store.prefix} active="fibers">
17
- <div style="display:flex;flex-direction:column;flex:1;overflow:hidden">
18
- <div class="tab-header">Fibers</div>
19
- <div id="fibers-container" class="tab-body">
20
- <Fibers.FiberList fibers={fibers} prefix={ConsoleStore.store.prefix} />
21
- </div>
22
- <div data-init={`@get('${ConsoleStore.store.prefix}/fibers')`} />
23
- </div>
24
- </Shell.Shell>
25
- )
26
- }),
27
- Route.sse(
28
- Stream.fromPubSub(ConsoleStore.store.events).pipe(
29
- Stream.filter((e) => e._tag === "SpanStart" || e._tag === "SpanEnd" || e._tag === "Log"),
30
- Stream.map(() => {
31
- const fibers = Fibers.collectFibers(
32
- ConsoleStore.store.logs.toArray(),
33
- ConsoleStore.store.spans.toArray(),
34
- )
35
- const html = HyperHtml.renderToString(
36
- <Fibers.FiberList fibers={fibers} prefix={ConsoleStore.store.prefix} />,
37
- ).replace(/\n/g, "")
38
- return {
39
- event: "datastar-patch-elements",
40
- data: `selector #fibers-container\nmode inner\nelements ${html}`,
41
- }
42
- }),
43
- ),
44
- ),
45
- )
@@ -1,64 +0,0 @@
1
- import * as Effect from "effect/Effect"
2
- import * as Schedule from "effect/Schedule"
3
- import * as Stream from "effect/Stream"
4
- import * as ChildProcess from "../../../ChildProcess.ts"
5
- import * as Route from "../../../Route.ts"
6
- import * as HyperHtml from "../../../hyper/HyperHtml.ts"
7
- import * as HyperRoute from "../../../hyper/HyperRoute.ts"
8
- import * as ConsoleStore from "../../ConsoleStore.ts"
9
- import * as Git from "../../ui/Git.tsx"
10
- import * as Shell from "../../ui/Shell.tsx"
11
-
12
- const exec = (args: Array<string>, cwd?: string) =>
13
- Effect.gen(function* () {
14
- const handle = yield* ChildProcess.make("git", args, cwd ? { cwd } : undefined)
15
- return yield* handle.stdout.pipe(Stream.decodeText("utf-8"), Stream.mkString)
16
- }).pipe(Effect.catchAll(() => Effect.succeed("")))
17
-
18
- const gitStatus = exec(["rev-parse", "--show-toplevel"]).pipe(
19
- Effect.flatMap((root) => {
20
- const cwd = root.trim()
21
- return Effect.all(
22
- {
23
- porcelain: exec(["status", "--porcelain=v2", "--branch"], cwd),
24
- tag: exec(["describe", "--tags", "--abbrev=0"], cwd),
25
- tagDistance: exec(["rev-list", "--count", "HEAD", "--not", "--tags"], cwd),
26
- },
27
- { concurrency: "unbounded" },
28
- )
29
- }),
30
- Effect.map(({ porcelain, tag, tagDistance }) =>
31
- Git.parseGitStatus(porcelain, `${tag.trim()}\n${tagDistance.trim()}`),
32
- ),
33
- )
34
-
35
- export default Route.get(
36
- HyperRoute.html(function* () {
37
- const status = yield* gitStatus
38
- return (
39
- <Shell.Shell prefix={ConsoleStore.store.prefix} active="git">
40
- <div class="tab-header">Git</div>
41
- <div id="git-container" class="tab-body">
42
- <Git.GitStatusView status={status} />
43
- </div>
44
- <div data-init={`@get('${ConsoleStore.store.prefix}/git')`} />
45
- </Shell.Shell>
46
- )
47
- }),
48
- Route.sse(
49
- Stream.repeatEffect(
50
- gitStatus.pipe(
51
- Effect.map((status) => {
52
- const html = HyperHtml.renderToString(<Git.GitStatusView status={status} />).replace(
53
- /\n/g,
54
- "",
55
- )
56
- return {
57
- event: "datastar-patch-elements" as const,
58
- data: `selector #git-container\nmode inner\nelements ${html}`,
59
- }
60
- }),
61
- ),
62
- ).pipe(Stream.schedule(Schedule.spaced("2 seconds"))),
63
- ),
64
- )
@@ -1,4 +0,0 @@
1
- import * as BunRoute from "../../bun/BunRoute.ts"
2
- import * as Route from "../../Route.ts"
3
-
4
- export default Route.use(BunRoute.htmlBundle(() => import("../console.html")))
@@ -1,77 +0,0 @@
1
- import * as Stream from "effect/Stream"
2
- import * as Route from "../../../Route.ts"
3
- import * as HyperHtml from "../../../hyper/HyperHtml.ts"
4
- import * as HyperRoute from "../../../hyper/HyperRoute.ts"
5
- import * as ConsoleStore from "../../ConsoleStore.ts"
6
- import * as Logs from "../../ui/Logs.tsx"
7
- import * as Shell from "../../ui/Shell.tsx"
8
-
9
- const prefix = ConsoleStore.store.prefix
10
-
11
- function filterLogs(url: URL): Array<ConsoleStore.ConsoleLog> {
12
- const level = url.searchParams.get("logLevel") || ""
13
- const search = url.searchParams.get("logSearch") || ""
14
- let logs = ConsoleStore.store.logs.toArray()
15
- if (level) logs = logs.filter((l) => l.level === level)
16
- if (search) {
17
- const lower = search.toLowerCase()
18
- logs = logs.filter((l) => l.message.toLowerCase().includes(lower))
19
- }
20
- return logs.reverse()
21
- }
22
-
23
- export default Route.get(
24
- HyperRoute.html(function* (ctx) {
25
- const url = new URL(ctx.request.url)
26
- const logs = filterLogs(url)
27
-
28
- return (
29
- <Shell.Shell prefix={prefix} active="logs">
30
- <form
31
- data-signals={{ logLevel: "", logSearch: "" }}
32
- style="display:flex;flex-direction:column;flex:1;overflow:hidden"
33
- >
34
- <div class="tab-header">Logs</div>
35
- <div class="filter-bar">
36
- <select
37
- name="logLevel"
38
- data-bind:logLevel
39
- data-on:change={(c) => c.actions.get(location.href, { contentType: "form" })}
40
- >
41
- <option value="">All levels</option>
42
- <option value="DEBUG">DEBUG</option>
43
- <option value="INFO">INFO</option>
44
- <option value="WARNING">WARNING</option>
45
- <option value="ERROR">ERROR</option>
46
- <option value="FATAL">FATAL</option>
47
- </select>
48
- <input
49
- type="text"
50
- name="logSearch"
51
- placeholder="Search..."
52
- data-bind:logSearch
53
- data-on:input={(c) => c.actions.get(location.href, { contentType: "form" })}
54
- />
55
- </div>
56
- <div id="logs-container" class="tab-body">
57
- {logs.map((l) => (
58
- <Logs.LogLine log={l} />
59
- ))}
60
- </div>
61
- </form>
62
- </Shell.Shell>
63
- )
64
- }),
65
- Route.sse(
66
- Stream.fromPubSub(ConsoleStore.store.events).pipe(
67
- Stream.filter((e) => e._tag === "Log"),
68
- Stream.map((e) => {
69
- const html = HyperHtml.renderToString(<Logs.LogLine log={e.log} />).replace(/\n/g, "")
70
- return {
71
- event: "datastar-patch-elements",
72
- data: `selector #logs-container\nmode prepend\nelements ${html}`,
73
- }
74
- }),
75
- ),
76
- ),
77
- )