@k2b/cloud 0.11.0 → 0.12.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 +5 -5
- package/src/ai/store.ts +4 -4
- package/src/browser/live-websocket.ts +75 -10
- package/src/browser/mermaid.ts +202 -0
- package/src/services/account-lifecycle/index.ts +2 -2
- package/src/services/accounts/app.ts +1 -1
- package/src/services/logging/index.ts +3 -3
- package/src/services/logging/trace.ts +1 -1
- package/src/services/notifications/batches.ts +1 -1
- package/src/services/notifications/index.ts +4 -4
- package/src/services/service-account-credentials.ts +1 -1
- package/src/shared/markdown/client.ts +3 -33
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@k2b/cloud",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.12.0",
|
|
4
4
|
"description": "Application platform library for independently deployed Hono and SolidJS services behind a dynamic gateway.",
|
|
5
5
|
"license": "AGPL-3.0-or-later",
|
|
6
6
|
"repository": {
|
|
@@ -31,6 +31,7 @@
|
|
|
31
31
|
"./browser": "./src/server/api-client.ts",
|
|
32
32
|
"./browser/app-approval": "./src/browser/app-approval.ts",
|
|
33
33
|
"./browser/live": "./src/browser/live-websocket.ts",
|
|
34
|
+
"./browser/mermaid": "./src/browser/mermaid.ts",
|
|
34
35
|
"./browser/notifications": "./src/browser/notifications.ts",
|
|
35
36
|
"./browser/resource-clipboard": "./src/browser/resource-clipboard.ts",
|
|
36
37
|
"./browser/commands": "./src/browser/commands.ts",
|
|
@@ -84,8 +85,7 @@
|
|
|
84
85
|
"./styles/global.css": "./src/styles/global.css"
|
|
85
86
|
},
|
|
86
87
|
"scripts": {
|
|
87
|
-
"test": "bun test --isolate --path-ignore-patterns '**/*.behavior.test
|
|
88
|
-
"test:dom": "bun test --isolate --conditions=browser --preload ../ui/test/solid-dom-preload.ts behavior.test.tsx dom.test.tsx",
|
|
88
|
+
"test": "bun test --isolate --path-ignore-patterns '**/*.behavior.test.*'",
|
|
89
89
|
"typecheck": "tsc -p tsconfig.json --noEmit --pretty false"
|
|
90
90
|
},
|
|
91
91
|
"dependencies": {
|
|
@@ -98,7 +98,7 @@
|
|
|
98
98
|
"@tailwindcss/typography": "0.5.20",
|
|
99
99
|
"@k2b/nessi": "0.12.1",
|
|
100
100
|
"@k2b/ssr": "0.14.0",
|
|
101
|
-
"@k2b/ui": "0.5.
|
|
101
|
+
"@k2b/ui": "0.5.1",
|
|
102
102
|
"@k2b/stdlib": "0.25.0",
|
|
103
103
|
"@k2b/sync": "6.5.0",
|
|
104
104
|
"@nats-io/transport-node": "3.4.0",
|
|
@@ -109,7 +109,7 @@
|
|
|
109
109
|
"katex": "0.18.7",
|
|
110
110
|
"liquidjs": "10.29.0",
|
|
111
111
|
"marked": "17.0.6",
|
|
112
|
-
"mermaid": "
|
|
112
|
+
"mermaid": "12.0.0",
|
|
113
113
|
"nodemailer": "10.0.10",
|
|
114
114
|
"postcss": "8.5.28",
|
|
115
115
|
"reflect-metadata": "0.2.2",
|
package/src/ai/store.ts
CHANGED
|
@@ -1095,10 +1095,10 @@ export const aiConversations: AiConversationService = {
|
|
|
1095
1095
|
const refs = input.refs ?? [];
|
|
1096
1096
|
const rows = await withConversationSearchBackend(query, async (backend) => {
|
|
1097
1097
|
const order = query
|
|
1098
|
-
? sql`${conversationSearchRank(backend, query)} DESC, conversation.pinned_at DESC NULLS LAST, conversation.updated_at DESC, conversation.created_at DESC`
|
|
1098
|
+
? sql`${conversationSearchRank(backend, query)} DESC, conversation.pinned_at DESC NULLS LAST, conversation.updated_at DESC, conversation.created_at DESC, conversation.id DESC`
|
|
1099
1099
|
: input.done === true
|
|
1100
1100
|
? sql`conversation.last_used_at DESC, conversation.id DESC`
|
|
1101
|
-
: sql`conversation.pinned_at DESC NULLS LAST, conversation.updated_at DESC, conversation.created_at DESC`;
|
|
1101
|
+
: sql`conversation.pinned_at DESC NULLS LAST, conversation.updated_at DESC, conversation.created_at DESC, conversation.id DESC`;
|
|
1102
1102
|
return sql<ConversationRow[]>`
|
|
1103
1103
|
SELECT
|
|
1104
1104
|
conversation.*,
|
|
@@ -1198,10 +1198,10 @@ export const aiConversations: AiConversationService = {
|
|
|
1198
1198
|
const { page, perPage, offset } = sanitizePagination(input);
|
|
1199
1199
|
const rows = await withConversationSearchBackend(query, async (backend) => {
|
|
1200
1200
|
const order = query
|
|
1201
|
-
? sql`${conversationSearchRank(backend, query)} DESC, conversation.pinned_at DESC NULLS LAST, conversation.updated_at DESC, conversation.created_at DESC`
|
|
1201
|
+
? sql`${conversationSearchRank(backend, query)} DESC, conversation.pinned_at DESC NULLS LAST, conversation.updated_at DESC, conversation.created_at DESC, conversation.id DESC`
|
|
1202
1202
|
: input.done === true
|
|
1203
1203
|
? sql`conversation.last_used_at DESC, conversation.id DESC`
|
|
1204
|
-
: sql`conversation.pinned_at DESC NULLS LAST, conversation.updated_at DESC, conversation.created_at DESC`;
|
|
1204
|
+
: sql`conversation.pinned_at DESC NULLS LAST, conversation.updated_at DESC, conversation.created_at DESC, conversation.id DESC`;
|
|
1205
1205
|
return sql<ConversationRow[]>`
|
|
1206
1206
|
SELECT
|
|
1207
1207
|
conversation.*,
|
|
@@ -52,6 +52,16 @@ const DEFAULT_RECONNECT = {
|
|
|
52
52
|
jitterMs: 250,
|
|
53
53
|
} as const;
|
|
54
54
|
|
|
55
|
+
/**
|
|
56
|
+
* Deadline for one opening handshake. The handshake is a single HTTP upgrade
|
|
57
|
+
* round trip (the gateway accepts it before reaching the application), so a
|
|
58
|
+
* healthy attempt finishes far sooner. Browsers can otherwise leave a socket in
|
|
59
|
+
* `CONNECTING` indefinitely after sleep or a network change. Matching the
|
|
60
|
+
* longest default reconnect delay means a stalled attempt never blocks recovery
|
|
61
|
+
* for longer than an ordinary retry.
|
|
62
|
+
*/
|
|
63
|
+
const CONNECT_TIMEOUT_MS = DEFAULT_RECONNECT.maxDelayMs;
|
|
64
|
+
|
|
55
65
|
const defaultCloseError = ({ code, reason }: LiveWebSocketClose): LiveWebSocketError | null => {
|
|
56
66
|
if (code === 1008) return { code: reason || "access_denied", message: "Live access changed or expired." };
|
|
57
67
|
if (code === 1011) return { code: reason || "internal_error", message: "Live updates failed." };
|
|
@@ -72,7 +82,8 @@ const socketUrl = (raw: string): string => {
|
|
|
72
82
|
*
|
|
73
83
|
* The app owns its wire protocol, validation, permissions, and domain updates.
|
|
74
84
|
* This helper owns only browser transport lifecycle: one socket, visibility,
|
|
75
|
-
* reconnect backoff,
|
|
85
|
+
* reconnect backoff, connection deadlines, recovery when the tab or network
|
|
86
|
+
* returns, applied-cursor resume, terminal closes, and disposal.
|
|
76
87
|
*/
|
|
77
88
|
export const createLiveWebSocket = <TMessage>(options: LiveWebSocketOptions<TMessage>): LiveWebSocket => {
|
|
78
89
|
const activity = options.activity ?? "visible";
|
|
@@ -81,6 +92,8 @@ export const createLiveWebSocket = <TMessage>(options: LiveWebSocketOptions<TMes
|
|
|
81
92
|
|
|
82
93
|
let socket: WebSocket | null = null;
|
|
83
94
|
let reconnectTimer: ReturnType<typeof setTimeout> | null = null;
|
|
95
|
+
let connectTimer: ReturnType<typeof setTimeout> | null = null;
|
|
96
|
+
let connectStartedAt = 0;
|
|
84
97
|
let reconnectAttempt = 0;
|
|
85
98
|
let lastAppliedCursor = options.initialCursor ?? null;
|
|
86
99
|
let started = false;
|
|
@@ -120,9 +133,15 @@ export const createLiveWebSocket = <TMessage>(options: LiveWebSocketOptions<TMes
|
|
|
120
133
|
}
|
|
121
134
|
};
|
|
122
135
|
|
|
136
|
+
const clearConnectDeadline = () => {
|
|
137
|
+
if (connectTimer !== null) clearTimeout(connectTimer);
|
|
138
|
+
connectTimer = null;
|
|
139
|
+
};
|
|
140
|
+
|
|
123
141
|
const closeSocket = (code = 1000, reason = "") => {
|
|
124
142
|
const current = socket;
|
|
125
143
|
socket = null;
|
|
144
|
+
clearConnectDeadline();
|
|
126
145
|
if (!current || current.readyState > WebSocket.OPEN) return;
|
|
127
146
|
try {
|
|
128
147
|
current.close(code, reason);
|
|
@@ -135,6 +154,7 @@ export const createLiveWebSocket = <TMessage>(options: LiveWebSocketOptions<TMes
|
|
|
135
154
|
if (terminated) return;
|
|
136
155
|
terminated = true;
|
|
137
156
|
clearReconnect();
|
|
157
|
+
detachRecovery();
|
|
138
158
|
closeSocket(close.code, close.reason);
|
|
139
159
|
setStatus("closed");
|
|
140
160
|
if (!fatalSent) {
|
|
@@ -165,6 +185,13 @@ export const createLiveWebSocket = <TMessage>(options: LiveWebSocketOptions<TMes
|
|
|
165
185
|
}, delay);
|
|
166
186
|
};
|
|
167
187
|
|
|
188
|
+
/** Gives up on an attempt that is still connecting and schedules a retry. */
|
|
189
|
+
const abandonAttempt = (attempt: WebSocket) => {
|
|
190
|
+
if (socket !== attempt) return;
|
|
191
|
+
closeSocket();
|
|
192
|
+
scheduleReconnect();
|
|
193
|
+
};
|
|
194
|
+
|
|
168
195
|
const openSocket = () => {
|
|
169
196
|
if (disposed || terminated || !started || socket) return;
|
|
170
197
|
if (!browserAvailable()) {
|
|
@@ -174,7 +201,7 @@ export const createLiveWebSocket = <TMessage>(options: LiveWebSocketOptions<TMes
|
|
|
174
201
|
if (!isActive()) return;
|
|
175
202
|
|
|
176
203
|
let next: WebSocket;
|
|
177
|
-
setStatus(reconnectAttempt > 0 ? "reconnecting" : "connecting");
|
|
204
|
+
setStatus(reconnectAttempt > 0 || status === "reconnecting" ? "reconnecting" : "connecting");
|
|
178
205
|
try {
|
|
179
206
|
next = new WebSocket(socketUrl(typeof options.url === "function" ? options.url() : options.url));
|
|
180
207
|
} catch (error) {
|
|
@@ -182,9 +209,15 @@ export const createLiveWebSocket = <TMessage>(options: LiveWebSocketOptions<TMes
|
|
|
182
209
|
return;
|
|
183
210
|
}
|
|
184
211
|
socket = next;
|
|
212
|
+
connectStartedAt = Date.now();
|
|
213
|
+
connectTimer = setTimeout(() => {
|
|
214
|
+
connectTimer = null;
|
|
215
|
+
if (next.readyState === WebSocket.CONNECTING) abandonAttempt(next);
|
|
216
|
+
}, CONNECT_TIMEOUT_MS);
|
|
185
217
|
|
|
186
218
|
next.onopen = () => {
|
|
187
219
|
if (socket !== next || disposed || terminated) return;
|
|
220
|
+
clearConnectDeadline();
|
|
188
221
|
try {
|
|
189
222
|
if (!send(options.subscribe(lastAppliedCursor))) throw new Error("Live WebSocket subscription could not be sent");
|
|
190
223
|
setStatus("open");
|
|
@@ -213,6 +246,7 @@ export const createLiveWebSocket = <TMessage>(options: LiveWebSocketOptions<TMes
|
|
|
213
246
|
next.onclose = (event) => {
|
|
214
247
|
if (socket !== next) return;
|
|
215
248
|
socket = null;
|
|
249
|
+
clearConnectDeadline();
|
|
216
250
|
if (disposed || terminated) return;
|
|
217
251
|
const closeError = classifyClose({ code: event.code, reason: event.reason.trim() });
|
|
218
252
|
if (closeError) fatal(closeError, { code: event.code, reason: event.reason });
|
|
@@ -224,22 +258,55 @@ export const createLiveWebSocket = <TMessage>(options: LiveWebSocketOptions<TMes
|
|
|
224
258
|
next.close();
|
|
225
259
|
} catch {
|
|
226
260
|
if (socket === next) {
|
|
227
|
-
|
|
261
|
+
closeSocket();
|
|
228
262
|
scheduleReconnect();
|
|
229
263
|
}
|
|
230
264
|
}
|
|
231
265
|
};
|
|
232
266
|
};
|
|
233
267
|
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
268
|
+
/**
|
|
269
|
+
* Reconnects now when the tab or network returns. Open and closing sockets
|
|
270
|
+
* are left alone; a closing socket's close event still decides between retry
|
|
271
|
+
* and a terminal error. A handshake within its deadline is kept unless the
|
|
272
|
+
* network just returned, because it began on the network that was gone. A
|
|
273
|
+
* stalled handshake or a pending backoff is replaced by an immediate attempt
|
|
274
|
+
* whose backoff starts over.
|
|
275
|
+
*/
|
|
276
|
+
const recover = (event: Event) => {
|
|
277
|
+
if (disposed || terminated || !started || !isActive()) return;
|
|
278
|
+
if (socket) {
|
|
279
|
+
if (socket.readyState !== WebSocket.CONNECTING) return;
|
|
280
|
+
if (event.type !== "online" && Date.now() - connectStartedAt < CONNECT_TIMEOUT_MS) return;
|
|
281
|
+
closeSocket();
|
|
282
|
+
}
|
|
283
|
+
clearReconnect();
|
|
284
|
+
reconnectAttempt = 0;
|
|
285
|
+
openSocket();
|
|
286
|
+
};
|
|
287
|
+
|
|
288
|
+
const syncVisibility = (event: Event) => {
|
|
289
|
+
if (isActive()) recover(event);
|
|
290
|
+
else if (activity === "visible") {
|
|
237
291
|
clearReconnect();
|
|
238
292
|
closeSocket(1000, "inactive");
|
|
239
293
|
setStatus("paused");
|
|
240
294
|
}
|
|
241
295
|
};
|
|
242
296
|
|
|
297
|
+
const attachRecovery = () => {
|
|
298
|
+
document.addEventListener("visibilitychange", syncVisibility);
|
|
299
|
+
window.addEventListener("online", recover);
|
|
300
|
+
window.addEventListener("focus", recover);
|
|
301
|
+
};
|
|
302
|
+
|
|
303
|
+
const detachRecovery = () => {
|
|
304
|
+
if (!started || !browserAvailable()) return;
|
|
305
|
+
document.removeEventListener("visibilitychange", syncVisibility);
|
|
306
|
+
window.removeEventListener("online", recover);
|
|
307
|
+
window.removeEventListener("focus", recover);
|
|
308
|
+
};
|
|
309
|
+
|
|
243
310
|
return {
|
|
244
311
|
connect: () => {
|
|
245
312
|
if (started || disposed || terminated) return;
|
|
@@ -248,7 +315,7 @@ export const createLiveWebSocket = <TMessage>(options: LiveWebSocketOptions<TMes
|
|
|
248
315
|
fatal({ code: "unsupported", message: "Live WebSockets are not available in this browser" });
|
|
249
316
|
return;
|
|
250
317
|
}
|
|
251
|
-
|
|
318
|
+
attachRecovery();
|
|
252
319
|
openSocket();
|
|
253
320
|
},
|
|
254
321
|
markApplied,
|
|
@@ -258,9 +325,7 @@ export const createLiveWebSocket = <TMessage>(options: LiveWebSocketOptions<TMes
|
|
|
258
325
|
if (disposed) return;
|
|
259
326
|
disposed = true;
|
|
260
327
|
clearReconnect();
|
|
261
|
-
|
|
262
|
-
document.removeEventListener("visibilitychange", syncVisibility);
|
|
263
|
-
}
|
|
328
|
+
detachRecovery();
|
|
264
329
|
closeSocket();
|
|
265
330
|
setStatus("closed");
|
|
266
331
|
},
|
|
@@ -0,0 +1,202 @@
|
|
|
1
|
+
import type { MermaidConfig } from "mermaid";
|
|
2
|
+
|
|
3
|
+
/** Series colors shared by pie slices and mindmap branches. */
|
|
4
|
+
const lightSeries = [
|
|
5
|
+
"#dbeafe",
|
|
6
|
+
"#d1fae5",
|
|
7
|
+
"#fef3c7",
|
|
8
|
+
"#ede9fe",
|
|
9
|
+
"#ffe4e6",
|
|
10
|
+
"#cffafe",
|
|
11
|
+
"#ecfccb",
|
|
12
|
+
"#ffedd4",
|
|
13
|
+
"#e0f2fe",
|
|
14
|
+
"#ccfbf1",
|
|
15
|
+
"#fae8ff",
|
|
16
|
+
"#fef9c3",
|
|
17
|
+
];
|
|
18
|
+
const darkSeries = [
|
|
19
|
+
"#1e3a8a",
|
|
20
|
+
"#065f46",
|
|
21
|
+
"#78350f",
|
|
22
|
+
"#4c1d95",
|
|
23
|
+
"#881337",
|
|
24
|
+
"#155e75",
|
|
25
|
+
"#3f6212",
|
|
26
|
+
"#7c2d12",
|
|
27
|
+
"#075985",
|
|
28
|
+
"#115e59",
|
|
29
|
+
"#86198f",
|
|
30
|
+
"#713f12",
|
|
31
|
+
];
|
|
32
|
+
|
|
33
|
+
// Mermaid derives colors from hex values, so these mirror the Cloud tokens
|
|
34
|
+
// (zinc ink, --ui-surface*, --ui-border*) instead of reading CSS variables.
|
|
35
|
+
const light = {
|
|
36
|
+
darkMode: false,
|
|
37
|
+
background: "#ffffff",
|
|
38
|
+
textColor: "#27272a",
|
|
39
|
+
titleColor: "#18181b",
|
|
40
|
+
lineColor: "#71717a",
|
|
41
|
+
primaryColor: "#f4f4f5",
|
|
42
|
+
primaryTextColor: "#18181b",
|
|
43
|
+
primaryBorderColor: "#a1a1aa",
|
|
44
|
+
secondaryColor: "#eff6ff",
|
|
45
|
+
secondaryTextColor: "#18181b",
|
|
46
|
+
secondaryBorderColor: "#93c5fd",
|
|
47
|
+
tertiaryColor: "#fafafa",
|
|
48
|
+
tertiaryTextColor: "#18181b",
|
|
49
|
+
tertiaryBorderColor: "#d4d4d8",
|
|
50
|
+
mainBkg: "#f4f4f5",
|
|
51
|
+
nodeBorder: "#a1a1aa",
|
|
52
|
+
clusterBkg: "#fafafa",
|
|
53
|
+
clusterBorder: "#d4d4d8",
|
|
54
|
+
edgeLabelBackground: "#ffffff",
|
|
55
|
+
noteBkgColor: "#fefce8",
|
|
56
|
+
noteBorderColor: "#eab308",
|
|
57
|
+
noteTextColor: "#27272a",
|
|
58
|
+
actorBkg: "#f4f4f5",
|
|
59
|
+
actorBorder: "#a1a1aa",
|
|
60
|
+
actorTextColor: "#18181b",
|
|
61
|
+
signalColor: "#52525b",
|
|
62
|
+
signalTextColor: "#27272a",
|
|
63
|
+
labelBoxBkgColor: "#f4f4f5",
|
|
64
|
+
labelBoxBorderColor: "#a1a1aa",
|
|
65
|
+
activationBkgColor: "#e4e4e7",
|
|
66
|
+
activationBorderColor: "#a1a1aa",
|
|
67
|
+
attributeBackgroundColorOdd: "#ffffff",
|
|
68
|
+
attributeBackgroundColorEven: "#fafafa",
|
|
69
|
+
sectionBkgColor: "#f4f4f5",
|
|
70
|
+
altSectionBkgColor: "#ffffff",
|
|
71
|
+
sectionBkgColor2: "#fafafa",
|
|
72
|
+
gridColor: "#e4e4e7",
|
|
73
|
+
taskBkgColor: "#dbeafe",
|
|
74
|
+
taskBorderColor: "#3b82f6",
|
|
75
|
+
taskTextColor: "#18181b",
|
|
76
|
+
taskTextLightColor: "#18181b",
|
|
77
|
+
taskTextDarkColor: "#18181b",
|
|
78
|
+
taskTextOutsideColor: "#27272a",
|
|
79
|
+
activeTaskBkgColor: "#bfdbfe",
|
|
80
|
+
activeTaskBorderColor: "#2563eb",
|
|
81
|
+
doneTaskBkgColor: "#e4e4e7",
|
|
82
|
+
doneTaskBorderColor: "#a1a1aa",
|
|
83
|
+
critBkgColor: "#ffe4e6",
|
|
84
|
+
critBorderColor: "#e11d48",
|
|
85
|
+
todayLineColor: "#e11d48",
|
|
86
|
+
pieStrokeColor: "#ffffff",
|
|
87
|
+
pieOuterStrokeColor: "#d4d4d8",
|
|
88
|
+
pieSectionTextColor: "#18181b",
|
|
89
|
+
pieTitleTextColor: "#18181b",
|
|
90
|
+
pieLegendTextColor: "#27272a",
|
|
91
|
+
series: lightSeries,
|
|
92
|
+
seriesText: "#18181b",
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const dark: typeof light = {
|
|
96
|
+
darkMode: true,
|
|
97
|
+
background: "#11151b",
|
|
98
|
+
textColor: "#e4e4e7",
|
|
99
|
+
titleColor: "#f4f4f5",
|
|
100
|
+
lineColor: "#a1a1aa",
|
|
101
|
+
primaryColor: "#20242a",
|
|
102
|
+
primaryTextColor: "#f4f4f5",
|
|
103
|
+
primaryBorderColor: "#5b6573",
|
|
104
|
+
secondaryColor: "#172554",
|
|
105
|
+
secondaryTextColor: "#f4f4f5",
|
|
106
|
+
secondaryBorderColor: "#3b82f6",
|
|
107
|
+
tertiaryColor: "#181d24",
|
|
108
|
+
tertiaryTextColor: "#f4f4f5",
|
|
109
|
+
tertiaryBorderColor: "#3a4552",
|
|
110
|
+
mainBkg: "#20242a",
|
|
111
|
+
nodeBorder: "#5b6573",
|
|
112
|
+
clusterBkg: "#181d24",
|
|
113
|
+
clusterBorder: "#3a4552",
|
|
114
|
+
edgeLabelBackground: "#11151b",
|
|
115
|
+
noteBkgColor: "#422006",
|
|
116
|
+
noteBorderColor: "#a16207",
|
|
117
|
+
noteTextColor: "#fef9c3",
|
|
118
|
+
actorBkg: "#20242a",
|
|
119
|
+
actorBorder: "#5b6573",
|
|
120
|
+
actorTextColor: "#f4f4f5",
|
|
121
|
+
signalColor: "#a1a1aa",
|
|
122
|
+
signalTextColor: "#e4e4e7",
|
|
123
|
+
labelBoxBkgColor: "#20242a",
|
|
124
|
+
labelBoxBorderColor: "#5b6573",
|
|
125
|
+
activationBkgColor: "#29313a",
|
|
126
|
+
activationBorderColor: "#5b6573",
|
|
127
|
+
attributeBackgroundColorOdd: "#181d24",
|
|
128
|
+
attributeBackgroundColorEven: "#20242a",
|
|
129
|
+
sectionBkgColor: "#181d24",
|
|
130
|
+
altSectionBkgColor: "#11151b",
|
|
131
|
+
sectionBkgColor2: "#1c2129",
|
|
132
|
+
gridColor: "#29313a",
|
|
133
|
+
taskBkgColor: "#1e3a8a",
|
|
134
|
+
taskBorderColor: "#60a5fa",
|
|
135
|
+
taskTextColor: "#f4f4f5",
|
|
136
|
+
taskTextLightColor: "#f4f4f5",
|
|
137
|
+
taskTextDarkColor: "#f4f4f5",
|
|
138
|
+
taskTextOutsideColor: "#e4e4e7",
|
|
139
|
+
activeTaskBkgColor: "#1e40af",
|
|
140
|
+
activeTaskBorderColor: "#93c5fd",
|
|
141
|
+
doneTaskBkgColor: "#3f3f46",
|
|
142
|
+
doneTaskBorderColor: "#71717a",
|
|
143
|
+
critBkgColor: "#881337",
|
|
144
|
+
critBorderColor: "#fb7185",
|
|
145
|
+
todayLineColor: "#fb7185",
|
|
146
|
+
pieStrokeColor: "#11151b",
|
|
147
|
+
pieOuterStrokeColor: "#3a4552",
|
|
148
|
+
pieSectionTextColor: "#f4f4f5",
|
|
149
|
+
pieTitleTextColor: "#f4f4f5",
|
|
150
|
+
pieLegendTextColor: "#e4e4e7",
|
|
151
|
+
series: darkSeries,
|
|
152
|
+
seriesText: "#f4f4f5",
|
|
153
|
+
};
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Mermaid configuration that follows Cloud's light or dark tokens.
|
|
157
|
+
*
|
|
158
|
+
* Diagrams keep Mermaid 11's flat look and layouts; an author may still
|
|
159
|
+
* choose `layout: elk` in front matter, which loads ELK on demand. Author
|
|
160
|
+
* directives cannot change security, theme, or CSS.
|
|
161
|
+
*/
|
|
162
|
+
export function mermaidConfig(options: { dark: boolean }): MermaidConfig {
|
|
163
|
+
const { series, seriesText, ...palette } = options.dark ? dark : light;
|
|
164
|
+
const themeVariables: Record<string, string | boolean> = {
|
|
165
|
+
...palette,
|
|
166
|
+
fontFamily: '"IBM Plex Sans", ui-sans-serif, system-ui, sans-serif',
|
|
167
|
+
};
|
|
168
|
+
series.forEach((color, index) => {
|
|
169
|
+
themeVariables[`pie${index + 1}`] = color;
|
|
170
|
+
themeVariables[`cScale${index}`] = color;
|
|
171
|
+
themeVariables[`cScaleLabel${index}`] = seriesText;
|
|
172
|
+
});
|
|
173
|
+
return {
|
|
174
|
+
startOnLoad: false,
|
|
175
|
+
securityLevel: "strict",
|
|
176
|
+
suppressErrorRendering: true,
|
|
177
|
+
theme: "base",
|
|
178
|
+
look: "classic",
|
|
179
|
+
// Mermaid 12 lays these types out with ELK by default. Keep dagre so ELK
|
|
180
|
+
// loads only for diagrams whose front matter asks for `layout: elk`.
|
|
181
|
+
flowchart: { layout: "dagre" },
|
|
182
|
+
class: { layout: "dagre" },
|
|
183
|
+
state: { layout: "dagre" },
|
|
184
|
+
er: { layout: "dagre" },
|
|
185
|
+
requirement: { layout: "dagre" },
|
|
186
|
+
themeVariables,
|
|
187
|
+
secure: [
|
|
188
|
+
"secure",
|
|
189
|
+
"securityLevel",
|
|
190
|
+
"startOnLoad",
|
|
191
|
+
"maxTextSize",
|
|
192
|
+
"maxEdges",
|
|
193
|
+
"suppressErrorRendering",
|
|
194
|
+
"htmlLabels",
|
|
195
|
+
"themeCSS",
|
|
196
|
+
"themeVariables",
|
|
197
|
+
"theme",
|
|
198
|
+
"fontFamily",
|
|
199
|
+
"altFontFamily",
|
|
200
|
+
],
|
|
201
|
+
};
|
|
202
|
+
}
|
|
@@ -831,7 +831,7 @@ export const accountLifecycle = {
|
|
|
831
831
|
OR LOWER(COALESCE(display_name, '')) LIKE ${pattern} ESCAPE '\\'
|
|
832
832
|
OR LOWER(COALESCE(mail, '')) LIKE ${pattern} ESCAPE '\\'
|
|
833
833
|
)
|
|
834
|
-
ORDER BY deleted_at DESC
|
|
834
|
+
ORDER BY deleted_at DESC, id DESC
|
|
835
835
|
LIMIT ${config.perPage}
|
|
836
836
|
OFFSET ${offset}
|
|
837
837
|
`;
|
|
@@ -905,7 +905,7 @@ export const accountLifecycle = {
|
|
|
905
905
|
OR LOWER(COALESCE(r.mail, u.mail, '')) LIKE ${pattern} ESCAPE '\\'
|
|
906
906
|
OR LOWER(COALESCE(r.display_name, u.display_name, '')) LIKE ${pattern} ESCAPE '\\'
|
|
907
907
|
)
|
|
908
|
-
ORDER BY r.created_at DESC
|
|
908
|
+
ORDER BY r.created_at DESC, r.id DESC
|
|
909
909
|
LIMIT ${config.perPage}
|
|
910
910
|
OFFSET ${offset}
|
|
911
911
|
`;
|
|
@@ -1261,7 +1261,7 @@ export const accountsAppService = {
|
|
|
1261
1261
|
FROM auth.account_requests r
|
|
1262
1262
|
JOIN auth.users u ON u.id = r.user_id
|
|
1263
1263
|
WHERE ${where}
|
|
1264
|
-
ORDER BY r.created_at DESC
|
|
1264
|
+
ORDER BY r.created_at DESC, r.id DESC
|
|
1265
1265
|
LIMIT ${perPage}
|
|
1266
1266
|
OFFSET ${offset}
|
|
1267
1267
|
`;
|
|
@@ -205,7 +205,7 @@ const list = async (
|
|
|
205
205
|
AND (${filterLevel}::text IS NULL OR level = ${filterLevel})
|
|
206
206
|
AND (${filterSinceHours}::int IS NULL OR created_at >= now() - (${filterSinceHours}::int * INTERVAL '1 hour'))
|
|
207
207
|
AND (${searchPattern}::text IS NULL OR message ILIKE ${searchPattern} ESCAPE '\\' OR metadata::text ILIKE ${searchPattern} ESCAPE '\\')
|
|
208
|
-
ORDER BY created_at DESC
|
|
208
|
+
ORDER BY created_at DESC, id DESC
|
|
209
209
|
LIMIT ${perPage} OFFSET ${offset}
|
|
210
210
|
`;
|
|
211
211
|
} else if (hasSourceList) {
|
|
@@ -225,7 +225,7 @@ const list = async (
|
|
|
225
225
|
AND (${filterLevel}::text IS NULL OR level = ${filterLevel})
|
|
226
226
|
AND (${filterSinceHours}::int IS NULL OR created_at >= now() - (${filterSinceHours}::int * INTERVAL '1 hour'))
|
|
227
227
|
AND (${searchPattern}::text IS NULL OR message ILIKE ${searchPattern} ESCAPE '\\' OR metadata::text ILIKE ${searchPattern} ESCAPE '\\')
|
|
228
|
-
ORDER BY created_at DESC
|
|
228
|
+
ORDER BY created_at DESC, id DESC
|
|
229
229
|
LIMIT ${perPage} OFFSET ${offset}
|
|
230
230
|
`;
|
|
231
231
|
} else {
|
|
@@ -243,7 +243,7 @@ const list = async (
|
|
|
243
243
|
WHERE (${filterLevel}::text IS NULL OR level = ${filterLevel})
|
|
244
244
|
AND (${filterSinceHours}::int IS NULL OR created_at >= now() - (${filterSinceHours}::int * INTERVAL '1 hour'))
|
|
245
245
|
AND (${searchPattern}::text IS NULL OR message ILIKE ${searchPattern} ESCAPE '\\' OR metadata::text ILIKE ${searchPattern} ESCAPE '\\')
|
|
246
|
-
ORDER BY created_at DESC
|
|
246
|
+
ORDER BY created_at DESC, id DESC
|
|
247
247
|
LIMIT ${perPage} OFFSET ${offset}
|
|
248
248
|
`;
|
|
249
249
|
}
|
|
@@ -789,7 +789,7 @@ const list = async (
|
|
|
789
789
|
) AS event_count
|
|
790
790
|
FROM logging.trace_spans s
|
|
791
791
|
WHERE ${where}
|
|
792
|
-
ORDER BY COALESCE(s.ended_at, s.updated_at, s.started_at) DESC
|
|
792
|
+
ORDER BY COALESCE(s.ended_at, s.updated_at, s.started_at) DESC, s.trace_id DESC, s.span_id DESC
|
|
793
793
|
LIMIT ${perPage} OFFSET ${offset}
|
|
794
794
|
`,
|
|
795
795
|
]);
|
|
@@ -460,7 +460,7 @@ export const list = async (params?: {
|
|
|
460
460
|
SELECT *, COUNT(*) OVER() AS total
|
|
461
461
|
FROM notifications.batches
|
|
462
462
|
WHERE (${params?.status ?? null}::text IS NULL OR status = ${params?.status ?? null})
|
|
463
|
-
ORDER BY created_at DESC
|
|
463
|
+
ORDER BY created_at DESC, id DESC
|
|
464
464
|
LIMIT ${perPage} OFFSET ${offset}
|
|
465
465
|
`;
|
|
466
466
|
return {
|
|
@@ -235,7 +235,7 @@ export const list = async (
|
|
|
235
235
|
WHERE
|
|
236
236
|
(${statusFilter}::text IS NULL OR CASE WHEN m.sent_at IS NOT NULL THEN 'sent' WHEN m.error IS NOT NULL THEN 'error' ELSE 'pending' END = ${statusFilter})
|
|
237
237
|
AND (m.subject ILIKE ${searchPattern} ESCAPE '\' OR m.content ILIKE ${searchPattern} ESCAPE '\' OR m.recipient ILIKE ${searchPattern} ESCAPE '\')
|
|
238
|
-
ORDER BY m.created_at DESC
|
|
238
|
+
ORDER BY m.created_at DESC, m.id DESC
|
|
239
239
|
LIMIT ${perPage} OFFSET ${offset}
|
|
240
240
|
`;
|
|
241
241
|
} else {
|
|
@@ -251,7 +251,7 @@ export const list = async (
|
|
|
251
251
|
FROM notifications.messages m
|
|
252
252
|
LEFT JOIN auth.users u ON m.sent_by = u.id
|
|
253
253
|
WHERE ${statusFilter}::text IS NULL OR CASE WHEN m.sent_at IS NOT NULL THEN 'sent' WHEN m.error IS NOT NULL THEN 'error' ELSE 'pending' END = ${statusFilter}
|
|
254
|
-
ORDER BY m.created_at DESC
|
|
254
|
+
ORDER BY m.created_at DESC, m.id DESC
|
|
255
255
|
LIMIT ${perPage} OFFSET ${offset}
|
|
256
256
|
`;
|
|
257
257
|
}
|
|
@@ -276,7 +276,7 @@ export const list = async (
|
|
|
276
276
|
m.sent_by = ${sentBy}
|
|
277
277
|
AND (${statusFilter}::text IS NULL OR CASE WHEN m.sent_at IS NOT NULL THEN 'sent' WHEN m.error IS NOT NULL THEN 'error' ELSE 'pending' END = ${statusFilter})
|
|
278
278
|
AND (m.subject ILIKE ${searchPattern} ESCAPE '\' OR m.content ILIKE ${searchPattern} ESCAPE '\' OR m.recipient ILIKE ${searchPattern} ESCAPE '\')
|
|
279
|
-
ORDER BY m.created_at DESC
|
|
279
|
+
ORDER BY m.created_at DESC, m.id DESC
|
|
280
280
|
LIMIT ${perPage} OFFSET ${offset}
|
|
281
281
|
`;
|
|
282
282
|
} else {
|
|
@@ -296,7 +296,7 @@ export const list = async (
|
|
|
296
296
|
WHERE
|
|
297
297
|
m.sent_by = ${sentBy}
|
|
298
298
|
AND (${statusFilter}::text IS NULL OR CASE WHEN m.sent_at IS NOT NULL THEN 'sent' WHEN m.error IS NOT NULL THEN 'error' ELSE 'pending' END = ${statusFilter})
|
|
299
|
-
ORDER BY m.created_at DESC
|
|
299
|
+
ORDER BY m.created_at DESC, m.id DESC
|
|
300
300
|
LIMIT ${perPage} OFFSET ${offset}
|
|
301
301
|
`;
|
|
302
302
|
}
|
|
@@ -553,7 +553,7 @@ export const listOverview = async (config?: {
|
|
|
553
553
|
OR sa.resource_type ILIKE '%' || ${search} || '%'
|
|
554
554
|
OR sa.resource_id ILIKE '%' || ${search} || '%'
|
|
555
555
|
)
|
|
556
|
-
ORDER BY c.created_at DESC
|
|
556
|
+
ORDER BY c.created_at DESC, c.id DESC
|
|
557
557
|
LIMIT ${perPage}
|
|
558
558
|
OFFSET ${offset}
|
|
559
559
|
`;
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { mermaidConfig } from "../../browser/mermaid";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Initialize Mermaid diagrams within a container element.
|
|
3
5
|
* Call this in onMount() of your client-side component.
|
|
@@ -6,39 +8,7 @@
|
|
|
6
8
|
*/
|
|
7
9
|
export async function initMermaid(container: HTMLElement): Promise<void> {
|
|
8
10
|
const mermaid = (await import("mermaid")).default;
|
|
9
|
-
|
|
10
|
-
const isDark = document.documentElement.classList.contains("dark");
|
|
11
|
-
mermaid.initialize({
|
|
12
|
-
startOnLoad: false,
|
|
13
|
-
theme: "base",
|
|
14
|
-
themeVariables: isDark
|
|
15
|
-
? {
|
|
16
|
-
darkMode: true,
|
|
17
|
-
background: "#09090b",
|
|
18
|
-
textColor: "#e5e7eb",
|
|
19
|
-
lineColor: "#6b7280",
|
|
20
|
-
primaryTextColor: "#e5e7eb",
|
|
21
|
-
secondaryTextColor: "#e5e7eb",
|
|
22
|
-
tertiaryTextColor: "#e5e7eb",
|
|
23
|
-
noteTextColor: "#e5e7eb",
|
|
24
|
-
mainBkg: "#111827",
|
|
25
|
-
secondBkg: "#1f2937",
|
|
26
|
-
tertiaryColor: "#374151",
|
|
27
|
-
}
|
|
28
|
-
: {
|
|
29
|
-
darkMode: false,
|
|
30
|
-
background: "#ffffff",
|
|
31
|
-
textColor: "#111827",
|
|
32
|
-
lineColor: "#6b7280",
|
|
33
|
-
primaryTextColor: "#111827",
|
|
34
|
-
secondaryTextColor: "#111827",
|
|
35
|
-
tertiaryTextColor: "#111827",
|
|
36
|
-
noteTextColor: "#111827",
|
|
37
|
-
mainBkg: "#ffffff",
|
|
38
|
-
secondBkg: "#f9fafb",
|
|
39
|
-
tertiaryColor: "#f3f4f6",
|
|
40
|
-
},
|
|
41
|
-
});
|
|
11
|
+
mermaid.initialize(mermaidConfig({ dark: document.documentElement.classList.contains("dark") }));
|
|
42
12
|
|
|
43
13
|
// Find mermaid blocks (rendered with fixed-height container from server)
|
|
44
14
|
const mermaidBlocks = container.querySelectorAll(".md-mermaid-block");
|