@rudderjs/telescope 0.0.1 → 0.0.2

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 (150) hide show
  1. package/README.md +101 -27
  2. package/dist/api/routes.d.ts +10 -6
  3. package/dist/api/routes.d.ts.map +1 -1
  4. package/dist/api/routes.js +42 -54
  5. package/dist/api/routes.js.map +1 -1
  6. package/dist/batch-context.d.ts +19 -0
  7. package/dist/batch-context.d.ts.map +1 -0
  8. package/dist/batch-context.js +24 -0
  9. package/dist/batch-context.js.map +1 -0
  10. package/dist/collectors/ai.d.ts +13 -0
  11. package/dist/collectors/ai.d.ts.map +1 -0
  12. package/dist/collectors/ai.js +61 -0
  13. package/dist/collectors/ai.js.map +1 -0
  14. package/dist/collectors/broadcast.d.ts +10 -0
  15. package/dist/collectors/broadcast.d.ts.map +1 -0
  16. package/dist/collectors/broadcast.js +46 -0
  17. package/dist/collectors/broadcast.js.map +1 -0
  18. package/dist/collectors/cache.d.ts.map +1 -1
  19. package/dist/collectors/cache.js +5 -4
  20. package/dist/collectors/cache.js.map +1 -1
  21. package/dist/collectors/command.d.ts +21 -0
  22. package/dist/collectors/command.d.ts.map +1 -0
  23. package/dist/collectors/command.js +55 -0
  24. package/dist/collectors/command.js.map +1 -0
  25. package/dist/collectors/dump.d.ts +17 -0
  26. package/dist/collectors/dump.d.ts.map +1 -0
  27. package/dist/collectors/dump.js +37 -0
  28. package/dist/collectors/dump.js.map +1 -0
  29. package/dist/collectors/event.d.ts.map +1 -1
  30. package/dist/collectors/event.js +2 -1
  31. package/dist/collectors/event.js.map +1 -1
  32. package/dist/collectors/exception.d.ts +1 -0
  33. package/dist/collectors/exception.d.ts.map +1 -1
  34. package/dist/collectors/exception.js +23 -4
  35. package/dist/collectors/exception.js.map +1 -1
  36. package/dist/collectors/gate.d.ts +18 -0
  37. package/dist/collectors/gate.d.ts.map +1 -0
  38. package/dist/collectors/gate.js +49 -0
  39. package/dist/collectors/gate.js.map +1 -0
  40. package/dist/collectors/http.d.ts +21 -0
  41. package/dist/collectors/http.d.ts.map +1 -0
  42. package/dist/collectors/http.js +64 -0
  43. package/dist/collectors/http.js.map +1 -0
  44. package/dist/collectors/job.d.ts.map +1 -1
  45. package/dist/collectors/job.js +3 -2
  46. package/dist/collectors/job.js.map +1 -1
  47. package/dist/collectors/live.d.ts +13 -0
  48. package/dist/collectors/live.d.ts.map +1 -0
  49. package/dist/collectors/live.js +60 -0
  50. package/dist/collectors/live.js.map +1 -0
  51. package/dist/collectors/log.d.ts.map +1 -1
  52. package/dist/collectors/log.js +2 -1
  53. package/dist/collectors/log.js.map +1 -1
  54. package/dist/collectors/mail.d.ts.map +1 -1
  55. package/dist/collectors/mail.js +2 -1
  56. package/dist/collectors/mail.js.map +1 -1
  57. package/dist/collectors/mcp.d.ts +14 -0
  58. package/dist/collectors/mcp.d.ts.map +1 -0
  59. package/dist/collectors/mcp.js +49 -0
  60. package/dist/collectors/mcp.js.map +1 -0
  61. package/dist/collectors/model.d.ts +6 -0
  62. package/dist/collectors/model.d.ts.map +1 -1
  63. package/dist/collectors/model.js +44 -1
  64. package/dist/collectors/model.js.map +1 -1
  65. package/dist/collectors/notification.d.ts.map +1 -1
  66. package/dist/collectors/notification.js +2 -1
  67. package/dist/collectors/notification.js.map +1 -1
  68. package/dist/collectors/query.d.ts.map +1 -1
  69. package/dist/collectors/query.js +5 -3
  70. package/dist/collectors/query.js.map +1 -1
  71. package/dist/collectors/request.d.ts +2 -1
  72. package/dist/collectors/request.d.ts.map +1 -1
  73. package/dist/collectors/request.js +78 -4
  74. package/dist/collectors/request.js.map +1 -1
  75. package/dist/index.d.ts +17 -2
  76. package/dist/index.d.ts.map +1 -1
  77. package/dist/index.js +152 -95
  78. package/dist/index.js.map +1 -1
  79. package/dist/redact.d.ts +25 -0
  80. package/dist/redact.d.ts.map +1 -0
  81. package/dist/redact.js +55 -0
  82. package/dist/redact.js.map +1 -0
  83. package/dist/routes.d.ts +22 -0
  84. package/dist/routes.d.ts.map +1 -0
  85. package/dist/routes.js +105 -0
  86. package/dist/routes.js.map +1 -0
  87. package/dist/storage.d.ts.map +1 -1
  88. package/dist/storage.js +13 -0
  89. package/dist/storage.js.map +1 -1
  90. package/dist/types.d.ts +32 -1
  91. package/dist/types.d.ts.map +1 -1
  92. package/dist/types.js +13 -0
  93. package/dist/types.js.map +1 -1
  94. package/dist/views/vanilla/Dashboard.d.ts +10 -0
  95. package/dist/views/vanilla/Dashboard.d.ts.map +1 -0
  96. package/dist/views/vanilla/Dashboard.js +48 -0
  97. package/dist/views/vanilla/Dashboard.js.map +1 -0
  98. package/dist/views/vanilla/EntryList.d.ts +29 -0
  99. package/dist/views/vanilla/EntryList.d.ts.map +1 -0
  100. package/dist/views/vanilla/EntryList.js +172 -0
  101. package/dist/views/vanilla/EntryList.js.map +1 -0
  102. package/dist/views/vanilla/Layout.d.ts +25 -0
  103. package/dist/views/vanilla/Layout.d.ts.map +1 -0
  104. package/dist/views/vanilla/Layout.js +225 -0
  105. package/dist/views/vanilla/Layout.js.map +1 -0
  106. package/dist/views/vanilla/_html.d.ts +27 -0
  107. package/dist/views/vanilla/_html.d.ts.map +1 -0
  108. package/dist/views/vanilla/_html.js +55 -0
  109. package/dist/views/vanilla/_html.js.map +1 -0
  110. package/dist/views/vanilla/columns.d.ts +20 -0
  111. package/dist/views/vanilla/columns.d.ts.map +1 -0
  112. package/dist/views/vanilla/columns.js +181 -0
  113. package/dist/views/vanilla/columns.js.map +1 -0
  114. package/dist/views/vanilla/details/Batch.d.ts +14 -0
  115. package/dist/views/vanilla/details/Batch.d.ts.map +1 -0
  116. package/dist/views/vanilla/details/Batch.js +120 -0
  117. package/dist/views/vanilla/details/Batch.js.map +1 -0
  118. package/dist/views/vanilla/details/Layout.d.ts +21 -0
  119. package/dist/views/vanilla/details/Layout.d.ts.map +1 -0
  120. package/dist/views/vanilla/details/Layout.js +163 -0
  121. package/dist/views/vanilla/details/Layout.js.map +1 -0
  122. package/dist/views/vanilla/details/NotFound.d.ts +8 -0
  123. package/dist/views/vanilla/details/NotFound.d.ts.map +1 -0
  124. package/dist/views/vanilla/details/NotFound.js +20 -0
  125. package/dist/views/vanilla/details/NotFound.js.map +1 -0
  126. package/dist/views/vanilla/details/index.d.ts +5 -0
  127. package/dist/views/vanilla/details/index.d.ts.map +1 -0
  128. package/dist/views/vanilla/details/index.js +5 -0
  129. package/dist/views/vanilla/details/index.js.map +1 -0
  130. package/dist/views/vanilla/details/sections.d.ts +46 -0
  131. package/dist/views/vanilla/details/sections.d.ts.map +1 -0
  132. package/dist/views/vanilla/details/sections.js +145 -0
  133. package/dist/views/vanilla/details/sections.js.map +1 -0
  134. package/dist/views/vanilla/details/views.d.ts +15 -0
  135. package/dist/views/vanilla/details/views.d.ts.map +1 -0
  136. package/dist/views/vanilla/details/views.js +534 -0
  137. package/dist/views/vanilla/details/views.js.map +1 -0
  138. package/dist/views/vanilla/index.d.ts +5 -0
  139. package/dist/views/vanilla/index.d.ts.map +1 -0
  140. package/dist/views/vanilla/index.js +5 -0
  141. package/dist/views/vanilla/index.js.map +1 -0
  142. package/package.json +53 -13
  143. package/dist/ui/layout.d.ts +0 -11
  144. package/dist/ui/layout.d.ts.map +0 -1
  145. package/dist/ui/layout.js +0 -69
  146. package/dist/ui/layout.js.map +0 -1
  147. package/dist/ui/pages.d.ts +0 -21
  148. package/dist/ui/pages.d.ts.map +0 -1
  149. package/dist/ui/pages.js +0 -225
  150. package/dist/ui/pages.js.map +0 -1
@@ -0,0 +1,120 @@
1
+ import { Layout } from '../Layout.js';
2
+ import { html } from '../_html.js';
3
+ import { Card, Badge } from './sections.js';
4
+ /**
5
+ * Batch view — lists every entry sharing one `batchId`. Typically a single
6
+ * HTTP request and all the queries / cache lookups / events / model writes
7
+ * it triggered. The framework's batch propagation through the request
8
+ * lifecycle is what makes this useful.
9
+ */
10
+ export function BatchPage(props) {
11
+ const { basePath, batchId, entries } = props;
12
+ const sorted = [...entries].sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
13
+ // The first request entry (if any) is the entry-point
14
+ const requestEntry = sorted.find(e => e.type === 'request');
15
+ const counts = {};
16
+ for (const e of sorted)
17
+ counts[e.type] = (counts[e.type] ?? 0) + 1;
18
+ const summary = html `
19
+ ${Card(null, html `
20
+ <div class="flex items-center justify-between">
21
+ <div>
22
+ <div class="text-xs uppercase tracking-wide text-gray-500 dark:text-gray-400 mb-1">Batch</div>
23
+ <div class="font-mono text-xs">${batchId}</div>
24
+ </div>
25
+ <div class="flex flex-wrap gap-2">
26
+ ${Object.entries(counts).map(([type, count]) => html `
27
+ <span class="inline-flex items-center gap-1 px-2 py-0.5 rounded bg-gray-100 dark:bg-gray-800 text-xs">
28
+ <span class="font-medium">${count}</span>
29
+ <span class="text-gray-500 dark:text-gray-400">${type}${count === 1 ? '' : 's'}</span>
30
+ </span>
31
+ `)}
32
+ </div>
33
+ </div>
34
+ ${requestEntry ? html `
35
+ <div class="mt-4 pt-4 border-t border-gray-100 dark:border-gray-800 text-sm">
36
+ <div class="flex items-center gap-2">
37
+ ${Badge(String(requestEntry.content['method'] ?? ''))}
38
+ <span class="font-mono text-xs">${String(requestEntry.content['path'] ?? '')}</span>
39
+ </div>
40
+ </div>
41
+ ` : ''}
42
+ `)}
43
+ `;
44
+ const tableRows = sorted.map(e => {
45
+ const c = e.content;
46
+ const detailUrl = `${basePath}/${pluralUrlSegment(e.type)}/${e.id}`;
47
+ const summaryText = entrySummary(e.type, c);
48
+ const offsetMs = requestEntry
49
+ ? new Date(e.createdAt).getTime() - new Date(requestEntry.createdAt).getTime()
50
+ : 0;
51
+ return html `
52
+ <tr class="hover:bg-gray-50 dark:hover:bg-gray-800">
53
+ <td class="px-4 py-2 text-right text-xs text-gray-400 dark:text-gray-500 font-mono w-16">+${offsetMs}ms</td>
54
+ <td class="px-4 py-2 w-24">${Badge(e.type)}</td>
55
+ <td class="px-4 py-2 text-sm break-all">${summaryText}</td>
56
+ <td class="px-4 py-2 text-right">
57
+ <a href="${detailUrl}" @click.prevent="$dispatch('telescope:navigate', '${detailUrl}')" class="text-xs text-indigo-600 dark:text-indigo-400 hover:text-indigo-700 dark:hover:text-indigo-300">View →</a>
58
+ </td>
59
+ </tr>
60
+ `;
61
+ });
62
+ const table = html `
63
+ ${Card(null, html `
64
+ <table class="w-full">
65
+ <thead class="text-left">
66
+ <tr class="text-xs uppercase tracking-wide text-gray-500 dark:text-gray-400 border-b border-gray-100 dark:border-gray-800">
67
+ <th class="px-4 py-2 text-right">Offset</th>
68
+ <th class="px-4 py-2">Type</th>
69
+ <th class="px-4 py-2">Summary</th>
70
+ <th class="px-4 py-2"></th>
71
+ </tr>
72
+ </thead>
73
+ <tbody class="divide-y divide-gray-100 dark:divide-gray-800">
74
+ ${tableRows}
75
+ </tbody>
76
+ </table>
77
+ `)}
78
+ `;
79
+ const backLink = html `
80
+ <a href="${basePath}" @click.prevent="$dispatch('telescope:navigate', '${basePath}')" class="inline-flex items-center gap-1 text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 mb-3">
81
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"/></svg>
82
+ Back to Dashboard
83
+ </a>
84
+ `;
85
+ const body = html `
86
+ ${backLink}
87
+ <h2 class="text-xl font-bold mb-6">Batch Detail</h2>
88
+ ${summary}
89
+ ${table}
90
+ `.toString();
91
+ return Layout({ title: 'Batch', body, basePath, activePath: '/' });
92
+ }
93
+ function pluralUrlSegment(type) {
94
+ // Mirrors the URL segments declared in `columns.ts` `pages` map.
95
+ if (type === 'mail' || type === 'cache' || type === 'schedule')
96
+ return type;
97
+ if (type === 'query')
98
+ return 'queries';
99
+ return `${type}s`; // request → requests, command → commands, etc.
100
+ }
101
+ function entrySummary(type, c) {
102
+ switch (type) {
103
+ case 'request': return `${c['method'] ?? ''} ${c['path'] ?? ''}`;
104
+ case 'query': return String(c['sql'] ?? '');
105
+ case 'job': return String(c['class'] ?? '');
106
+ case 'exception': return `${c['class'] ?? ''}: ${c['message'] ?? ''}`;
107
+ case 'log': return `[${c['level'] ?? ''}] ${c['message'] ?? ''}`;
108
+ case 'mail': return String(c['subject'] ?? '');
109
+ case 'notification': return String(c['class'] ?? '');
110
+ case 'event': return String(c['name'] ?? '');
111
+ case 'cache': return `${c['operation'] ?? ''} ${c['key'] ?? ''}`;
112
+ case 'schedule': return String(c['description'] ?? '');
113
+ case 'model': return `${c['action'] ?? ''} ${c['model'] ?? ''}`;
114
+ case 'command': return `${c['name'] ?? ''} (exit ${c['exitCode'] ?? '?'})`;
115
+ case 'broadcast': return `${c['kind'] ?? ''}${c['channel'] ? ' ' + c['channel'] : ''}${c['event'] ? ' → ' + c['event'] : ''}`;
116
+ case 'live': return `${c['kind'] ?? ''}${c['docName'] ? ' ' + c['docName'] : ''}${c['byteSize'] != null ? ' (' + c['byteSize'] + 'b)' : ''}`;
117
+ default: return '';
118
+ }
119
+ }
120
+ //# sourceMappingURL=Batch.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Batch.js","sourceRoot":"","sources":["../../../../src/views/vanilla/details/Batch.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ import { type SafeString } from '../_html.js';
2
+ import type { TelescopeEntry } from '../../../types.js';
3
+ export interface DetailLayoutProps {
4
+ basePath: string;
5
+ /** URL segment for this watcher (e.g. `requests`, `mail`, `cache`) */
6
+ pageKey: string;
7
+ /** Display title — usually the watcher type pluralised */
8
+ pageTitle: string;
9
+ entry: TelescopeEntry;
10
+ /** Pre-rendered detail body — must be a `SafeString` */
11
+ body: SafeString;
12
+ /** Related entries from the same batch (if any) */
13
+ relatedEntries?: TelescopeEntry[] | undefined;
14
+ }
15
+ /**
16
+ * Shared chrome for every per-watcher detail page. Wraps the existing
17
+ * sidebar Layout and adds a back link, entry header (id, type, age, tags),
18
+ * watcher-specific body, and inline related entries from the same batch.
19
+ */
20
+ export declare function DetailLayout(props: DetailLayoutProps): string;
21
+ //# sourceMappingURL=Layout.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Layout.d.ts","sourceRoot":"","sources":["../../../../src/views/vanilla/details/Layout.ts"],"names":[],"mappings":"AACA,OAAO,EAAa,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA;AAExD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAEvD,MAAM,WAAW,iBAAiB;IAChC,QAAQ,EAAK,MAAM,CAAA;IACnB,sEAAsE;IACtE,OAAO,EAAM,MAAM,CAAA;IACnB,0DAA0D;IAC1D,SAAS,EAAI,MAAM,CAAA;IACnB,KAAK,EAAQ,cAAc,CAAA;IAC3B,wDAAwD;IACxD,IAAI,EAAS,UAAU,CAAA;IACvB,mDAAmD;IACnD,cAAc,CAAC,EAAE,cAAc,EAAE,GAAG,SAAS,CAAA;CAC9C;AAED;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,iBAAiB,GAAG,MAAM,CA+C7D"}
@@ -0,0 +1,163 @@
1
+ import { Layout as BaseLayout } from '../Layout.js';
2
+ import { html } from '../_html.js';
3
+ import { Card } from './sections.js';
4
+ /**
5
+ * Shared chrome for every per-watcher detail page. Wraps the existing
6
+ * sidebar Layout and adds a back link, entry header (id, type, age, tags),
7
+ * watcher-specific body, and inline related entries from the same batch.
8
+ */
9
+ export function DetailLayout(props) {
10
+ const { basePath, pageKey, pageTitle, entry, body, relatedEntries } = props;
11
+ const ageText = formatAge(new Date(entry.createdAt));
12
+ const tagPills = entry.tags.length > 0
13
+ ? html `<div class="flex flex-wrap gap-1 mt-2">
14
+ ${entry.tags.map((tag) => html `<a href="${basePath}/${pageKey}?tag=${tag}" @click.prevent="$dispatch('telescope:navigate', '${basePath}/${pageKey}?tag=${tag}')" class="inline-flex items-center px-2 py-0.5 rounded-full text-xs bg-gray-100 dark:bg-gray-800 text-gray-600 dark:text-gray-400 hover:bg-gray-200 dark:hover:bg-gray-700">${tag}</a>`)}
15
+ </div>`
16
+ : '';
17
+ const batchLink = entry.batchId
18
+ ? html `<a href="${basePath}/batches/${entry.batchId}" @click.prevent="$dispatch('telescope:navigate', '${basePath}/batches/${entry.batchId}')" class="inline-flex items-center gap-1 text-xs text-indigo-600 dark:text-indigo-400 hover:text-indigo-700 dark:hover:text-indigo-300 ml-3">
19
+ <svg class="w-3 h-3" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M19 11H5m14 0a2 2 0 012 2v6a2 2 0 01-2 2H5a2 2 0 01-2-2v-6a2 2 0 012-2m14 0V9a2 2 0 00-2-2M5 11V9a2 2 0 012-2m0 0V5a2 2 0 012-2h6a2 2 0 012 2v2M7 7h10"/></svg>
20
+ View all related entries
21
+ </a>`
22
+ : '';
23
+ const headerHtml = html `
24
+ <div class="mb-6">
25
+ <a href="${basePath}/${pageKey}" @click.prevent="$dispatch('telescope:navigate', '${basePath}/${pageKey}')" class="inline-flex items-center gap-1 text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 mb-3">
26
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"/></svg>
27
+ Back to ${pageTitle}
28
+ </a>
29
+ <div class="flex items-baseline justify-between">
30
+ <h2 class="text-xl font-bold">${pageTitle.replace(/s$/, '')} Detail</h2>
31
+ <div class="text-xs text-gray-500 dark:text-gray-400">
32
+ <span class="font-mono">${entry.id}</span>
33
+ <span class="mx-2">·</span>
34
+ <span>${ageText}</span>
35
+ ${batchLink}
36
+ </div>
37
+ </div>
38
+ ${tagPills}
39
+ </div>
40
+ `;
41
+ // Render inline related entries grouped by type
42
+ const relatedHtml = renderRelatedEntries(relatedEntries ?? [], basePath, entry);
43
+ const fullBody = html `${headerHtml}${body}${relatedHtml}`.toString();
44
+ return BaseLayout({
45
+ title: `${pageTitle.replace(/s$/, '')} ${entry.id.slice(0, 8)}`,
46
+ body: fullBody,
47
+ basePath,
48
+ activePath: `/${pageKey}`,
49
+ });
50
+ }
51
+ // ─── Related Entries ──────────────────────────────────────
52
+ function renderRelatedEntries(entries, basePath, parentEntry) {
53
+ if (entries.length === 0)
54
+ return html ``;
55
+ // Group by type
56
+ const groups = new Map();
57
+ for (const e of entries) {
58
+ const list = groups.get(e.type) ?? [];
59
+ list.push(e);
60
+ groups.set(e.type, list);
61
+ }
62
+ // Sort entries within each group chronologically
63
+ for (const list of groups.values()) {
64
+ list.sort((a, b) => new Date(a.createdAt).getTime() - new Date(b.createdAt).getTime());
65
+ }
66
+ // Render each group as a section
67
+ const parentTime = new Date(parentEntry.createdAt).getTime();
68
+ const sections = [...groups.entries()].map(([type, list]) => {
69
+ const title = `${typeLabel(type)} (${list.length})`;
70
+ const rows = list.map(e => {
71
+ const c = e.content;
72
+ const summary = entrySummary(type, c);
73
+ const detailUrl = `${basePath}/${pluralUrlSegment(type)}/${e.id}`;
74
+ const offsetMs = new Date(e.createdAt).getTime() - parentTime;
75
+ const durationText = c['duration'] != null ? `${c['duration']}ms` : '';
76
+ return html `
77
+ <tr class="hover:bg-gray-50 dark:hover:bg-gray-800">
78
+ <td class="px-4 py-2 text-right text-xs text-gray-400 dark:text-gray-500 font-mono w-16">${offsetMs >= 0 ? '+' : ''}${offsetMs}ms</td>
79
+ <td class="px-4 py-2 text-sm break-all">
80
+ <a href="${detailUrl}" @click.prevent="$dispatch('telescope:navigate', '${detailUrl}')" class="text-indigo-600 dark:text-indigo-400 hover:text-indigo-700 dark:hover:text-indigo-300">${summary}</a>
81
+ </td>
82
+ <td class="px-4 py-2 text-right text-xs text-gray-400 dark:text-gray-500 font-mono">${durationText}</td>
83
+ </tr>
84
+ `;
85
+ });
86
+ return Card(title, html `
87
+ <table class="w-full">
88
+ <tbody class="divide-y divide-gray-100 dark:divide-gray-800">
89
+ ${rows}
90
+ </tbody>
91
+ </table>
92
+ `);
93
+ });
94
+ return html `
95
+ <div class="mt-6">
96
+ <h3 class="text-sm uppercase tracking-wide font-medium text-gray-500 dark:text-gray-400 mb-3">Related Entries</h3>
97
+ ${sections}
98
+ </div>
99
+ `;
100
+ }
101
+ function typeLabel(type) {
102
+ const labels = {
103
+ request: 'Requests', query: 'Queries', job: 'Jobs', exception: 'Exceptions',
104
+ log: 'Logs', mail: 'Mail', notification: 'Notifications', event: 'Events',
105
+ cache: 'Cache', schedule: 'Scheduled Tasks', model: 'Model Changes',
106
+ command: 'Commands', http: 'HTTP Client', gate: 'Gates', dump: 'Dumps',
107
+ broadcast: 'WebSockets', live: 'Live (Yjs)',
108
+ };
109
+ return labels[type] ?? type;
110
+ }
111
+ function pluralUrlSegment(type) {
112
+ if (type === 'mail' || type === 'cache' || type === 'schedule' || type === 'http')
113
+ return type;
114
+ if (type === 'query')
115
+ return 'queries';
116
+ return `${type}s`;
117
+ }
118
+ function entrySummary(type, c) {
119
+ switch (type) {
120
+ case 'request': return `${c['method'] ?? ''} ${c['path'] ?? ''}`;
121
+ case 'query': return String(c['sql'] ?? '').slice(0, 120);
122
+ case 'job': return String(c['class'] ?? '');
123
+ case 'exception': return `${c['class'] ?? ''}: ${c['message'] ?? ''}`;
124
+ case 'log': return `[${c['level'] ?? ''}] ${String(c['message'] ?? '').slice(0, 100)}`;
125
+ case 'mail': return String(c['subject'] ?? '');
126
+ case 'notification': return String(c['class'] ?? '');
127
+ case 'event': return String(c['name'] ?? '');
128
+ case 'cache': return `${c['operation'] ?? ''} ${c['key'] ?? ''}`;
129
+ case 'schedule': return String(c['description'] ?? '');
130
+ case 'model': return `${c['action'] ?? ''} ${c['model'] ?? ''}`;
131
+ case 'command': return `${c['name'] ?? ''} (exit ${c['exitCode'] ?? '?'})`;
132
+ case 'http': return `${c['method'] ?? ''} ${c['url'] ?? ''}`;
133
+ case 'gate': return `${c['ability'] ?? ''} → ${c['allowed'] ? 'allowed' : 'denied'}`;
134
+ case 'dump': return `${c['method'] ?? ''}(${c['count'] ?? 0} args)`;
135
+ case 'broadcast': return `${c['kind'] ?? ''}${c['channel'] ? ' ' + c['channel'] : ''}`;
136
+ case 'live': return `${c['kind'] ?? ''}${c['docName'] ? ' ' + c['docName'] : ''}`;
137
+ default: return '';
138
+ }
139
+ }
140
+ function formatAge(date) {
141
+ const s = Math.floor((Date.now() - date.getTime()) / 1000);
142
+ const relative = s < 60 ? `${s}s ago`
143
+ : s < 3600 ? `${Math.floor(s / 60)}m ago`
144
+ : s < 86400 ? `${Math.floor(s / 3600)}h ago`
145
+ : `${Math.floor(s / 86400)}d ago`;
146
+ return `${formatFullDate(date)} (${relative})`;
147
+ }
148
+ function formatFullDate(date) {
149
+ const months = ['January', 'February', 'March', 'April', 'May', 'June',
150
+ 'July', 'August', 'September', 'October', 'November', 'December'];
151
+ const month = months[date.getMonth()];
152
+ const day = date.getDate();
153
+ const year = date.getFullYear();
154
+ const suffix = day === 1 || day === 21 || day === 31 ? 'st'
155
+ : day === 2 || day === 22 ? 'nd'
156
+ : day === 3 || day === 23 ? 'rd' : 'th';
157
+ const h = date.getHours();
158
+ const m = date.getMinutes().toString().padStart(2, '0');
159
+ const ampm = h >= 12 ? 'PM' : 'AM';
160
+ const h12 = h % 12 || 12;
161
+ return `${month} ${day}${suffix} ${year}, ${h12}:${m} ${ampm}`;
162
+ }
163
+ //# sourceMappingURL=Layout.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Layout.js","sourceRoot":"","sources":["../../../../src/views/vanilla/details/Layout.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,8 @@
1
+ export interface NotFoundProps {
2
+ basePath: string;
3
+ /** What we were looking up — used in the message. */
4
+ what: string;
5
+ id: string;
6
+ }
7
+ export declare function NotFoundPage(props: NotFoundProps): string;
8
+ //# sourceMappingURL=NotFound.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NotFound.d.ts","sourceRoot":"","sources":["../../../../src/views/vanilla/details/NotFound.ts"],"names":[],"mappings":"AAGA,MAAM,WAAW,aAAa;IAC5B,QAAQ,EAAE,MAAM,CAAA;IAChB,qDAAqD;IACrD,IAAI,EAAK,MAAM,CAAA;IACf,EAAE,EAAO,MAAM,CAAA;CAChB;AAED,wBAAgB,YAAY,CAAC,KAAK,EAAE,aAAa,GAAG,MAAM,CAiBzD"}
@@ -0,0 +1,20 @@
1
+ import { Layout } from '../Layout.js';
2
+ import { html } from '../_html.js';
3
+ export function NotFoundPage(props) {
4
+ const { basePath, what, id } = props;
5
+ const body = html `
6
+ <div class="text-center py-20">
7
+ <div class="inline-flex items-center justify-center w-12 h-12 bg-gray-100 dark:bg-gray-800 rounded-full mb-4">
8
+ <svg class="w-6 h-6 text-gray-400 dark:text-gray-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9.172 16.172a4 4 0 015.656 0M9 10h.01M15 10h.01M21 12a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
9
+ </div>
10
+ <h2 class="text-lg font-semibold text-gray-900 dark:text-gray-100 mb-1">${what} not found</h2>
11
+ <p class="text-sm text-gray-500 dark:text-gray-400 mb-6">No entry with id <span class="font-mono">${id}</span> exists.</p>
12
+ <a href="${basePath}" class="inline-flex items-center gap-1 text-sm text-indigo-600 dark:text-indigo-400 hover:text-indigo-700 dark:hover:text-indigo-300">
13
+ <svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 19l-7-7m0 0l7-7m-7 7h18"/></svg>
14
+ Back to Dashboard
15
+ </a>
16
+ </div>
17
+ `.toString();
18
+ return Layout({ title: 'Not Found', body, basePath, activePath: '/' });
19
+ }
20
+ //# sourceMappingURL=NotFound.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"NotFound.js","sourceRoot":"","sources":["../../../../src/views/vanilla/details/NotFound.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,cAAc,CAAA;AACrC,OAAO,EAAE,IAAI,EAAE,MAAM,aAAa,CAAA;AASlC,MAAM,UAAU,YAAY,CAAC,KAAoB;IAC/C,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,EAAE,EAAE,GAAG,KAAK,CAAA;IACpC,MAAM,IAAI,GAAG,IAAI,CAAA;;;;;gFAK6D,IAAI;0GACsB,EAAE;iBAC3F,QAAQ;;;;;GAKtB,CAAC,QAAQ,EAAE,CAAA;IAEZ,OAAO,MAAM,CAAC,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,GAAG,EAAE,CAAC,CAAA;AACxE,CAAC"}
@@ -0,0 +1,5 @@
1
+ export { DetailLayout, type DetailLayoutProps } from './Layout.js';
2
+ export { detailViews } from './views.js';
3
+ export { NotFoundPage } from './NotFound.js';
4
+ export { BatchPage, type BatchPageProps } from './Batch.js';
5
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/views/vanilla/details/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAA;AAClE,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EAAE,SAAS,EAAE,KAAK,cAAc,EAAE,MAAM,YAAY,CAAA"}
@@ -0,0 +1,5 @@
1
+ export { DetailLayout } from './Layout.js';
2
+ export { detailViews } from './views.js';
3
+ export { NotFoundPage } from './NotFound.js';
4
+ export { BatchPage } from './Batch.js';
5
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../../src/views/vanilla/details/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAA0B,MAAM,aAAa,CAAA;AAClE,OAAO,EAAE,WAAW,EAAE,MAAM,YAAY,CAAA;AACxC,OAAO,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AAC5C,OAAO,EAAE,SAAS,EAAuB,MAAM,YAAY,CAAA"}
@@ -0,0 +1,46 @@
1
+ import { SafeString } from '../_html.js';
2
+ /**
3
+ * Reusable detail-page sections. Composed by per-watcher views in `views.ts`
4
+ * to keep each detail view small and consistent.
5
+ */
6
+ /**
7
+ * Card wrapper used to group a section of a detail page. Title is optional —
8
+ * omit it for sections where the content is self-explanatory.
9
+ */
10
+ export declare function Card(title: string | null, body: SafeString | string): SafeString;
11
+ /**
12
+ * Two-column key/value table. `null`/`undefined` values are rendered as `—`.
13
+ * String values are HTML-escaped automatically by `html\`\``.
14
+ */
15
+ export declare function KeyValueTable(rows: Record<string, unknown>): SafeString;
16
+ /**
17
+ * Formatted JSON block in a `<pre>`. Used for arbitrary nested values
18
+ * (request bodies, payloads, dirty attribute diffs, etc.).
19
+ */
20
+ export declare function JsonBlock(value: unknown): SafeString;
21
+ /**
22
+ * Code block — like JsonBlock but for arbitrary text (SQL, stack traces,
23
+ * log messages). No JSON formatting.
24
+ */
25
+ export declare function CodeBlock(text: string, opts?: {
26
+ language?: string;
27
+ maxHeight?: string;
28
+ }): SafeString;
29
+ /**
30
+ * Coloured pill matching the badge style used in EntryList.
31
+ * The colour palette mirrors `EntryList.ts`'s `badgeClass()` function.
32
+ */
33
+ export declare function Badge(value: string | undefined): SafeString;
34
+ /**
35
+ * Tabbed sections — uses Alpine.js `x-data` for tab switching.
36
+ * Only renders tabs that have non-empty content.
37
+ */
38
+ export declare function Tabs(tabs: {
39
+ label: string;
40
+ content: SafeString | string;
41
+ }[]): SafeString;
42
+ /**
43
+ * Get a content field with a fallback. Helper to keep view files terse.
44
+ */
45
+ export declare function field(content: Record<string, unknown>, key: string): unknown;
46
+ //# sourceMappingURL=sections.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sections.d.ts","sourceRoot":"","sources":["../../../../src/views/vanilla/details/sections.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,UAAU,EAAE,MAAM,aAAa,CAAA;AAEnD;;;GAGG;AAEH;;;GAGG;AACH,wBAAgB,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,IAAI,EAAE,IAAI,EAAE,UAAU,GAAG,MAAM,GAAG,UAAU,CAQhF;AAED;;;GAGG;AACH,wBAAgB,aAAa,CAAC,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,UAAU,CAgBvE;AAWD;;;GAGG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,OAAO,GAAG,UAAU,CAQpD;AAED;;;GAGG;AACH,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,SAAS,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,UAAU,CAIxG;AAED;;;GAGG;AACH,wBAAgB,KAAK,CAAC,KAAK,EAAE,MAAM,GAAG,SAAS,GAAG,UAAU,CA0B3D;AAED;;;GAGG;AACH,wBAAgB,IAAI,CAAC,IAAI,EAAE;IAAE,KAAK,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,UAAU,GAAG,MAAM,CAAA;CAAE,EAAE,GAAG,UAAU,CAyBxF;AAED;;GAEG;AACH,wBAAgB,KAAK,CAAC,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,GAAG,EAAE,MAAM,GAAG,OAAO,CAE5E"}
@@ -0,0 +1,145 @@
1
+ import { html, raw, SafeString } from '../_html.js';
2
+ /**
3
+ * Reusable detail-page sections. Composed by per-watcher views in `views.ts`
4
+ * to keep each detail view small and consistent.
5
+ */
6
+ /**
7
+ * Card wrapper used to group a section of a detail page. Title is optional —
8
+ * omit it for sections where the content is self-explanatory.
9
+ */
10
+ export function Card(title, body) {
11
+ const titleHtml = title ? html `<h3 class="text-xs uppercase tracking-wide font-medium text-gray-500 dark:text-gray-400 mb-3">${title}</h3>` : '';
12
+ return html `
13
+ <div class="bg-white dark:bg-gray-900 rounded-xl border border-gray-200 dark:border-gray-800 shadow-sm p-5 mb-4">
14
+ ${titleHtml}
15
+ ${typeof body === 'string' ? body : body}
16
+ </div>
17
+ `;
18
+ }
19
+ /**
20
+ * Two-column key/value table. `null`/`undefined` values are rendered as `—`.
21
+ * String values are HTML-escaped automatically by `html\`\``.
22
+ */
23
+ export function KeyValueTable(rows) {
24
+ const entries = Object.entries(rows);
25
+ if (entries.length === 0)
26
+ return html `<p class="text-sm text-gray-400 dark:text-gray-500">No data.</p>`;
27
+ return html `
28
+ <table class="w-full text-sm">
29
+ <tbody class="divide-y divide-gray-100 dark:divide-gray-800">
30
+ ${entries.map(([k, v]) => html `
31
+ <tr>
32
+ <td class="py-2 pr-4 text-gray-500 dark:text-gray-400 font-medium align-top w-40">${k}</td>
33
+ <td class="py-2 break-all">${formatValue(v)}</td>
34
+ </tr>
35
+ `)}
36
+ </tbody>
37
+ </table>
38
+ `;
39
+ }
40
+ function formatValue(v) {
41
+ if (v === null || v === undefined || v === '')
42
+ return raw('<span class="text-gray-300 dark:text-gray-600">—</span>');
43
+ if (v instanceof SafeString)
44
+ return v;
45
+ if (typeof v === 'boolean')
46
+ return raw(`<span class="font-mono text-xs">${v}</span>`);
47
+ if (typeof v === 'number')
48
+ return raw(`<span class="font-mono text-xs">${v}</span>`);
49
+ if (typeof v === 'object')
50
+ return JsonBlock(v);
51
+ return String(v);
52
+ }
53
+ /**
54
+ * Formatted JSON block in a `<pre>`. Used for arbitrary nested values
55
+ * (request bodies, payloads, dirty attribute diffs, etc.).
56
+ */
57
+ export function JsonBlock(value) {
58
+ let json;
59
+ try {
60
+ json = JSON.stringify(value, null, 2);
61
+ }
62
+ catch {
63
+ json = String(value);
64
+ }
65
+ return html `<pre class="text-xs bg-gray-50 dark:bg-gray-800 rounded-lg p-3 overflow-auto max-h-96 font-mono">${json}</pre>`;
66
+ }
67
+ /**
68
+ * Code block — like JsonBlock but for arbitrary text (SQL, stack traces,
69
+ * log messages). No JSON formatting.
70
+ */
71
+ export function CodeBlock(text, opts = {}) {
72
+ const cls = opts.maxHeight ? `max-h-${opts.maxHeight}` : 'max-h-96';
73
+ const langCls = opts.language ? ` language-${opts.language}` : '';
74
+ return html `<pre class="text-xs bg-gray-50 dark:bg-gray-800 rounded-lg p-3 overflow-auto ${cls} font-mono${langCls}">${text}</pre>`;
75
+ }
76
+ /**
77
+ * Coloured pill matching the badge style used in EntryList.
78
+ * The colour palette mirrors `EntryList.ts`'s `badgeClass()` function.
79
+ */
80
+ export function Badge(value) {
81
+ if (!value)
82
+ return html `<span class="text-gray-300 dark:text-gray-600">—</span>`;
83
+ const colors = {
84
+ GET: 'bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-400',
85
+ POST: 'bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-400',
86
+ PUT: 'bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-400',
87
+ DELETE: 'bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-400',
88
+ PATCH: 'bg-purple-100 text-purple-700 dark:bg-purple-900/40 dark:text-purple-400',
89
+ error: 'bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-400',
90
+ warning: 'bg-amber-100 text-amber-700 dark:bg-amber-900/40 dark:text-amber-400',
91
+ info: 'bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-400',
92
+ debug: 'bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300',
93
+ dispatched: 'bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-400',
94
+ failed: 'bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-400',
95
+ completed: 'bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-400',
96
+ running: 'bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-400',
97
+ hit: 'bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-400',
98
+ miss: 'bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-400',
99
+ set: 'bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-400',
100
+ forget: 'bg-gray-100 text-gray-700 dark:bg-gray-800 dark:text-gray-300',
101
+ created: 'bg-green-100 text-green-700 dark:bg-green-900/40 dark:text-green-400',
102
+ updated: 'bg-blue-100 text-blue-700 dark:bg-blue-900/40 dark:text-blue-400',
103
+ deleted: 'bg-red-100 text-red-700 dark:bg-red-900/40 dark:text-red-400',
104
+ };
105
+ const cls = colors[value] ?? 'bg-gray-100 text-gray-600 dark:bg-gray-800 dark:text-gray-300';
106
+ return html `<span class="inline-flex items-center px-2 py-0.5 rounded text-xs font-medium ${cls}">${value}</span>`;
107
+ }
108
+ /**
109
+ * Tabbed sections — uses Alpine.js `x-data` for tab switching.
110
+ * Only renders tabs that have non-empty content.
111
+ */
112
+ export function Tabs(tabs) {
113
+ const visible = tabs.filter(t => {
114
+ const s = typeof t.content === 'string' ? t.content : t.content.toString();
115
+ return s.trim().length > 0;
116
+ });
117
+ if (visible.length === 0)
118
+ return html ``;
119
+ if (visible.length === 1)
120
+ return html `${visible[0].content}`;
121
+ const id = `tabs_${Math.random().toString(36).slice(2, 8)}`;
122
+ return html `
123
+ <div x-data="{ tab: '${visible[0].label}' }" class="bg-white dark:bg-gray-900 rounded-xl border border-gray-200 dark:border-gray-800 shadow-sm mb-4">
124
+ <div class="flex border-b border-gray-200 dark:border-gray-800">
125
+ ${visible.map(t => html `
126
+ <button @click="tab = '${t.label}'"
127
+ :class="tab === '${t.label}' ? 'border-indigo-500 text-indigo-600 dark:text-indigo-400' : 'border-transparent text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-200'"
128
+ class="px-4 py-2.5 text-sm font-medium border-b-2 -mb-px transition-colors">
129
+ ${t.label}
130
+ </button>
131
+ `)}
132
+ </div>
133
+ ${visible.map(t => html `
134
+ <div x-show="tab === '${t.label}'" class="p-5">${t.content}</div>
135
+ `)}
136
+ </div>
137
+ `;
138
+ }
139
+ /**
140
+ * Get a content field with a fallback. Helper to keep view files terse.
141
+ */
142
+ export function field(content, key) {
143
+ return content[key];
144
+ }
145
+ //# sourceMappingURL=sections.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sections.js","sourceRoot":"","sources":["../../../../src/views/vanilla/details/sections.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,15 @@
1
+ import { type SafeString } from '../_html.js';
2
+ import type { TelescopeEntry } from '../../../types.js';
3
+ /**
4
+ * Per-watcher detail view functions. Each takes a `TelescopeEntry` and
5
+ * returns a `SafeString` body to be slotted into `DetailLayout`.
6
+ *
7
+ * Keep these short — push reusable rendering into `sections.ts`. The
8
+ * point of having one function per watcher is to make the type-specific
9
+ * rendering decisions explicit and obvious to read.
10
+ */
11
+ type ViewFn = (entry: TelescopeEntry) => SafeString;
12
+ /** Map of EntryType → detail view function. Used by the dispatcher. */
13
+ export declare const detailViews: Record<string, ViewFn>;
14
+ export {};
15
+ //# sourceMappingURL=views.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"views.d.ts","sourceRoot":"","sources":["../../../../src/views/vanilla/details/views.ts"],"names":[],"mappings":"AAAA,OAAO,EAAa,KAAK,UAAU,EAAE,MAAM,aAAa,CAAA;AAExD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,mBAAmB,CAAA;AAEvD;;;;;;;GAOG;AAEH,KAAK,MAAM,GAAG,CAAC,KAAK,EAAE,cAAc,KAAK,UAAU,CAAA;AAwhBnD,uEAAuE;AACvE,eAAO,MAAM,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAoB9C,CAAA"}