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.
- package/package.json +4 -2
- package/src/Entity.ts +6 -6
- package/src/FileRouterCodegen.ts +4 -4
- package/src/FileSystem.ts +4 -8
- package/src/RouteHook.ts +1 -1
- package/src/RouteSse.ts +3 -3
- package/src/SqlIntrospect.ts +2 -2
- package/src/Start.ts +102 -2
- package/src/Values.ts +11 -0
- package/src/bun/BunRoute.ts +1 -1
- package/src/bun/BunRuntime.ts +5 -5
- package/src/hyper/HyperHtml.ts +11 -7
- package/src/hyper/jsx.d.ts +1 -1
- package/src/lint/plugin.js +174 -4
- package/src/sql/SqlClient.ts +355 -0
- package/src/sql/bun/index.ts +117 -50
- package/src/sql/index.ts +1 -1
- package/src/sql/libsql/index.ts +91 -77
- package/src/sql/libsql/libsql.d.ts +4 -1
- package/src/sql/mssql/index.ts +141 -108
- package/src/sql/mssql/mssql.d.ts +1 -0
- package/src/testing/TestLogger.ts +4 -4
- package/src/x/tailwind/compile.ts +6 -14
- package/src/console/Console.ts +0 -42
- package/src/console/ConsoleErrors.ts +0 -213
- package/src/console/ConsoleLogger.ts +0 -56
- package/src/console/ConsoleMetrics.ts +0 -72
- package/src/console/ConsoleProcess.ts +0 -59
- package/src/console/ConsoleStore.ts +0 -187
- package/src/console/ConsoleTracer.ts +0 -107
- package/src/console/Simulation.ts +0 -814
- package/src/console/console.html +0 -340
- package/src/console/index.ts +0 -3
- package/src/console/routes/errors/route.tsx +0 -97
- package/src/console/routes/fiberDetail.tsx +0 -54
- package/src/console/routes/fibers/route.tsx +0 -45
- package/src/console/routes/git/route.tsx +0 -64
- package/src/console/routes/layout.tsx +0 -4
- package/src/console/routes/logs/route.tsx +0 -77
- package/src/console/routes/metrics/route.tsx +0 -36
- package/src/console/routes/route.tsx +0 -8
- package/src/console/routes/routes/route.tsx +0 -30
- package/src/console/routes/services/route.tsx +0 -21
- package/src/console/routes/system/route.tsx +0 -43
- package/src/console/routes/traceDetail.tsx +0 -22
- package/src/console/routes/traces/route.tsx +0 -81
- package/src/console/routes/tree.ts +0 -30
- package/src/console/ui/Errors.tsx +0 -76
- package/src/console/ui/Fibers.tsx +0 -321
- package/src/console/ui/Git.tsx +0 -182
- package/src/console/ui/Logs.tsx +0 -46
- package/src/console/ui/Metrics.tsx +0 -78
- package/src/console/ui/Routes.tsx +0 -125
- package/src/console/ui/Services.tsx +0 -273
- package/src/console/ui/Shell.tsx +0 -62
- package/src/console/ui/System.tsx +0 -131
- package/src/console/ui/Traces.tsx +0 -426
- package/src/sql/Sql.ts +0 -51
|
@@ -1,36 +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 Metrics from "../../ui/Metrics.tsx"
|
|
7
|
-
import * as Shell from "../../ui/Shell.tsx"
|
|
8
|
-
|
|
9
|
-
export default Route.get(
|
|
10
|
-
HyperRoute.html(function* () {
|
|
11
|
-
return (
|
|
12
|
-
<Shell.Shell prefix={ConsoleStore.store.prefix} active="metrics">
|
|
13
|
-
<div class="tab-header">Metrics</div>
|
|
14
|
-
<div id="metrics-container" class="tab-body metrics-grid">
|
|
15
|
-
<Metrics.MetricsGrid metrics={ConsoleStore.store.metrics} />
|
|
16
|
-
</div>
|
|
17
|
-
<div data-init={`@get('${ConsoleStore.store.prefix}/metrics')`} />
|
|
18
|
-
</Shell.Shell>
|
|
19
|
-
)
|
|
20
|
-
}),
|
|
21
|
-
Route.sse(
|
|
22
|
-
Stream.fromPubSub(ConsoleStore.store.events).pipe(
|
|
23
|
-
Stream.filter((e) => e._tag === "MetricsSnapshot"),
|
|
24
|
-
Stream.map((e) => {
|
|
25
|
-
const html = HyperHtml.renderToString(<Metrics.MetricsGrid metrics={e.metrics} />).replace(
|
|
26
|
-
/\n/g,
|
|
27
|
-
"",
|
|
28
|
-
)
|
|
29
|
-
return {
|
|
30
|
-
event: "datastar-patch-elements",
|
|
31
|
-
data: `selector #metrics-container\nmode inner\nelements ${html}`,
|
|
32
|
-
}
|
|
33
|
-
}),
|
|
34
|
-
),
|
|
35
|
-
),
|
|
36
|
-
)
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import * as Route from "../../../Route.ts"
|
|
2
|
-
import * as RouteTree from "../../../RouteTree.ts"
|
|
3
|
-
import * as HyperRoute from "../../../hyper/HyperRoute.ts"
|
|
4
|
-
import * as ConsoleStore from "../../ConsoleStore.ts"
|
|
5
|
-
import * as Routes from "../../ui/Routes.tsx"
|
|
6
|
-
import * as Shell from "../../ui/Shell.tsx"
|
|
7
|
-
|
|
8
|
-
export default Route.get(
|
|
9
|
-
HyperRoute.html(function* () {
|
|
10
|
-
const tree = yield* Route.Routes
|
|
11
|
-
const routes: Array<Routes.RouteInfo> = []
|
|
12
|
-
for (const route of RouteTree.walk(tree)) {
|
|
13
|
-
const desc = Route.descriptor(route)
|
|
14
|
-
routes.push({
|
|
15
|
-
method: desc.method ?? "*",
|
|
16
|
-
path: desc.path ?? "/",
|
|
17
|
-
format: desc.format,
|
|
18
|
-
})
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
return (
|
|
22
|
-
<Shell.Shell prefix={ConsoleStore.store.prefix} active="routes">
|
|
23
|
-
<div class="tab-header">Routes</div>
|
|
24
|
-
<div class="tab-body">
|
|
25
|
-
<Routes.RouteList routes={routes} />
|
|
26
|
-
</div>
|
|
27
|
-
</Shell.Shell>
|
|
28
|
-
)
|
|
29
|
-
}),
|
|
30
|
-
)
|
|
@@ -1,21 +0,0 @@
|
|
|
1
|
-
import * as Effect from "effect/Effect"
|
|
2
|
-
import * as Route from "../../../Route.ts"
|
|
3
|
-
import * as HyperRoute from "../../../hyper/HyperRoute.ts"
|
|
4
|
-
import * as ConsoleStore from "../../ConsoleStore.ts"
|
|
5
|
-
import * as Services from "../../ui/Services.tsx"
|
|
6
|
-
import * as Shell from "../../ui/Shell.tsx"
|
|
7
|
-
|
|
8
|
-
export default Route.get(
|
|
9
|
-
HyperRoute.html(function* () {
|
|
10
|
-
const ctx = yield* Effect.context<never>()
|
|
11
|
-
const services = Services.collectServices(ctx.unsafeMap)
|
|
12
|
-
return (
|
|
13
|
-
<Shell.Shell prefix={ConsoleStore.store.prefix} active="services">
|
|
14
|
-
<div class="tab-header">Services ({services.length})</div>
|
|
15
|
-
<div class="tab-body">
|
|
16
|
-
<Services.ServiceList services={services} />
|
|
17
|
-
</div>
|
|
18
|
-
</Shell.Shell>
|
|
19
|
-
)
|
|
20
|
-
}),
|
|
21
|
-
)
|
|
@@ -1,43 +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 System from "../../ui/System.tsx"
|
|
7
|
-
import * as Shell from "../../ui/Shell.tsx"
|
|
8
|
-
|
|
9
|
-
export default Route.get(
|
|
10
|
-
HyperRoute.html(function* () {
|
|
11
|
-
const stats = ConsoleStore.store.process
|
|
12
|
-
return (
|
|
13
|
-
<Shell.Shell prefix={ConsoleStore.store.prefix} active="system">
|
|
14
|
-
<div style="display:flex;flex-direction:column;flex:1;overflow:hidden">
|
|
15
|
-
<div class="tab-header">System</div>
|
|
16
|
-
<div id="system-container" class="tab-body">
|
|
17
|
-
{stats ? (
|
|
18
|
-
<System.SystemStatsView stats={stats} />
|
|
19
|
-
) : (
|
|
20
|
-
<div class="empty">Waiting for system data...</div>
|
|
21
|
-
)}
|
|
22
|
-
</div>
|
|
23
|
-
<div data-init={`@get('${ConsoleStore.store.prefix}/system')`} />
|
|
24
|
-
</div>
|
|
25
|
-
</Shell.Shell>
|
|
26
|
-
)
|
|
27
|
-
}),
|
|
28
|
-
Route.sse(
|
|
29
|
-
Stream.fromPubSub(ConsoleStore.store.events).pipe(
|
|
30
|
-
Stream.filter((e) => e._tag === "ProcessSnapshot"),
|
|
31
|
-
Stream.map((e) => {
|
|
32
|
-
const html = HyperHtml.renderToString(<System.SystemStatsView stats={e.stats} />).replace(
|
|
33
|
-
/\n/g,
|
|
34
|
-
"",
|
|
35
|
-
)
|
|
36
|
-
return {
|
|
37
|
-
event: "datastar-patch-elements",
|
|
38
|
-
data: `selector #system-container\nmode inner\nelements ${html}`,
|
|
39
|
-
}
|
|
40
|
-
}),
|
|
41
|
-
),
|
|
42
|
-
),
|
|
43
|
-
)
|
|
@@ -1,22 +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 Shell from "../ui/Shell.tsx"
|
|
7
|
-
import * as Traces from "../ui/Traces.tsx"
|
|
8
|
-
|
|
9
|
-
export default Route.get(
|
|
10
|
-
RouteSchema.schemaPathParams(Schema.Struct({ id: Schema.String })),
|
|
11
|
-
HyperRoute.html(function* (ctx) {
|
|
12
|
-
const allSpans = ConsoleStore.store.spans.toArray()
|
|
13
|
-
const groups = Traces.groupByTraceId(allSpans)
|
|
14
|
-
const spans = groups.get(ctx.pathParams.id) ?? []
|
|
15
|
-
|
|
16
|
-
return (
|
|
17
|
-
<Shell.Shell prefix={ConsoleStore.store.prefix} active="traces">
|
|
18
|
-
<Traces.TraceDetail prefix={ConsoleStore.store.prefix} spans={spans} />
|
|
19
|
-
</Shell.Shell>
|
|
20
|
-
)
|
|
21
|
-
}),
|
|
22
|
-
)
|
|
@@ -1,81 +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 Shell from "../../ui/Shell.tsx"
|
|
7
|
-
import * as Traces from "../../ui/Traces.tsx"
|
|
8
|
-
|
|
9
|
-
const prefix = ConsoleStore.store.prefix
|
|
10
|
-
|
|
11
|
-
function collectNames(): Array<string> {
|
|
12
|
-
const names = new Set<string>()
|
|
13
|
-
for (const span of ConsoleStore.store.spans.toArray()) {
|
|
14
|
-
if (span.name) names.add(span.name)
|
|
15
|
-
}
|
|
16
|
-
return Array.from(names).sort()
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
function filterSpans(url: URL): Array<ConsoleStore.ConsoleSpan> {
|
|
20
|
-
const search = url.searchParams.get("traceSearch") || ""
|
|
21
|
-
let spans = ConsoleStore.store.spans.toArray()
|
|
22
|
-
if (search) {
|
|
23
|
-
const lower = search.toLowerCase()
|
|
24
|
-
spans = spans.filter((s) => s.name.toLowerCase().startsWith(lower))
|
|
25
|
-
}
|
|
26
|
-
return spans
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export default Route.get(
|
|
30
|
-
HyperRoute.html(function* (ctx) {
|
|
31
|
-
const url = new URL(ctx.request.url)
|
|
32
|
-
const spans = filterSpans(url)
|
|
33
|
-
const names = collectNames()
|
|
34
|
-
|
|
35
|
-
return (
|
|
36
|
-
<Shell.Shell prefix={prefix} active="traces">
|
|
37
|
-
<form
|
|
38
|
-
data-signals={{ traceSearch: "" }}
|
|
39
|
-
style="display:flex;flex-direction:column;flex:1;overflow:hidden"
|
|
40
|
-
>
|
|
41
|
-
<div class="tab-header">Traces</div>
|
|
42
|
-
<div class="filter-bar">
|
|
43
|
-
<input
|
|
44
|
-
type="text"
|
|
45
|
-
name="traceSearch"
|
|
46
|
-
placeholder="Search trace name..."
|
|
47
|
-
list="trace-names"
|
|
48
|
-
data-bind:traceSearch
|
|
49
|
-
data-on:input={(c) => c.actions.get(location.href, { contentType: "form" })}
|
|
50
|
-
/>
|
|
51
|
-
<datalist id="trace-names">
|
|
52
|
-
{names.map((n) => (
|
|
53
|
-
<option value={n} />
|
|
54
|
-
))}
|
|
55
|
-
</datalist>
|
|
56
|
-
</div>
|
|
57
|
-
<div id="traces-container" class="tab-body">
|
|
58
|
-
<Traces.TraceGroups spans={spans} />
|
|
59
|
-
</div>
|
|
60
|
-
</form>
|
|
61
|
-
<div data-init={`@get('${prefix}/traces')`} />
|
|
62
|
-
</Shell.Shell>
|
|
63
|
-
)
|
|
64
|
-
}),
|
|
65
|
-
Route.sse(
|
|
66
|
-
Stream.fromPubSub(ConsoleStore.store.events).pipe(
|
|
67
|
-
Stream.filter((e) => e._tag === "SpanStart" || e._tag === "SpanEnd"),
|
|
68
|
-
Stream.map(() => {
|
|
69
|
-
const spans = ConsoleStore.store.spans.toArray()
|
|
70
|
-
const html = HyperHtml.renderToString(<Traces.TraceGroups spans={spans} />).replace(
|
|
71
|
-
/\n/g,
|
|
72
|
-
"",
|
|
73
|
-
)
|
|
74
|
-
return {
|
|
75
|
-
event: "datastar-patch-elements",
|
|
76
|
-
data: `selector #traces-container\nmode inner\nelements ${html}`,
|
|
77
|
-
}
|
|
78
|
-
}),
|
|
79
|
-
),
|
|
80
|
-
),
|
|
81
|
-
)
|
|
@@ -1,30 +0,0 @@
|
|
|
1
|
-
import * as Route from "../../Route.ts"
|
|
2
|
-
import errorsRoute from "./errors/route.tsx"
|
|
3
|
-
import fiberDetailRoute from "./fiberDetail.tsx"
|
|
4
|
-
import fibersRoute from "./fibers/route.tsx"
|
|
5
|
-
import gitRoute from "./git/route.tsx"
|
|
6
|
-
import layout from "./layout.tsx"
|
|
7
|
-
import logsRoute from "./logs/route.tsx"
|
|
8
|
-
import metricsRoute from "./metrics/route.tsx"
|
|
9
|
-
import systemRoute from "./system/route.tsx"
|
|
10
|
-
import rootRoute from "./route.tsx"
|
|
11
|
-
import routesRoute from "./routes/route.tsx"
|
|
12
|
-
import servicesRoute from "./services/route.tsx"
|
|
13
|
-
import traceDetailRoute from "./traceDetail.tsx"
|
|
14
|
-
import tracesRoute from "./traces/route.tsx"
|
|
15
|
-
|
|
16
|
-
export default Route.tree({
|
|
17
|
-
"*": layout,
|
|
18
|
-
"/": rootRoute,
|
|
19
|
-
"/traces": tracesRoute,
|
|
20
|
-
"/traces/:id": traceDetailRoute,
|
|
21
|
-
"/metrics": metricsRoute,
|
|
22
|
-
"/logs": logsRoute,
|
|
23
|
-
"/errors": errorsRoute,
|
|
24
|
-
"/fibers": fibersRoute,
|
|
25
|
-
"/fibers/:id": fiberDetailRoute,
|
|
26
|
-
"/routes": routesRoute,
|
|
27
|
-
"/system": systemRoute,
|
|
28
|
-
"/services": servicesRoute,
|
|
29
|
-
"/git": gitRoute,
|
|
30
|
-
})
|
|
@@ -1,76 +0,0 @@
|
|
|
1
|
-
import * as ConsoleStore from "../ConsoleStore.ts"
|
|
2
|
-
|
|
3
|
-
export function ErrorLine({ error }: { error: ConsoleStore.ConsoleError }) {
|
|
4
|
-
const time = error.date.toLocaleTimeString("en", {
|
|
5
|
-
hour12: false,
|
|
6
|
-
hour: "2-digit",
|
|
7
|
-
minute: "2-digit",
|
|
8
|
-
second: "2-digit",
|
|
9
|
-
})
|
|
10
|
-
const firstLine = error.prettyPrint.split("\n")[0] ?? ""
|
|
11
|
-
const tags = error.details.map((d) => d.tag).filter(Boolean)
|
|
12
|
-
|
|
13
|
-
const allSpans = error.details.map((d) => d.span).filter(Boolean)
|
|
14
|
-
const allProps = error.details.flatMap((d) => Object.entries(d.properties))
|
|
15
|
-
|
|
16
|
-
return (
|
|
17
|
-
<details style="border-bottom:1px solid #1e293b">
|
|
18
|
-
<summary style="display:flex;align-items:center;gap:8px;padding:6px 12px;cursor:pointer;font-size:12px;font-family:monospace">
|
|
19
|
-
<span style="color:#6b7280;flex-shrink:0">{time}</span>
|
|
20
|
-
<span style="color:#fca5a5">{firstLine}</span>
|
|
21
|
-
</summary>
|
|
22
|
-
<div style="padding:4px 12px 10px;font-size:12px;font-family:monospace">
|
|
23
|
-
{tags.length > 0 && (
|
|
24
|
-
<div style="display:flex;flex-wrap:wrap;gap:4px 12px;margin-bottom:6px">
|
|
25
|
-
{tags.map((t) => (
|
|
26
|
-
<div>
|
|
27
|
-
<span style="color:#64748b">tag </span>
|
|
28
|
-
<span
|
|
29
|
-
style="color:#fca5a5;text-decoration:underline;cursor:copy"
|
|
30
|
-
data-on:click={`(e) => { e.signals.errorTag.value = '${t}'; e.actions.get(location.href, { contentType: 'form' }) }`}
|
|
31
|
-
>
|
|
32
|
-
{t}
|
|
33
|
-
</span>
|
|
34
|
-
</div>
|
|
35
|
-
))}
|
|
36
|
-
</div>
|
|
37
|
-
)}
|
|
38
|
-
{allSpans.length > 0 && (
|
|
39
|
-
<div style="display:flex;flex-wrap:wrap;gap:4px 12px;margin-bottom:6px">
|
|
40
|
-
{allSpans.map((s) => (
|
|
41
|
-
<div>
|
|
42
|
-
<span style="color:#64748b">span </span>
|
|
43
|
-
<span style="color:#818cf8">{s}</span>
|
|
44
|
-
</div>
|
|
45
|
-
))}
|
|
46
|
-
</div>
|
|
47
|
-
)}
|
|
48
|
-
{allProps.length > 0 && (
|
|
49
|
-
<div style="display:flex;flex-wrap:wrap;gap:4px 12px;margin-bottom:6px">
|
|
50
|
-
{allProps.map(([k, v]) => (
|
|
51
|
-
<div>
|
|
52
|
-
<span style="color:#64748b">{k}</span>
|
|
53
|
-
<span style="color:#4b5563">=</span>
|
|
54
|
-
<span style="color:#e2e8f0">
|
|
55
|
-
{typeof v === "object" ? JSON.stringify(v) : String(v)}
|
|
56
|
-
</span>
|
|
57
|
-
</div>
|
|
58
|
-
))}
|
|
59
|
-
</div>
|
|
60
|
-
)}
|
|
61
|
-
<div style="margin-bottom:6px">
|
|
62
|
-
<span style="color:#64748b">fiber </span>
|
|
63
|
-
<a
|
|
64
|
-
href={`${ConsoleStore.store.prefix}/fibers/${error.fiberId.replace("#", "")}`}
|
|
65
|
-
style="color:#9ca3af;text-decoration:none"
|
|
66
|
-
>
|
|
67
|
-
{error.fiberId}
|
|
68
|
-
</a>
|
|
69
|
-
</div>
|
|
70
|
-
<pre style="color:#9ca3af;font-size:11px;padding:0;margin:0;white-space:pre-wrap;word-break:break-all">
|
|
71
|
-
{error.prettyPrint}
|
|
72
|
-
</pre>
|
|
73
|
-
</div>
|
|
74
|
-
</details>
|
|
75
|
-
)
|
|
76
|
-
}
|
|
@@ -1,321 +0,0 @@
|
|
|
1
|
-
import * as ConsoleStore from "../ConsoleStore.ts"
|
|
2
|
-
import * as Logs from "./Logs.tsx"
|
|
3
|
-
|
|
4
|
-
function formatDuration(ms: number | undefined): string {
|
|
5
|
-
if (ms == null) return "..."
|
|
6
|
-
if (ms < 1) return `${(ms * 1000).toFixed(0)}µs`
|
|
7
|
-
if (ms < 1000) return `${ms.toFixed(1)}ms`
|
|
8
|
-
return `${(ms / 1000).toFixed(2)}s`
|
|
9
|
-
}
|
|
10
|
-
|
|
11
|
-
function KeyValue({ label, value }: { label: string; value: string | undefined | null }) {
|
|
12
|
-
if (value == null) return null
|
|
13
|
-
return (
|
|
14
|
-
<div style="display:flex;gap:8px;padding:4px 0;border-bottom:1px solid #1e293b;font-size:12px">
|
|
15
|
-
<span style="color:#64748b;min-width:120px">{label}</span>
|
|
16
|
-
<span style="color:#e2e8f0;font-family:monospace;word-break:break-all">{value}</span>
|
|
17
|
-
</div>
|
|
18
|
-
)
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function StatusBadge({ status }: { status: string }) {
|
|
22
|
-
const bg = status === "ok" ? "#166534" : status === "error" ? "#7f1d1d" : "#713f12"
|
|
23
|
-
const fg = status === "ok" ? "#4ade80" : status === "error" ? "#fca5a5" : "#fde047"
|
|
24
|
-
return (
|
|
25
|
-
<span style={`font-size:11px;padding:2px 8px;border-radius:4px;background:${bg};color:${fg}`}>
|
|
26
|
-
{status}
|
|
27
|
-
</span>
|
|
28
|
-
)
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
export interface FiberSummary {
|
|
32
|
-
readonly id: string
|
|
33
|
-
logCount: number
|
|
34
|
-
spanCount: number
|
|
35
|
-
lastSeen: Date | undefined
|
|
36
|
-
alive: "alive" | "dead" | "unknown"
|
|
37
|
-
readonly levels: Set<string>
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
export function getParentChain(fiberId: string, fiberParents: Map<string, string>): Array<string> {
|
|
41
|
-
const chain: Array<string> = []
|
|
42
|
-
const visited = new Set<string>()
|
|
43
|
-
let current = fiberParents.get(fiberId)
|
|
44
|
-
while (current && !visited.has(current)) {
|
|
45
|
-
chain.push(current)
|
|
46
|
-
visited.add(current)
|
|
47
|
-
current = fiberParents.get(current)
|
|
48
|
-
}
|
|
49
|
-
return chain.reverse()
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
export function collectFibers(
|
|
53
|
-
logs: Array<ConsoleStore.ConsoleLog>,
|
|
54
|
-
spans: Array<ConsoleStore.ConsoleSpan>,
|
|
55
|
-
): Array<FiberSummary> {
|
|
56
|
-
const map = new Map<string, FiberSummary>()
|
|
57
|
-
const counter = ConsoleStore.fiberIdCounter()
|
|
58
|
-
const now = Date.now()
|
|
59
|
-
|
|
60
|
-
for (const log of logs) {
|
|
61
|
-
let fiber = map.get(log.fiberId)
|
|
62
|
-
if (!fiber) {
|
|
63
|
-
fiber = {
|
|
64
|
-
id: log.fiberId,
|
|
65
|
-
logCount: 0,
|
|
66
|
-
spanCount: 0,
|
|
67
|
-
lastSeen: undefined,
|
|
68
|
-
alive: "unknown",
|
|
69
|
-
levels: new Set(),
|
|
70
|
-
}
|
|
71
|
-
map.set(log.fiberId, fiber)
|
|
72
|
-
}
|
|
73
|
-
fiber.logCount++
|
|
74
|
-
fiber.levels.add(log.level)
|
|
75
|
-
if (!fiber.lastSeen || log.date > fiber.lastSeen) {
|
|
76
|
-
fiber.lastSeen = log.date
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
for (const span of spans) {
|
|
81
|
-
const fiberId = span.attributes["fiber.id"] as string | undefined
|
|
82
|
-
if (!fiberId) continue
|
|
83
|
-
let fiber = map.get(fiberId)
|
|
84
|
-
if (!fiber) {
|
|
85
|
-
fiber = {
|
|
86
|
-
id: fiberId,
|
|
87
|
-
logCount: 0,
|
|
88
|
-
spanCount: 0,
|
|
89
|
-
lastSeen: undefined,
|
|
90
|
-
alive: "unknown",
|
|
91
|
-
levels: new Set(),
|
|
92
|
-
}
|
|
93
|
-
map.set(fiberId, fiber)
|
|
94
|
-
}
|
|
95
|
-
fiber.spanCount++
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
for (const fiber of map.values()) {
|
|
99
|
-
const num = parseInt(fiber.id.replace("#", ""), 10)
|
|
100
|
-
if (!isNaN(num)) {
|
|
101
|
-
if (fiber.lastSeen && now - fiber.lastSeen.getTime() < 5000) {
|
|
102
|
-
fiber.alive = "alive"
|
|
103
|
-
} else if (num < counter) {
|
|
104
|
-
fiber.alive = "dead"
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
return Array.from(map.values()).sort((a, b) => {
|
|
110
|
-
const na = parseInt(a.id.replace("#", ""), 10)
|
|
111
|
-
const nb = parseInt(b.id.replace("#", ""), 10)
|
|
112
|
-
return nb - na
|
|
113
|
-
})
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
function FiberRow({ fiber, prefix }: { fiber: FiberSummary; prefix: string }) {
|
|
117
|
-
const aliveColor =
|
|
118
|
-
fiber.alive === "alive" ? "#4ade80" : fiber.alive === "dead" ? "#ef4444" : "#94a3b8"
|
|
119
|
-
const aliveBg =
|
|
120
|
-
fiber.alive === "alive" ? "#166534" : fiber.alive === "dead" ? "#7f1d1d" : "#334155"
|
|
121
|
-
const lastSeen = fiber.lastSeen
|
|
122
|
-
? fiber.lastSeen.toLocaleTimeString("en", {
|
|
123
|
-
hour12: false,
|
|
124
|
-
hour: "2-digit",
|
|
125
|
-
minute: "2-digit",
|
|
126
|
-
second: "2-digit",
|
|
127
|
-
})
|
|
128
|
-
: "—"
|
|
129
|
-
|
|
130
|
-
return (
|
|
131
|
-
<a
|
|
132
|
-
href={`${prefix}/fibers/${fiber.id.replace("#", "")}`}
|
|
133
|
-
style="display:flex;align-items:center;gap:12px;padding:8px 12px;border-bottom:1px solid #1e293b;text-decoration:none;transition:background .1s"
|
|
134
|
-
onmouseover="this.style.background='#1e293b'"
|
|
135
|
-
onmouseout="this.style.background='transparent'"
|
|
136
|
-
>
|
|
137
|
-
<span style="color:#e2e8f0;font-family:monospace;font-size:13px;font-weight:600;min-width:60px">
|
|
138
|
-
{fiber.id}
|
|
139
|
-
</span>
|
|
140
|
-
<span
|
|
141
|
-
style={`font-size:10px;padding:2px 8px;border-radius:4px;background:${aliveBg};color:${aliveColor}`}
|
|
142
|
-
>
|
|
143
|
-
{fiber.alive}
|
|
144
|
-
</span>
|
|
145
|
-
<span style="color:#94a3b8;font-size:12px">
|
|
146
|
-
{fiber.spanCount} span{fiber.spanCount !== 1 ? "s" : ""}
|
|
147
|
-
</span>
|
|
148
|
-
<span style="color:#94a3b8;font-size:12px">
|
|
149
|
-
{fiber.logCount} log{fiber.logCount !== 1 ? "s" : ""}
|
|
150
|
-
</span>
|
|
151
|
-
{fiber.levels.has("ERROR") && (
|
|
152
|
-
<span style="font-size:10px;padding:1px 6px;border-radius:4px;background:#7f1d1d;color:#fca5a5">
|
|
153
|
-
ERROR
|
|
154
|
-
</span>
|
|
155
|
-
)}
|
|
156
|
-
{fiber.levels.has("WARNING") && (
|
|
157
|
-
<span style="font-size:10px;padding:1px 6px;border-radius:4px;background:#713f12;color:#fde047">
|
|
158
|
-
WARN
|
|
159
|
-
</span>
|
|
160
|
-
)}
|
|
161
|
-
<span style="color:#6b7280;font-size:11px;margin-left:auto;font-family:monospace">
|
|
162
|
-
{lastSeen}
|
|
163
|
-
</span>
|
|
164
|
-
</a>
|
|
165
|
-
)
|
|
166
|
-
}
|
|
167
|
-
|
|
168
|
-
export function FiberList({ fibers, prefix }: { fibers: Array<FiberSummary>; prefix: string }) {
|
|
169
|
-
if (fibers.length === 0) {
|
|
170
|
-
return <div class="empty">Waiting for fibers...</div>
|
|
171
|
-
}
|
|
172
|
-
return (
|
|
173
|
-
<>
|
|
174
|
-
{fibers.map((f) => (
|
|
175
|
-
<FiberRow fiber={f} prefix={prefix} />
|
|
176
|
-
))}
|
|
177
|
-
</>
|
|
178
|
-
)
|
|
179
|
-
}
|
|
180
|
-
|
|
181
|
-
export function FiberDetail({
|
|
182
|
-
prefix,
|
|
183
|
-
fiberId,
|
|
184
|
-
logs,
|
|
185
|
-
spans,
|
|
186
|
-
alive,
|
|
187
|
-
parents,
|
|
188
|
-
context,
|
|
189
|
-
}: {
|
|
190
|
-
prefix: string
|
|
191
|
-
fiberId: string
|
|
192
|
-
logs: Array<ConsoleStore.ConsoleLog>
|
|
193
|
-
spans: Array<ConsoleStore.ConsoleSpan>
|
|
194
|
-
alive: "alive" | "dead" | "unknown"
|
|
195
|
-
parents: Array<string>
|
|
196
|
-
context: ConsoleStore.FiberContext | undefined
|
|
197
|
-
}) {
|
|
198
|
-
const aliveColor = alive === "alive" ? "#4ade80" : alive === "dead" ? "#ef4444" : "#94a3b8"
|
|
199
|
-
const aliveBg = alive === "alive" ? "#166534" : alive === "dead" ? "#7f1d1d" : "#334155"
|
|
200
|
-
|
|
201
|
-
return (
|
|
202
|
-
<>
|
|
203
|
-
<div class="tab-header">
|
|
204
|
-
<div style="display:flex;align-items:center;gap:8px;margin-bottom:4px">
|
|
205
|
-
<a href={`${prefix}/fibers`} style="color:#64748b;text-decoration:none;font-size:12px">
|
|
206
|
-
Fibers
|
|
207
|
-
</a>
|
|
208
|
-
<span style="color:#475569">/</span>
|
|
209
|
-
<span style="color:#e2e8f0;font-size:13px;font-family:monospace">{fiberId}</span>
|
|
210
|
-
<span
|
|
211
|
-
style={`font-size:11px;padding:2px 8px;border-radius:4px;background:${aliveBg};color:${aliveColor}`}
|
|
212
|
-
>
|
|
213
|
-
{alive}
|
|
214
|
-
</span>
|
|
215
|
-
</div>
|
|
216
|
-
<div style="display:flex;gap:16px;font-size:12px;color:#94a3b8">
|
|
217
|
-
<span>
|
|
218
|
-
{logs.length} log{logs.length !== 1 ? "s" : ""}
|
|
219
|
-
</span>
|
|
220
|
-
<span>
|
|
221
|
-
{spans.length} span{spans.length !== 1 ? "s" : ""}
|
|
222
|
-
</span>
|
|
223
|
-
</div>
|
|
224
|
-
</div>
|
|
225
|
-
<div class="tab-body">
|
|
226
|
-
{parents.length > 0 && (
|
|
227
|
-
<div style="padding:8px 16px">
|
|
228
|
-
<div style="color:#94a3b8;font-size:12px;font-weight:600;margin-bottom:8px">
|
|
229
|
-
Parents
|
|
230
|
-
</div>
|
|
231
|
-
<div style="display:flex;gap:6px;flex-wrap:wrap">
|
|
232
|
-
{parents.map((id) => (
|
|
233
|
-
<a
|
|
234
|
-
href={`${prefix}/fibers/${id.replace("#", "")}`}
|
|
235
|
-
style="color:#38bdf8;font-family:monospace;font-size:13px;text-decoration:none;padding:4px 10px;background:#111827;border:1px solid #1e293b;border-radius:6px"
|
|
236
|
-
>
|
|
237
|
-
{id}
|
|
238
|
-
</a>
|
|
239
|
-
))}
|
|
240
|
-
</div>
|
|
241
|
-
</div>
|
|
242
|
-
)}
|
|
243
|
-
|
|
244
|
-
{context &&
|
|
245
|
-
(context.spanName || context.traceId || Object.keys(context.annotations).length > 0) && (
|
|
246
|
-
<div style="padding:8px 16px">
|
|
247
|
-
<div style="color:#94a3b8;font-size:12px;font-weight:600;margin-bottom:8px">
|
|
248
|
-
Context
|
|
249
|
-
</div>
|
|
250
|
-
<div style="background:#111827;border:1px solid #1e293b;border-radius:6px;padding:8px 12px">
|
|
251
|
-
{context.spanName && <KeyValue label="Span" value={context.spanName} />}
|
|
252
|
-
{context.traceId && (
|
|
253
|
-
<div style="display:flex;gap:8px;padding:4px 0;border-bottom:1px solid #1e293b;font-size:12px">
|
|
254
|
-
<span style="color:#64748b;min-width:120px">Trace</span>
|
|
255
|
-
<a
|
|
256
|
-
href={`${prefix}/traces/${context.traceId}`}
|
|
257
|
-
style="color:#38bdf8;font-family:monospace;word-break:break-all;text-decoration:none"
|
|
258
|
-
>
|
|
259
|
-
{context.traceId}
|
|
260
|
-
</a>
|
|
261
|
-
</div>
|
|
262
|
-
)}
|
|
263
|
-
{Object.entries(context.annotations).map(([k, v]) => (
|
|
264
|
-
<KeyValue
|
|
265
|
-
label={k}
|
|
266
|
-
value={typeof v === "object" ? JSON.stringify(v) : String(v)}
|
|
267
|
-
/>
|
|
268
|
-
))}
|
|
269
|
-
</div>
|
|
270
|
-
</div>
|
|
271
|
-
)}
|
|
272
|
-
|
|
273
|
-
{spans.length > 0 && (
|
|
274
|
-
<div style="padding:8px 16px">
|
|
275
|
-
<div style="color:#94a3b8;font-size:12px;font-weight:600;margin-bottom:8px">Spans</div>
|
|
276
|
-
{spans.map((s) => {
|
|
277
|
-
const stacktrace = s.attributes["code.stacktrace"] as string | undefined
|
|
278
|
-
return (
|
|
279
|
-
<div style="margin-bottom:6px;background:#111827;border:1px solid #1e293b;border-radius:6px;padding:8px 12px">
|
|
280
|
-
<div style="display:flex;align-items:center;gap:8px;margin-bottom:4px">
|
|
281
|
-
<a
|
|
282
|
-
href={`${prefix}/traces/${s.traceId}`}
|
|
283
|
-
style="color:#38bdf8;font-family:monospace;font-size:13px;text-decoration:none"
|
|
284
|
-
>
|
|
285
|
-
{s.name}
|
|
286
|
-
</a>
|
|
287
|
-
<StatusBadge status={s.status} />
|
|
288
|
-
<span style="color:#64748b;font-size:11px;margin-left:auto;font-family:monospace">
|
|
289
|
-
{formatDuration(s.durationMs)}
|
|
290
|
-
</span>
|
|
291
|
-
</div>
|
|
292
|
-
<KeyValue label="Trace" value={s.traceId} />
|
|
293
|
-
<KeyValue label="Kind" value={s.kind} />
|
|
294
|
-
{stacktrace && <KeyValue label="Source" value={stacktrace} />}
|
|
295
|
-
{Object.entries(s.attributes)
|
|
296
|
-
.filter(([k]) => k !== "code.stacktrace")
|
|
297
|
-
.map(([k, v]) => (
|
|
298
|
-
<KeyValue label={k} value={String(v)} />
|
|
299
|
-
))}
|
|
300
|
-
</div>
|
|
301
|
-
)
|
|
302
|
-
})}
|
|
303
|
-
</div>
|
|
304
|
-
)}
|
|
305
|
-
|
|
306
|
-
{logs.length > 0 && (
|
|
307
|
-
<div style="padding:8px 16px">
|
|
308
|
-
<div style="color:#94a3b8;font-size:12px;font-weight:600;margin-bottom:8px">Logs</div>
|
|
309
|
-
{logs.map((l) => (
|
|
310
|
-
<Logs.LogLine log={l} />
|
|
311
|
-
))}
|
|
312
|
-
</div>
|
|
313
|
-
)}
|
|
314
|
-
|
|
315
|
-
{logs.length === 0 && spans.length === 0 && (
|
|
316
|
-
<div class="empty">No data found for fiber {fiberId}</div>
|
|
317
|
-
)}
|
|
318
|
-
</div>
|
|
319
|
-
</>
|
|
320
|
-
)
|
|
321
|
-
}
|