@syncular/console 0.0.4-26 → 0.0.6-100

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 (103) hide show
  1. package/dist/App.d.ts +3 -7
  2. package/dist/App.d.ts.map +1 -1
  3. package/dist/App.js +3 -3
  4. package/dist/App.js.map +1 -1
  5. package/dist/hooks/ConnectionContext.d.ts +4 -1
  6. package/dist/hooks/ConnectionContext.d.ts.map +1 -1
  7. package/dist/hooks/ConnectionContext.js +116 -28
  8. package/dist/hooks/ConnectionContext.js.map +1 -1
  9. package/dist/hooks/useConsoleApi.d.ts +11 -1
  10. package/dist/hooks/useConsoleApi.d.ts.map +1 -1
  11. package/dist/hooks/useConsoleApi.js +78 -0
  12. package/dist/hooks/useConsoleApi.js.map +1 -1
  13. package/dist/hooks/useLiveEvents.d.ts.map +1 -1
  14. package/dist/hooks/useLiveEvents.js +116 -3
  15. package/dist/hooks/useLiveEvents.js.map +1 -1
  16. package/dist/layout.d.ts +4 -1
  17. package/dist/layout.d.ts.map +1 -1
  18. package/dist/layout.js +8 -7
  19. package/dist/layout.js.map +1 -1
  20. package/dist/lib/api.d.ts +1 -1
  21. package/dist/lib/api.d.ts.map +1 -1
  22. package/dist/lib/api.js +36 -4
  23. package/dist/lib/api.js.map +1 -1
  24. package/dist/lib/types.d.ts +13 -0
  25. package/dist/lib/types.d.ts.map +1 -1
  26. package/dist/mount.d.ts +1 -0
  27. package/dist/mount.d.ts.map +1 -1
  28. package/dist/mount.js +1 -1
  29. package/dist/mount.js.map +1 -1
  30. package/dist/pages/Config.d.ts +3 -1
  31. package/dist/pages/Config.d.ts.map +1 -1
  32. package/dist/pages/Config.js +24 -17
  33. package/dist/pages/Config.js.map +1 -1
  34. package/dist/pages/Fleet.d.ts +3 -1
  35. package/dist/pages/Fleet.d.ts.map +1 -1
  36. package/dist/pages/Fleet.js +6 -3
  37. package/dist/pages/Fleet.js.map +1 -1
  38. package/dist/pages/Ops.js.map +1 -1
  39. package/dist/pages/Storage.d.ts +2 -0
  40. package/dist/pages/Storage.d.ts.map +1 -0
  41. package/dist/pages/Storage.js +103 -0
  42. package/dist/pages/Storage.js.map +1 -0
  43. package/dist/pages/Stream.d.ts.map +1 -1
  44. package/dist/pages/Stream.js +2 -3
  45. package/dist/pages/Stream.js.map +1 -1
  46. package/dist/pages/index.d.ts +1 -0
  47. package/dist/pages/index.d.ts.map +1 -1
  48. package/dist/pages/index.js +1 -0
  49. package/dist/pages/index.js.map +1 -1
  50. package/dist/routeTree.d.ts +1 -1
  51. package/dist/routeTree.d.ts.map +1 -1
  52. package/dist/routeTree.js +2 -0
  53. package/dist/routeTree.js.map +1 -1
  54. package/dist/routes/__root.d.ts +1 -1
  55. package/dist/routes/__root.d.ts.map +1 -1
  56. package/dist/routes/config.d.ts +1 -1
  57. package/dist/routes/config.d.ts.map +1 -1
  58. package/dist/routes/fleet.d.ts +1 -1
  59. package/dist/routes/fleet.d.ts.map +1 -1
  60. package/dist/routes/index.d.ts +1 -1
  61. package/dist/routes/index.d.ts.map +1 -1
  62. package/dist/routes/investigate-commit.d.ts +1 -1
  63. package/dist/routes/investigate-commit.d.ts.map +1 -1
  64. package/dist/routes/investigate-event.d.ts +1 -1
  65. package/dist/routes/investigate-event.d.ts.map +1 -1
  66. package/dist/routes/ops.d.ts +1 -1
  67. package/dist/routes/ops.d.ts.map +1 -1
  68. package/dist/routes/storage.d.ts +2 -0
  69. package/dist/routes/storage.d.ts.map +1 -0
  70. package/dist/routes/storage.js +9 -0
  71. package/dist/routes/storage.js.map +1 -0
  72. package/dist/routes/stream.d.ts +1 -1
  73. package/dist/routes/stream.d.ts.map +1 -1
  74. package/dist/static-server.d.ts.map +1 -1
  75. package/dist/static-server.js +6 -1
  76. package/dist/static-server.js.map +1 -1
  77. package/dist/styles.css +1 -1
  78. package/package.json +9 -9
  79. package/src/App.tsx +12 -10
  80. package/src/__tests__/static-server.test.ts +193 -0
  81. package/src/hooks/ConnectionContext.tsx +135 -29
  82. package/src/hooks/useConsoleApi.ts +103 -0
  83. package/src/hooks/useLiveEvents.ts +142 -4
  84. package/src/layout.tsx +35 -5
  85. package/src/lib/api.ts +38 -5
  86. package/src/lib/types.ts +17 -0
  87. package/src/mount.tsx +6 -1
  88. package/src/pages/Config.tsx +57 -49
  89. package/src/pages/Fleet.tsx +19 -17
  90. package/src/pages/Storage.tsx +277 -0
  91. package/src/pages/Stream.tsx +6 -3
  92. package/src/pages/index.ts +1 -0
  93. package/src/routeTree.ts +2 -0
  94. package/src/routes/storage.tsx +9 -0
  95. package/src/static-server.ts +12 -1
  96. package/src/styles/globals.css +4 -1
  97. package/web-dist/assets/index-D8JLMM1I.js +86 -0
  98. package/web-dist/assets/index-D_fQabjS.css +1 -0
  99. package/web-dist/console.css +1 -1
  100. package/web-dist/index.html +2 -2
  101. package/web-dist/site.webmanifest +2 -2
  102. package/web-dist/assets/index-CTkQp6YC.js +0 -86
  103. package/web-dist/assets/index-j_U2SoXa.css +0 -1
@@ -0,0 +1,277 @@
1
+ import {
2
+ Badge,
3
+ Button,
4
+ Dialog,
5
+ DialogContent,
6
+ DialogFooter,
7
+ DialogHeader,
8
+ DialogTitle,
9
+ EmptyState,
10
+ Input,
11
+ SectionCard,
12
+ Spinner,
13
+ Table,
14
+ TableBody,
15
+ TableCell,
16
+ TableHead,
17
+ TableHeader,
18
+ TableRow,
19
+ } from '@syncular/ui';
20
+ import { useState } from 'react';
21
+ import {
22
+ useBlobDownload,
23
+ useBlobs,
24
+ useDeleteBlobMutation,
25
+ } from '../hooks/useConsoleApi';
26
+
27
+ function formatFileSize(bytes: number): string {
28
+ if (bytes === 0) return '0 B';
29
+ const units = ['B', 'KB', 'MB', 'GB', 'TB'];
30
+ const i = Math.floor(Math.log(bytes) / Math.log(1024));
31
+ const value = bytes / 1024 ** i;
32
+ return `${value.toFixed(i === 0 ? 0 : 1)} ${units[i]}`;
33
+ }
34
+
35
+ function formatDateTime(iso: string): string {
36
+ const parsed = Date.parse(iso);
37
+ if (!Number.isFinite(parsed)) return iso;
38
+ return new Date(parsed).toLocaleString();
39
+ }
40
+
41
+ export function Storage() {
42
+ const [prefixInput, setPrefixInput] = useState('');
43
+ const [activePrefix, setActivePrefix] = useState<string | undefined>(
44
+ undefined
45
+ );
46
+ const [cursor, setCursor] = useState<string | undefined>(undefined);
47
+ const [cursorHistory, setCursorHistory] = useState<string[]>([]);
48
+ const [deletingKey, setDeletingKey] = useState<string | null>(null);
49
+
50
+ const { data, isLoading, error } = useBlobs({
51
+ prefix: activePrefix,
52
+ cursor,
53
+ limit: 100,
54
+ });
55
+ const deleteMutation = useDeleteBlobMutation();
56
+ const download = useBlobDownload();
57
+
58
+ function handleFilter() {
59
+ const trimmed = prefixInput.trim();
60
+ setActivePrefix(trimmed.length > 0 ? trimmed : undefined);
61
+ setCursor(undefined);
62
+ setCursorHistory([]);
63
+ }
64
+
65
+ function handleClearFilter() {
66
+ setPrefixInput('');
67
+ setActivePrefix(undefined);
68
+ setCursor(undefined);
69
+ setCursorHistory([]);
70
+ }
71
+
72
+ function handleNextPage() {
73
+ if (data?.cursor) {
74
+ setCursorHistory((prev) => [...prev, cursor ?? '']);
75
+ setCursor(data.cursor);
76
+ }
77
+ }
78
+
79
+ function handlePrevPage() {
80
+ setCursorHistory((prev) => {
81
+ const next = [...prev];
82
+ const prevCursor = next.pop();
83
+ setCursor(prevCursor && prevCursor.length > 0 ? prevCursor : undefined);
84
+ return next;
85
+ });
86
+ }
87
+
88
+ function handleDelete() {
89
+ if (!deletingKey) return;
90
+ deleteMutation.mutate(deletingKey, {
91
+ onSuccess: () => setDeletingKey(null),
92
+ });
93
+ }
94
+
95
+ if (isLoading) {
96
+ return (
97
+ <div className="flex flex-col gap-4 px-5 py-5">
98
+ <div className="flex items-center justify-center h-[200px]">
99
+ <Spinner size="lg" />
100
+ </div>
101
+ </div>
102
+ );
103
+ }
104
+
105
+ if (error) {
106
+ return (
107
+ <div className="flex flex-col gap-4 px-5 py-5">
108
+ <div className="flex items-center justify-center h-[200px]">
109
+ <p className="text-danger font-mono text-[11px]">
110
+ Failed to load storage items: {error.message}
111
+ </p>
112
+ </div>
113
+ </div>
114
+ );
115
+ }
116
+
117
+ const items = data?.items ?? [];
118
+
119
+ return (
120
+ <div className="flex flex-col gap-4 px-5 py-5">
121
+ <SectionCard
122
+ title="Storage"
123
+ actions={
124
+ <div className="flex items-center gap-2">
125
+ <Input
126
+ placeholder="Prefix filter..."
127
+ value={prefixInput}
128
+ onChange={(e) => setPrefixInput(e.target.value)}
129
+ onKeyDown={(e) => {
130
+ if (e.key === 'Enter') handleFilter();
131
+ }}
132
+ className="h-7 w-48 text-xs"
133
+ />
134
+ <Button variant="default" size="sm" onClick={handleFilter}>
135
+ Filter
136
+ </Button>
137
+ {activePrefix && (
138
+ <Button variant="ghost" size="sm" onClick={handleClearFilter}>
139
+ Clear
140
+ </Button>
141
+ )}
142
+ </div>
143
+ }
144
+ >
145
+ {items.length === 0 ? (
146
+ <EmptyState
147
+ message={
148
+ activePrefix
149
+ ? `No storage items matching prefix "${activePrefix}".`
150
+ : 'No storage items found.'
151
+ }
152
+ />
153
+ ) : (
154
+ <>
155
+ <div className="overflow-x-auto">
156
+ <Table>
157
+ <TableHeader>
158
+ <TableRow>
159
+ <TableHead>Key</TableHead>
160
+ <TableHead>Size</TableHead>
161
+ <TableHead>Type</TableHead>
162
+ <TableHead>Uploaded</TableHead>
163
+ <TableHead>Actions</TableHead>
164
+ </TableRow>
165
+ </TableHeader>
166
+ <TableBody>
167
+ {items.map((blob) => (
168
+ <TableRow key={blob.key}>
169
+ <TableCell
170
+ className="font-mono text-xs max-w-[320px]"
171
+ title={blob.key}
172
+ >
173
+ {blob.key}
174
+ </TableCell>
175
+ <TableCell className="font-mono text-xs text-neutral-400 whitespace-nowrap">
176
+ {formatFileSize(blob.size)}
177
+ </TableCell>
178
+ <TableCell>
179
+ {blob.httpMetadata?.contentType ? (
180
+ <Badge variant="ghost">
181
+ {blob.httpMetadata.contentType}
182
+ </Badge>
183
+ ) : (
184
+ <span className="text-neutral-500 text-xs">--</span>
185
+ )}
186
+ </TableCell>
187
+ <TableCell className="whitespace-nowrap text-xs text-neutral-400">
188
+ {formatDateTime(blob.uploaded)}
189
+ </TableCell>
190
+ <TableCell>
191
+ <div className="flex items-center gap-1">
192
+ <Button
193
+ variant="ghost"
194
+ size="sm"
195
+ onClick={() => void download(blob.key)}
196
+ >
197
+ Download
198
+ </Button>
199
+ <Button
200
+ variant="ghost"
201
+ size="sm"
202
+ onClick={() => setDeletingKey(blob.key)}
203
+ >
204
+ Delete
205
+ </Button>
206
+ </div>
207
+ </TableCell>
208
+ </TableRow>
209
+ ))}
210
+ </TableBody>
211
+ </Table>
212
+ </div>
213
+
214
+ {/* Pagination */}
215
+ <div className="flex items-center justify-between px-2 py-2">
216
+ <Button
217
+ variant="ghost"
218
+ size="sm"
219
+ disabled={cursorHistory.length === 0}
220
+ onClick={handlePrevPage}
221
+ >
222
+ Previous
223
+ </Button>
224
+ <Button
225
+ variant="ghost"
226
+ size="sm"
227
+ disabled={!data?.truncated}
228
+ onClick={handleNextPage}
229
+ >
230
+ Next
231
+ </Button>
232
+ </div>
233
+ </>
234
+ )}
235
+ </SectionCard>
236
+
237
+ {/* Delete confirmation dialog */}
238
+ <Dialog
239
+ open={deletingKey !== null}
240
+ onOpenChange={() => setDeletingKey(null)}
241
+ >
242
+ <DialogContent>
243
+ <DialogHeader>
244
+ <DialogTitle>Delete Storage Item</DialogTitle>
245
+ </DialogHeader>
246
+ <div className="px-5 py-4 flex flex-col gap-4">
247
+ <p className="font-mono text-[11px] text-neutral-300">
248
+ Are you sure you want to delete{' '}
249
+ <span className="font-mono text-white">{deletingKey}</span>?
250
+ </p>
251
+ <p className="font-mono text-[10px] text-neutral-500">
252
+ This action cannot be undone.
253
+ </p>
254
+ </div>
255
+ <DialogFooter>
256
+ <Button variant="default" onClick={() => setDeletingKey(null)}>
257
+ Cancel
258
+ </Button>
259
+ <Button
260
+ variant="destructive"
261
+ onClick={handleDelete}
262
+ disabled={deleteMutation.isPending}
263
+ >
264
+ {deleteMutation.isPending ? (
265
+ <>
266
+ <Spinner size="sm" /> Deleting...
267
+ </>
268
+ ) : (
269
+ 'Delete'
270
+ )}
271
+ </Button>
272
+ </DialogFooter>
273
+ </DialogContent>
274
+ </Dialog>
275
+ </div>
276
+ );
277
+ }
@@ -164,8 +164,11 @@ export function Stream({ initialSelectedEntryId }: StreamProps = {}) {
164
164
  const { range, setRange } = useTimeRangeState();
165
165
  const pageSize = preferences.pageSize;
166
166
  const refreshIntervalMs = preferences.refreshInterval * 1000;
167
- const traceUrlTemplate: string | undefined = import.meta.env
168
- ?.VITE_CONSOLE_TRACE_URL_TEMPLATE;
167
+ const traceUrlTemplate: string | undefined = (
168
+ import.meta as ImportMeta & {
169
+ env?: { VITE_CONSOLE_TRACE_URL_TEMPLATE?: string };
170
+ }
171
+ ).env?.VITE_CONSOLE_TRACE_URL_TEMPLATE;
169
172
 
170
173
  const [viewMode, setViewMode] = useState<ViewMode>(() => {
171
174
  if (initialSelectedEntryId?.startsWith('#')) return 'commits';
@@ -268,7 +271,7 @@ export function Stream({ initialSelectedEntryId }: StreamProps = {}) {
268
271
  selectedEvent?.traceId ?? null,
269
272
  selectedEvent?.spanId ?? null
270
273
  ),
271
- [selectedEvent?.spanId, selectedEvent?.traceId]
274
+ [selectedEvent?.spanId, selectedEvent?.traceId, traceUrlTemplate]
272
275
  );
273
276
 
274
277
  useEffect(() => {
@@ -2,4 +2,5 @@ export * from './Command';
2
2
  export * from './Config';
3
3
  export * from './Fleet';
4
4
  export * from './Ops';
5
+ export * from './Storage';
5
6
  export * from './Stream';
package/src/routeTree.ts CHANGED
@@ -5,6 +5,7 @@ import { Route as indexRoute } from './routes/index';
5
5
  import { Route as investigateCommitRoute } from './routes/investigate-commit';
6
6
  import { Route as investigateEventRoute } from './routes/investigate-event';
7
7
  import { Route as opsRoute } from './routes/ops';
8
+ import { Route as storageRoute } from './routes/storage';
8
9
  import { Route as streamRoute } from './routes/stream';
9
10
 
10
11
  export const routeTree = rootRoute.addChildren([
@@ -14,5 +15,6 @@ export const routeTree = rootRoute.addChildren([
14
15
  investigateEventRoute,
15
16
  fleetRoute,
16
17
  opsRoute,
18
+ storageRoute,
17
19
  configRoute,
18
20
  ]);
@@ -0,0 +1,9 @@
1
+ import { createRoute } from '@tanstack/react-router';
2
+ import { Storage } from '../pages';
3
+ import { Route as rootRoute } from './__root';
4
+
5
+ export const Route = createRoute({
6
+ getParentRoute: () => rootRoute,
7
+ path: '/storage',
8
+ component: Storage,
9
+ });
@@ -125,7 +125,7 @@ function renderIndexHtml(args: {
125
125
  const resolvedServerUrl = args.prefill?.serverUrl ?? '';
126
126
  const resolvedToken = args.prefill?.token ?? '';
127
127
 
128
- return withMetaTag(
128
+ const withMeta = withMetaTag(
129
129
  withMetaTag(
130
130
  withMetaTag(args.template, CONSOLE_BASEPATH_META, resolvedBasePath),
131
131
  CONSOLE_SERVER_URL_META,
@@ -134,6 +134,17 @@ function renderIndexHtml(args: {
134
134
  CONSOLE_TOKEN_META,
135
135
  resolvedToken
136
136
  );
137
+
138
+ if (resolvedBasePath === '/') {
139
+ return withMeta;
140
+ }
141
+
142
+ const mountPrefix = resolvedBasePath.replace(/\/+$/g, '');
143
+ return withMeta.replace(
144
+ /(src|href)=("|')\/assets\/([^"']+)("|')/g,
145
+ (_match, attribute, openQuote, assetPath, closeQuote) =>
146
+ `${attribute}=${openQuote}${mountPrefix}/assets/${assetPath}${closeQuote}`
147
+ );
137
148
  }
138
149
 
139
150
  export function createConsoleStaticResponder(
@@ -1,4 +1,7 @@
1
- @import '@syncular/ui/styles.css';
1
+ @import '@syncular/ui/styles.source.css';
2
+
3
+ /* Scan console page source files for Tailwind class usage */
4
+ @source "..";
2
5
 
3
6
  :where(.syncular-console-root) {
4
7
  color-scheme: dark;