@syncular/console 0.0.5-42 → 0.0.6-101
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/dist/App.d.ts +3 -7
- package/dist/App.d.ts.map +1 -1
- package/dist/App.js +3 -3
- package/dist/App.js.map +1 -1
- package/dist/hooks/ConnectionContext.d.ts +4 -1
- package/dist/hooks/ConnectionContext.d.ts.map +1 -1
- package/dist/hooks/ConnectionContext.js +111 -14
- package/dist/hooks/ConnectionContext.js.map +1 -1
- package/dist/hooks/useLiveEvents.d.ts.map +1 -1
- package/dist/hooks/useLiveEvents.js +22 -3
- package/dist/hooks/useLiveEvents.js.map +1 -1
- package/dist/layout.d.ts +4 -1
- package/dist/layout.d.ts.map +1 -1
- package/dist/layout.js +7 -7
- package/dist/layout.js.map +1 -1
- package/dist/mount.d.ts +1 -0
- package/dist/mount.d.ts.map +1 -1
- package/dist/mount.js +1 -1
- package/dist/mount.js.map +1 -1
- package/dist/pages/Config.js +22 -14
- package/dist/pages/Config.js.map +1 -1
- package/dist/pages/Ops.js.map +1 -1
- package/dist/pages/Stream.d.ts.map +1 -1
- package/dist/pages/Stream.js +2 -3
- package/dist/pages/Stream.js.map +1 -1
- package/dist/routeTree.d.ts +1 -1
- package/dist/routeTree.d.ts.map +1 -1
- package/dist/routes/__root.d.ts +1 -1
- package/dist/routes/__root.d.ts.map +1 -1
- package/dist/routes/config.d.ts +1 -1
- package/dist/routes/config.d.ts.map +1 -1
- package/dist/routes/fleet.d.ts +1 -1
- package/dist/routes/fleet.d.ts.map +1 -1
- package/dist/routes/index.d.ts +1 -1
- package/dist/routes/index.d.ts.map +1 -1
- package/dist/routes/investigate-commit.d.ts +1 -1
- package/dist/routes/investigate-commit.d.ts.map +1 -1
- package/dist/routes/investigate-event.d.ts +1 -1
- package/dist/routes/investigate-event.d.ts.map +1 -1
- package/dist/routes/ops.d.ts +1 -1
- package/dist/routes/ops.d.ts.map +1 -1
- package/dist/routes/storage.d.ts +1 -1
- package/dist/routes/storage.d.ts.map +1 -1
- package/dist/routes/stream.d.ts +1 -1
- package/dist/routes/stream.d.ts.map +1 -1
- package/dist/static-server.d.ts.map +1 -1
- package/dist/static-server.js +6 -1
- package/dist/static-server.js.map +1 -1
- package/dist/styles.css +1 -1
- package/package.json +8 -8
- package/src/App.tsx +12 -10
- package/src/__tests__/static-server.test.ts +193 -0
- package/src/hooks/ConnectionContext.tsx +130 -14
- package/src/hooks/useLiveEvents.ts +27 -3
- package/src/layout.tsx +27 -4
- package/src/mount.tsx +6 -1
- package/src/pages/Config.tsx +55 -48
- package/src/pages/Stream.tsx +6 -3
- package/src/static-server.ts +12 -1
- package/web-dist/assets/index-BuP84qca.js +86 -0
- package/web-dist/assets/index-D_fQabjS.css +1 -0
- package/web-dist/console.css +1 -1
- package/web-dist/index.html +2 -2
- package/web-dist/site.webmanifest +2 -2
- package/web-dist/assets/index-BhPtRvK0.css +0 -1
- package/web-dist/assets/index-Fyq7dTrO.js +0 -86
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
useContext,
|
|
11
11
|
useEffect,
|
|
12
12
|
useMemo,
|
|
13
|
+
useRef,
|
|
13
14
|
useState,
|
|
14
15
|
} from 'react';
|
|
15
16
|
import {
|
|
@@ -17,7 +18,58 @@ import {
|
|
|
17
18
|
createConsoleClient,
|
|
18
19
|
testConnection,
|
|
19
20
|
} from '../lib/api';
|
|
20
|
-
|
|
21
|
+
|
|
22
|
+
export type ConnectionStorageMode = 'memory' | 'session' | 'local';
|
|
23
|
+
|
|
24
|
+
const CONNECTION_STORAGE_KEY = 'sync-console-connection';
|
|
25
|
+
|
|
26
|
+
function normalizeConfig(
|
|
27
|
+
config: ConnectionConfig | null | undefined
|
|
28
|
+
): ConnectionConfig | null {
|
|
29
|
+
if (!config) return null;
|
|
30
|
+
const serverUrl = config.serverUrl?.trim() ?? '';
|
|
31
|
+
const token = config.token?.trim() ?? '';
|
|
32
|
+
if (!serverUrl || !token) return null;
|
|
33
|
+
return { serverUrl, token };
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function getStorageForMode(mode: ConnectionStorageMode): Storage | null {
|
|
37
|
+
if (typeof window === 'undefined') return null;
|
|
38
|
+
if (mode === 'local') return window.localStorage;
|
|
39
|
+
if (mode === 'session') return window.sessionStorage;
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function readStoredConfig(
|
|
44
|
+
mode: ConnectionStorageMode
|
|
45
|
+
): ConnectionConfig | null {
|
|
46
|
+
const storage = getStorageForMode(mode);
|
|
47
|
+
if (!storage) return null;
|
|
48
|
+
try {
|
|
49
|
+
const raw = storage.getItem(CONNECTION_STORAGE_KEY);
|
|
50
|
+
if (!raw) return null;
|
|
51
|
+
return normalizeConfig(JSON.parse(raw) as ConnectionConfig);
|
|
52
|
+
} catch {
|
|
53
|
+
return null;
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function writeStoredConfig(
|
|
58
|
+
mode: ConnectionStorageMode,
|
|
59
|
+
config: ConnectionConfig | null
|
|
60
|
+
): void {
|
|
61
|
+
const storage = getStorageForMode(mode);
|
|
62
|
+
if (!storage) return;
|
|
63
|
+
try {
|
|
64
|
+
if (!config) {
|
|
65
|
+
storage.removeItem(CONNECTION_STORAGE_KEY);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
storage.setItem(CONNECTION_STORAGE_KEY, JSON.stringify(config));
|
|
69
|
+
} catch {
|
|
70
|
+
// Ignore storage write errors.
|
|
71
|
+
}
|
|
72
|
+
}
|
|
21
73
|
|
|
22
74
|
interface ConnectionState {
|
|
23
75
|
isConnected: boolean;
|
|
@@ -44,6 +96,8 @@ interface ConnectionContextValue {
|
|
|
44
96
|
interface ConnectionProviderProps {
|
|
45
97
|
children: ReactNode;
|
|
46
98
|
defaultConfig?: ConnectionConfig | null;
|
|
99
|
+
autoConnect?: boolean;
|
|
100
|
+
storageMode?: ConnectionStorageMode;
|
|
47
101
|
}
|
|
48
102
|
|
|
49
103
|
const ConnectionContext = createContext<ConnectionContextValue | null>(null);
|
|
@@ -51,10 +105,35 @@ const ConnectionContext = createContext<ConnectionContextValue | null>(null);
|
|
|
51
105
|
export function ConnectionProvider({
|
|
52
106
|
children,
|
|
53
107
|
defaultConfig = null,
|
|
108
|
+
autoConnect = false,
|
|
109
|
+
storageMode = 'session',
|
|
54
110
|
}: ConnectionProviderProps) {
|
|
55
|
-
const [config,
|
|
56
|
-
|
|
57
|
-
|
|
111
|
+
const [config, setConfigState] = useState<ConnectionConfig | null>(() =>
|
|
112
|
+
readStoredConfig(storageMode)
|
|
113
|
+
);
|
|
114
|
+
|
|
115
|
+
useEffect(() => {
|
|
116
|
+
const storedConfig = readStoredConfig(storageMode);
|
|
117
|
+
setConfigState(storedConfig);
|
|
118
|
+
}, [storageMode]);
|
|
119
|
+
|
|
120
|
+
useEffect(() => {
|
|
121
|
+
if (typeof window === 'undefined') return;
|
|
122
|
+
if (storageMode !== 'local') {
|
|
123
|
+
window.localStorage.removeItem(CONNECTION_STORAGE_KEY);
|
|
124
|
+
}
|
|
125
|
+
if (storageMode === 'memory') {
|
|
126
|
+
window.sessionStorage.removeItem(CONNECTION_STORAGE_KEY);
|
|
127
|
+
}
|
|
128
|
+
}, [storageMode]);
|
|
129
|
+
|
|
130
|
+
const setConfigStorage = useCallback(
|
|
131
|
+
(nextConfig: ConnectionConfig | null) => {
|
|
132
|
+
const normalized = normalizeConfig(nextConfig);
|
|
133
|
+
setConfigState(normalized);
|
|
134
|
+
writeStoredConfig(storageMode, normalized);
|
|
135
|
+
},
|
|
136
|
+
[storageMode]
|
|
58
137
|
);
|
|
59
138
|
|
|
60
139
|
const [state, setState] = useState<ConnectionState>({
|
|
@@ -63,6 +142,7 @@ export function ConnectionProvider({
|
|
|
63
142
|
error: null,
|
|
64
143
|
client: null,
|
|
65
144
|
});
|
|
145
|
+
const lastAutoConnectConfigKeyRef = useRef<string | null>(null);
|
|
66
146
|
|
|
67
147
|
// Resolve initial config: saved config -> provided defaults
|
|
68
148
|
useEffect(() => {
|
|
@@ -90,18 +170,16 @@ export function ConnectionProvider({
|
|
|
90
170
|
return false;
|
|
91
171
|
}
|
|
92
172
|
|
|
93
|
-
const normalizedConfig
|
|
94
|
-
serverUrl: effectiveConfig.serverUrl?.trim() ?? '',
|
|
95
|
-
token: effectiveConfig.token?.trim() ?? '',
|
|
96
|
-
};
|
|
173
|
+
const normalizedConfig = normalizeConfig(effectiveConfig);
|
|
97
174
|
|
|
98
175
|
// Validate config has required fields
|
|
99
|
-
if (!normalizedConfig
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
176
|
+
if (!normalizedConfig) {
|
|
177
|
+
const hasServerUrl =
|
|
178
|
+
(effectiveConfig.serverUrl?.trim() ?? '').length > 0;
|
|
179
|
+
setState((s) => ({
|
|
180
|
+
...s,
|
|
181
|
+
error: hasServerUrl ? 'Token is required' : 'Server URL is required',
|
|
182
|
+
}));
|
|
105
183
|
return false;
|
|
106
184
|
}
|
|
107
185
|
|
|
@@ -182,6 +260,32 @@ export function ConnectionProvider({
|
|
|
182
260
|
[config, setConfig, state, connect, disconnect, clearError]
|
|
183
261
|
);
|
|
184
262
|
|
|
263
|
+
useEffect(() => {
|
|
264
|
+
if (!autoConnect || state.isConnected || state.isConnecting) {
|
|
265
|
+
return;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
const candidate = config ?? defaultConfig;
|
|
269
|
+
const key = normalizeConfigKey(candidate);
|
|
270
|
+
if (!candidate || !key) {
|
|
271
|
+
return;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
if (lastAutoConnectConfigKeyRef.current === key) {
|
|
275
|
+
return;
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
lastAutoConnectConfigKeyRef.current = key;
|
|
279
|
+
void connect(candidate, { persistOverride: true });
|
|
280
|
+
}, [
|
|
281
|
+
autoConnect,
|
|
282
|
+
config,
|
|
283
|
+
defaultConfig,
|
|
284
|
+
state.isConnected,
|
|
285
|
+
state.isConnecting,
|
|
286
|
+
connect,
|
|
287
|
+
]);
|
|
288
|
+
|
|
185
289
|
return (
|
|
186
290
|
<ConnectionContext.Provider value={value}>
|
|
187
291
|
{children}
|
|
@@ -189,6 +293,18 @@ export function ConnectionProvider({
|
|
|
189
293
|
);
|
|
190
294
|
}
|
|
191
295
|
|
|
296
|
+
function normalizeConfigKey(config: ConnectionConfig | null): string | null {
|
|
297
|
+
if (!config) {
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
const serverUrl = config.serverUrl?.trim() ?? '';
|
|
301
|
+
const token = config.token?.trim() ?? '';
|
|
302
|
+
if (!serverUrl || !token) {
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
return `${serverUrl}\u0000${token}`;
|
|
306
|
+
}
|
|
307
|
+
|
|
192
308
|
export function useConnection(): ConnectionContextValue {
|
|
193
309
|
const context = useContext(ConnectionContext);
|
|
194
310
|
if (!context) {
|
|
@@ -243,7 +243,6 @@ export function useLiveEvents(
|
|
|
243
243
|
: baseUrl.pathname;
|
|
244
244
|
baseUrl.pathname = `${normalizedPath}/console/events/live`;
|
|
245
245
|
baseUrl.search = '';
|
|
246
|
-
baseUrl.searchParams.set('token', config.token);
|
|
247
246
|
if (lastEventTimestampRef.current) {
|
|
248
247
|
baseUrl.searchParams.set('since', lastEventTimestampRef.current);
|
|
249
248
|
}
|
|
@@ -266,8 +265,21 @@ export function useLiveEvents(
|
|
|
266
265
|
return;
|
|
267
266
|
}
|
|
268
267
|
reconnectAttemptsRef.current = 0;
|
|
269
|
-
markActivity();
|
|
270
268
|
setError(null);
|
|
269
|
+
setConnectionState('connecting');
|
|
270
|
+
setIsConnected(false);
|
|
271
|
+
|
|
272
|
+
try {
|
|
273
|
+
ws.send(
|
|
274
|
+
JSON.stringify({
|
|
275
|
+
type: 'auth',
|
|
276
|
+
token: config.token,
|
|
277
|
+
})
|
|
278
|
+
);
|
|
279
|
+
} catch {
|
|
280
|
+
ws.close();
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
271
283
|
|
|
272
284
|
clearStaleInterval();
|
|
273
285
|
staleCheckIntervalRef.current = setInterval(() => {
|
|
@@ -304,7 +316,19 @@ export function useLiveEvents(
|
|
|
304
316
|
markActivity();
|
|
305
317
|
|
|
306
318
|
// Skip control events
|
|
307
|
-
if (
|
|
319
|
+
if (
|
|
320
|
+
eventType === 'connected' ||
|
|
321
|
+
eventType === 'heartbeat' ||
|
|
322
|
+
eventType === 'auth_required'
|
|
323
|
+
) {
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
if (eventType === 'error') {
|
|
327
|
+
const message =
|
|
328
|
+
typeof data.message === 'string'
|
|
329
|
+
? data.message
|
|
330
|
+
: 'Live events authentication failed';
|
|
331
|
+
setError(new Error(message));
|
|
308
332
|
return;
|
|
309
333
|
}
|
|
310
334
|
|
package/src/layout.tsx
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import {
|
|
2
|
+
Badge,
|
|
2
3
|
BottomBar,
|
|
3
4
|
Button,
|
|
4
5
|
ConnectionStatusBadge,
|
|
5
6
|
Input,
|
|
6
7
|
NavPill,
|
|
7
8
|
NavPillGroup,
|
|
9
|
+
navActionLinkClassName,
|
|
8
10
|
SyncularBrand,
|
|
9
11
|
TopNavigation,
|
|
10
12
|
} from '@syncular/ui';
|
|
11
13
|
import { Link, Outlet, useRouterState } from '@tanstack/react-router';
|
|
12
|
-
import { Settings } from 'lucide-react';
|
|
14
|
+
import { ArrowLeft, Settings } from 'lucide-react';
|
|
15
|
+
import type { ReactNode } from 'react';
|
|
13
16
|
import { useMemo } from 'react';
|
|
14
17
|
import { useConnection } from './hooks/ConnectionContext';
|
|
15
18
|
import { useStats } from './hooks/useConsoleApi';
|
|
@@ -20,6 +23,8 @@ import { SYNCULAR_CONSOLE_ROOT_CLASS } from './theme-scope';
|
|
|
20
23
|
|
|
21
24
|
interface ConsoleLayoutProps {
|
|
22
25
|
basePath?: string;
|
|
26
|
+
appHref?: string;
|
|
27
|
+
modeBadge?: ReactNode;
|
|
23
28
|
}
|
|
24
29
|
|
|
25
30
|
type ConsoleNavSuffix =
|
|
@@ -56,7 +61,11 @@ function resolvePath(basePath: string, suffix: ConsoleNavSuffix): string {
|
|
|
56
61
|
return suffix ? `${basePath}${suffix}` : basePath;
|
|
57
62
|
}
|
|
58
63
|
|
|
59
|
-
export function ConsoleLayout({
|
|
64
|
+
export function ConsoleLayout({
|
|
65
|
+
basePath,
|
|
66
|
+
appHref,
|
|
67
|
+
modeBadge,
|
|
68
|
+
}: ConsoleLayoutProps) {
|
|
60
69
|
const { connect, config, isConnected, isConnecting } = useConnection();
|
|
61
70
|
const { preferences } = usePreferences();
|
|
62
71
|
const { instanceId, rawInstanceId, setInstanceId, clearInstanceId } =
|
|
@@ -134,6 +143,14 @@ export function ConsoleLayout({ basePath }: ConsoleLayoutProps) {
|
|
|
134
143
|
}
|
|
135
144
|
right={
|
|
136
145
|
<div className="flex items-center gap-2">
|
|
146
|
+
{modeBadge ? (
|
|
147
|
+
<Badge
|
|
148
|
+
variant="flow"
|
|
149
|
+
className="hidden md:inline-flex px-2 py-1 text-[10px]"
|
|
150
|
+
>
|
|
151
|
+
{modeBadge}
|
|
152
|
+
</Badge>
|
|
153
|
+
) : null}
|
|
137
154
|
<div className="flex items-center gap-1">
|
|
138
155
|
<span className="font-mono text-[9px] text-neutral-500 uppercase tracking-wide">
|
|
139
156
|
Instance
|
|
@@ -186,14 +203,20 @@ export function ConsoleLayout({ basePath }: ConsoleLayoutProps) {
|
|
|
186
203
|
variant={pathname === configPath ? 'secondary' : 'ghost'}
|
|
187
204
|
size="icon"
|
|
188
205
|
>
|
|
189
|
-
<Settings />
|
|
206
|
+
<Settings className="h-3 w-3" />
|
|
190
207
|
</Button>
|
|
191
208
|
</Link>
|
|
209
|
+
{appHref ? (
|
|
210
|
+
<a href={appHref} className={navActionLinkClassName}>
|
|
211
|
+
<ArrowLeft className="h-3 w-3" />
|
|
212
|
+
Go to app
|
|
213
|
+
</a>
|
|
214
|
+
) : null}
|
|
192
215
|
</div>
|
|
193
216
|
}
|
|
194
217
|
/>
|
|
195
218
|
|
|
196
|
-
<main className="flex-1 overflow-auto
|
|
219
|
+
<main className="flex-1 overflow-auto pb-[32px]">
|
|
197
220
|
<div className="min-h-full">
|
|
198
221
|
{isConnected || pathname === configPath ? (
|
|
199
222
|
<div key={pathname} style={{ animation: 'pageIn 0.3s ease-out' }}>
|
package/src/mount.tsx
CHANGED
|
@@ -7,6 +7,7 @@ interface MountSyncularConsoleOptions {
|
|
|
7
7
|
strictMode?: boolean;
|
|
8
8
|
basePath?: SyncularConsoleProps['basePath'];
|
|
9
9
|
defaultConfig?: SyncularConsoleProps['defaultConfig'];
|
|
10
|
+
autoConnect?: SyncularConsoleProps['autoConnect'];
|
|
10
11
|
}
|
|
11
12
|
|
|
12
13
|
function resolveContainer(containerOrSelector: Element | string): Element {
|
|
@@ -33,7 +34,11 @@ export function mountSyncularConsoleApp(
|
|
|
33
34
|
|
|
34
35
|
const root = createRoot(container);
|
|
35
36
|
const app = (
|
|
36
|
-
<App
|
|
37
|
+
<App
|
|
38
|
+
basePath={options.basePath}
|
|
39
|
+
defaultConfig={options.defaultConfig}
|
|
40
|
+
autoConnect={options.autoConnect}
|
|
41
|
+
/>
|
|
37
42
|
);
|
|
38
43
|
|
|
39
44
|
if (options.strictMode === false) {
|
package/src/pages/Config.tsx
CHANGED
|
@@ -80,15 +80,24 @@ function ConnectionTab() {
|
|
|
80
80
|
|
|
81
81
|
useEffect(() => {
|
|
82
82
|
const params = new URLSearchParams(window.location.search);
|
|
83
|
-
const urlToken = params.get('token');
|
|
84
83
|
const urlServer = params.get('server');
|
|
84
|
+
let shouldReplaceUrl = false;
|
|
85
85
|
|
|
86
|
-
if (urlToken) {
|
|
87
|
-
setToken(urlToken);
|
|
88
|
-
window.history.replaceState({}, '', window.location.pathname);
|
|
89
|
-
}
|
|
90
86
|
if (urlServer) {
|
|
91
87
|
setServerUrl(urlServer);
|
|
88
|
+
params.delete('server');
|
|
89
|
+
shouldReplaceUrl = true;
|
|
90
|
+
}
|
|
91
|
+
if (params.has('token')) {
|
|
92
|
+
params.delete('token');
|
|
93
|
+
shouldReplaceUrl = true;
|
|
94
|
+
}
|
|
95
|
+
if (shouldReplaceUrl) {
|
|
96
|
+
const nextQuery = params.toString();
|
|
97
|
+
const nextUrl = nextQuery
|
|
98
|
+
? `${window.location.pathname}?${nextQuery}`
|
|
99
|
+
: window.location.pathname;
|
|
100
|
+
window.history.replaceState({}, '', nextUrl);
|
|
92
101
|
}
|
|
93
102
|
}, []);
|
|
94
103
|
|
|
@@ -555,7 +564,7 @@ function ApiKeysTab() {
|
|
|
555
564
|
<Table>
|
|
556
565
|
<TableHeader>
|
|
557
566
|
<TableRow>
|
|
558
|
-
<TableHead>
|
|
567
|
+
<TableHead className="w-[28px]">
|
|
559
568
|
<Checkbox
|
|
560
569
|
checked={allSelectableChecked}
|
|
561
570
|
indeterminate={
|
|
@@ -573,16 +582,15 @@ function ApiKeysTab() {
|
|
|
573
582
|
aria-label="Select all active keys"
|
|
574
583
|
/>
|
|
575
584
|
</TableHead>
|
|
576
|
-
<TableHead>NAME</TableHead>
|
|
577
|
-
<TableHead>TYPE</TableHead>
|
|
578
|
-
<TableHead>KEY PREFIX</TableHead>
|
|
579
|
-
<TableHead>ACTOR</TableHead>
|
|
580
|
-
<TableHead>SCOPES</TableHead>
|
|
581
|
-
<TableHead>CREATED</TableHead>
|
|
582
|
-
<TableHead>LAST USED</TableHead>
|
|
583
|
-
<TableHead>EXPIRES</TableHead>
|
|
584
|
-
<TableHead>STATUS</TableHead>
|
|
585
|
-
<TableHead>ACTIONS</TableHead>
|
|
585
|
+
<TableHead className="w-[100px]">NAME</TableHead>
|
|
586
|
+
<TableHead className="w-[55px]">TYPE</TableHead>
|
|
587
|
+
<TableHead className="w-[90px]">KEY PREFIX</TableHead>
|
|
588
|
+
<TableHead className="w-[80px]">ACTOR</TableHead>
|
|
589
|
+
<TableHead className="w-[100px]">SCOPES</TableHead>
|
|
590
|
+
<TableHead className="w-[120px]">CREATED</TableHead>
|
|
591
|
+
<TableHead className="w-[120px]">LAST USED</TableHead>
|
|
592
|
+
<TableHead className="w-[120px]">EXPIRES</TableHead>
|
|
593
|
+
<TableHead className="flex-1">STATUS</TableHead>
|
|
586
594
|
</TableRow>
|
|
587
595
|
</TableHeader>
|
|
588
596
|
<TableBody>
|
|
@@ -593,8 +601,8 @@ function ApiKeysTab() {
|
|
|
593
601
|
);
|
|
594
602
|
|
|
595
603
|
return (
|
|
596
|
-
<TableRow key={apiKey.keyId}>
|
|
597
|
-
<TableCell>
|
|
604
|
+
<TableRow key={apiKey.keyId} className="group relative">
|
|
605
|
+
<TableCell className="w-[28px]">
|
|
598
606
|
<Checkbox
|
|
599
607
|
checked={selectedKeyIds.includes(apiKey.keyId)}
|
|
600
608
|
onCheckedChange={(checked) => {
|
|
@@ -610,8 +618,10 @@ function ApiKeysTab() {
|
|
|
610
618
|
disabled={apiKey.revokedAt !== null}
|
|
611
619
|
/>
|
|
612
620
|
</TableCell>
|
|
613
|
-
<TableCell className="font-medium">
|
|
614
|
-
|
|
621
|
+
<TableCell className="w-[100px] font-medium">
|
|
622
|
+
{apiKey.name}
|
|
623
|
+
</TableCell>
|
|
624
|
+
<TableCell className="w-[55px]">
|
|
615
625
|
<Badge
|
|
616
626
|
variant={
|
|
617
627
|
apiKey.keyType === 'admin'
|
|
@@ -624,63 +634,60 @@ function ApiKeysTab() {
|
|
|
624
634
|
{apiKey.keyType}
|
|
625
635
|
</Badge>
|
|
626
636
|
</TableCell>
|
|
627
|
-
<TableCell>
|
|
637
|
+
<TableCell className="w-[90px]">
|
|
628
638
|
<code className="font-mono text-[11px]">
|
|
629
639
|
{apiKey.keyPrefix}...
|
|
630
640
|
</code>
|
|
631
641
|
</TableCell>
|
|
632
|
-
<TableCell className="text-neutral-500">
|
|
642
|
+
<TableCell className="w-[80px] text-neutral-500">
|
|
633
643
|
{apiKey.actorId ?? '-'}
|
|
634
644
|
</TableCell>
|
|
635
|
-
<TableCell className="
|
|
645
|
+
<TableCell className="w-[100px] text-neutral-500">
|
|
636
646
|
<code className="font-mono text-[10px]">
|
|
637
647
|
{summarizeScopeKeys(apiKey.scopeKeys)}
|
|
638
648
|
</code>
|
|
639
649
|
</TableCell>
|
|
640
|
-
<TableCell className="text-neutral-500">
|
|
650
|
+
<TableCell className="w-[120px] text-neutral-500">
|
|
641
651
|
{formatOptionalDateTime(apiKey.createdAt)}
|
|
642
652
|
</TableCell>
|
|
643
|
-
<TableCell className="text-neutral-500">
|
|
653
|
+
<TableCell className="w-[120px] text-neutral-500">
|
|
644
654
|
{formatOptionalDateTime(apiKey.lastUsedAt)}
|
|
645
655
|
</TableCell>
|
|
646
|
-
<TableCell className="text-neutral-500">
|
|
656
|
+
<TableCell className="w-[120px] text-neutral-500">
|
|
647
657
|
{formatOptionalDateTime(apiKey.expiresAt)}
|
|
648
658
|
</TableCell>
|
|
649
|
-
<TableCell>
|
|
659
|
+
<TableCell className="flex-1">
|
|
650
660
|
<Badge
|
|
651
661
|
variant={getApiKeyStatusBadgeVariant(lifecycleStatus)}
|
|
652
662
|
>
|
|
653
663
|
{lifecycleStatus}
|
|
654
664
|
</Badge>
|
|
655
665
|
</TableCell>
|
|
656
|
-
|
|
657
|
-
<div className="flex items-center gap-1">
|
|
658
|
-
<
|
|
659
|
-
|
|
660
|
-
size="sm"
|
|
666
|
+
{apiKey.revokedAt === null && (
|
|
667
|
+
<div className="absolute right-2 top-1/2 -translate-y-1/2 flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity">
|
|
668
|
+
<button
|
|
669
|
+
type="button"
|
|
661
670
|
onClick={() => setStagingRotateKey(apiKey)}
|
|
662
|
-
|
|
671
|
+
className="px-1.5 py-0.5 rounded text-[9px] font-mono text-neutral-600 hover:text-white hover:bg-white/[0.05] cursor-pointer transition-colors"
|
|
663
672
|
>
|
|
664
|
-
|
|
665
|
-
</
|
|
666
|
-
<
|
|
667
|
-
|
|
668
|
-
size="sm"
|
|
673
|
+
stage
|
|
674
|
+
</button>
|
|
675
|
+
<button
|
|
676
|
+
type="button"
|
|
669
677
|
onClick={() => setRotatingKeyId(apiKey.keyId)}
|
|
670
|
-
|
|
678
|
+
className="px-1.5 py-0.5 rounded text-[9px] font-mono text-neutral-600 hover:text-white hover:bg-white/[0.05] cursor-pointer transition-colors"
|
|
671
679
|
>
|
|
672
|
-
|
|
673
|
-
</
|
|
674
|
-
<
|
|
675
|
-
|
|
676
|
-
size="sm"
|
|
680
|
+
rotate
|
|
681
|
+
</button>
|
|
682
|
+
<button
|
|
683
|
+
type="button"
|
|
677
684
|
onClick={() => setRevokingKeyId(apiKey.keyId)}
|
|
678
|
-
|
|
685
|
+
className="px-1.5 py-0.5 rounded text-[9px] font-mono text-neutral-600 hover:text-offline hover:bg-offline/10 cursor-pointer transition-colors"
|
|
679
686
|
>
|
|
680
|
-
|
|
681
|
-
</
|
|
687
|
+
revoke
|
|
688
|
+
</button>
|
|
682
689
|
</div>
|
|
683
|
-
|
|
690
|
+
)}
|
|
684
691
|
</TableRow>
|
|
685
692
|
);
|
|
686
693
|
})}
|
package/src/pages/Stream.tsx
CHANGED
|
@@ -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 =
|
|
168
|
-
|
|
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(() => {
|
package/src/static-server.ts
CHANGED
|
@@ -125,7 +125,7 @@ function renderIndexHtml(args: {
|
|
|
125
125
|
const resolvedServerUrl = args.prefill?.serverUrl ?? '';
|
|
126
126
|
const resolvedToken = args.prefill?.token ?? '';
|
|
127
127
|
|
|
128
|
-
|
|
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(
|