@agent-relay/dashboard 2.0.82 → 2.0.84
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/out/404.html +1 -1
- package/out/_next/static/chunks/1028-da5d75e35d1420f1.js +1 -0
- package/out/_next/static/chunks/1528-78b17000a7e10bc6.js +2 -0
- package/out/_next/static/chunks/1695-4a5d33ba715e09b4.js +1 -0
- package/out/_next/static/chunks/1705-36c2180d00a4a569.js +1 -0
- package/out/_next/static/chunks/1dd3208c-e1f87c7b3dc1a820.js +1 -0
- package/out/_next/static/chunks/3663-47290254b8f6f5dd.js +1 -0
- package/out/_next/static/chunks/3677-4b225baf4801d9b9.js +73 -0
- package/out/_next/static/chunks/5118-7e8ada2df38eef07.js +1 -0
- package/out/_next/static/chunks/5888-15cbe97c90ed5fae.js +1 -0
- package/out/_next/static/chunks/6773-a45343a98df3abb5.js +1 -0
- package/out/_next/static/chunks/6940-b824612b605e79b3.js +9 -0
- package/out/_next/static/chunks/7894-f4a15249082a680d.js +1 -0
- package/out/_next/static/chunks/9175-b3617c1e5cbfed0e.js +1 -0
- package/out/_next/static/chunks/9372-1a804b8d08c7a236.js +1 -0
- package/out/_next/static/chunks/{ab6c8a12-0a58072fbb505134.js → ab6c8a12-91438a812d94ecf0.js} +1 -1
- package/out/_next/static/chunks/app/_not-found/page-8e8842f82d204726.js +1 -0
- package/out/_next/static/chunks/app/about/page-b78577a7da8fa459.js +1 -0
- package/out/_next/static/chunks/app/app/[[...slug]]/page-3dffd65b6344f53e.js +1 -0
- package/out/_next/static/chunks/app/app/onboarding/page-b89be9aa6264a5e1.js +1 -0
- package/out/_next/static/chunks/app/blog/go-to-bed-wake-up-to-a-finished-product/page-fbd00893ef69e499.js +1 -0
- package/out/_next/static/chunks/app/blog/let-them-cook-multi-agent-orchestration/page-de2ea13649d0b6d3.js +1 -0
- package/out/_next/static/chunks/app/blog/page-a08e263c57a156fa.js +1 -0
- package/out/_next/static/chunks/app/careers/page-02228e1d6969b232.js +1 -0
- package/out/_next/static/chunks/app/changelog/page-1b5c1d79efc6e53a.js +1 -0
- package/out/_next/static/chunks/app/cloud/link/page-99654edffffb3af2.js +1 -0
- package/out/_next/static/chunks/app/complete-profile/page-59d146e5ddeafc5c.js +1 -0
- package/out/_next/static/chunks/app/connect-repos/page-995e16a976a6632c.js +1 -0
- package/out/_next/static/chunks/app/contact/page-273396a5ad57bcee.js +1 -0
- package/out/_next/static/chunks/app/dev/cli-tools/page-a71b80dcb2d5fc8d.js +1 -0
- package/out/_next/static/chunks/app/dev/log-viewer/page-46a6151ae1be0796.js +1 -0
- package/out/_next/static/chunks/app/docs/page-7c7cb603b24b7c40.js +1 -0
- package/out/_next/static/chunks/app/history/page-0c5cab1dab4e8886.js +1 -0
- package/out/_next/static/chunks/app/layout-96d72ba8ef8a43a0.js +1 -0
- package/out/_next/static/chunks/app/login/page-0ccbab34213df842.js +1 -0
- package/out/_next/static/chunks/app/metrics/page-8616272aeab9c8b0.js +1 -0
- package/out/_next/static/chunks/app/page-09ce10603ad9a251.js +1 -0
- package/out/_next/static/chunks/app/pricing/page-91c975079120c941.js +1 -0
- package/out/_next/static/chunks/app/privacy/{page-c21d51ac2dee3a88.js → page-a49ab271cc686644.js} +1 -1
- package/out/_next/static/chunks/app/providers/{page-59114505f4353512.js → page-d775d6eb5bc29e96.js} +1 -1
- package/out/_next/static/chunks/app/providers/setup/[provider]/page-ec4ef3cd80de807e.js +1 -0
- package/out/_next/static/chunks/app/security/page-d9da9bd9191e8f95.js +1 -0
- package/out/_next/static/chunks/app/signup/page-930eca0bf5fd299d.js +1 -0
- package/out/_next/static/chunks/app/terms/page-3e4827620b98613c.js +1 -0
- package/out/_next/static/chunks/framework-648e1ae7da590300.js +1 -0
- package/out/_next/static/chunks/{main-acb1b24265295d6a.js → main-2b1990080c292d92.js} +1 -1
- package/out/_next/static/chunks/main-app-9f6b7ff9e754a8f5.js +1 -0
- package/out/_next/static/chunks/pages/_app-a077b72e02273ab1.js +1 -0
- package/out/_next/static/chunks/pages/_error-84001666436a04e4.js +1 -0
- package/out/_next/static/chunks/{webpack-dd93b81e2659669c.js → webpack-7586035f1585f2db.js} +1 -1
- package/out/_next/static/css/eb9fc69d1e3d2bed.css +1 -0
- package/out/_next/static/{IxfA6RZu4trcsEMYlkQra → g3G0LMdB7lxcrU5mdM54m}/_buildManifest.js +1 -1
- package/out/about.html +2 -2
- package/out/about.txt +2 -2
- package/out/app/onboarding.html +1 -1
- package/out/app/onboarding.txt +2 -2
- package/out/app.html +1 -1
- package/out/app.txt +2 -2
- package/out/blog/go-to-bed-wake-up-to-a-finished-product.html +3 -3
- package/out/blog/go-to-bed-wake-up-to-a-finished-product.txt +1 -1
- package/out/blog/let-them-cook-multi-agent-orchestration.html +2 -2
- package/out/blog/let-them-cook-multi-agent-orchestration.txt +2 -2
- package/out/blog.html +2 -2
- package/out/blog.txt +1 -1
- package/out/careers.html +2 -2
- package/out/careers.txt +2 -2
- package/out/changelog.html +2 -2
- package/out/changelog.txt +2 -2
- package/out/cloud/link.html +1 -1
- package/out/cloud/link.txt +2 -2
- package/out/complete-profile.html +2 -2
- package/out/complete-profile.txt +2 -2
- package/out/connect-repos.html +1 -1
- package/out/connect-repos.txt +2 -2
- package/out/contact.html +2 -2
- package/out/contact.txt +2 -2
- package/out/dev/cli-tools.html +1 -0
- package/out/dev/cli-tools.txt +7 -0
- package/out/dev/log-viewer.html +23 -0
- package/out/dev/log-viewer.txt +7 -0
- package/out/docs.html +2 -2
- package/out/docs.txt +2 -2
- package/out/history.html +1 -1
- package/out/history.txt +2 -2
- package/out/index.html +1 -1
- package/out/index.txt +2 -2
- package/out/login.html +2 -2
- package/out/login.txt +2 -2
- package/out/metrics.html +1 -1
- package/out/metrics.txt +2 -2
- package/out/pricing.html +2 -2
- package/out/pricing.txt +2 -2
- package/out/privacy.html +2 -2
- package/out/privacy.txt +2 -2
- package/out/providers/setup/claude.html +1 -1
- package/out/providers/setup/claude.txt +2 -2
- package/out/providers/setup/codex.html +1 -1
- package/out/providers/setup/codex.txt +2 -2
- package/out/providers/setup/cursor.html +1 -1
- package/out/providers/setup/cursor.txt +2 -2
- package/out/providers.html +1 -1
- package/out/providers.txt +2 -2
- package/out/security.html +2 -2
- package/out/security.txt +2 -2
- package/out/signup.html +2 -2
- package/out/signup.txt +2 -2
- package/out/terms.html +2 -2
- package/out/terms.txt +2 -2
- package/package.json +5 -1
- package/src/adapters/DashboardConfigProvider.tsx +56 -0
- package/src/adapters/cloudFetchAdapter.ts +278 -0
- package/src/adapters/index.ts +3 -0
- package/src/adapters/types.ts +508 -0
- package/src/app/app/[[...slug]]/DashboardPageClient.tsx +67 -18
- package/src/app/app/onboarding/page.tsx +870 -170
- package/src/app/cloud/link/page.tsx +14 -6
- package/src/app/connect-repos/page.tsx +9 -3
- package/src/app/dev/cli-tools/page.tsx +130 -0
- package/src/app/dev/log-viewer/MockLogViewer.tsx +132 -0
- package/src/app/dev/log-viewer/fixtures.ts +110 -0
- package/src/app/dev/log-viewer/page.tsx +288 -0
- package/src/app/history/page.tsx +28 -12
- package/src/app/page.tsx +1 -1
- package/src/app/providers/setup/[provider]/ProviderSetupClient.tsx +209 -59
- package/src/components/AgentCard.tsx +4 -4
- package/src/components/AgentLogPreview.tsx +2 -38
- package/src/components/App.tsx +441 -2624
- package/src/components/CliToolHarness.test.tsx +83 -0
- package/src/components/CliToolHarness.tsx +292 -0
- package/src/components/CoordinatorPanel.tsx +13 -6
- package/src/components/LogViewer.tsx +2 -42
- package/src/components/ProviderAuthFlow.tsx +201 -81
- package/src/components/ProvisioningProgress.tsx +1 -1
- package/src/components/ReactionChips.tsx +2 -1
- package/src/components/SpawnModal.test.tsx +51 -18
- package/src/components/SpawnModal.tsx +175 -207
- package/src/components/TerminalProviderSetup.tsx +1 -1
- package/src/components/ThreadPanel.tsx +2 -0
- package/src/components/WorkspaceContext.tsx +7 -19
- package/src/components/XTermLogViewer.tsx +190 -27
- package/src/components/channels/ChannelMessageList.tsx +94 -4
- package/src/components/channels/ChannelViewV1.tsx +35 -11
- package/src/components/channels/api.ts +21 -20
- package/src/components/channels/types.ts +16 -0
- package/src/components/hooks/index.ts +0 -19
- package/src/components/hooks/useMessages.test.ts +80 -0
- package/src/components/hooks/useMessages.ts +13 -4
- package/src/components/hooks/useOrchestrator.ts +1 -1
- package/src/components/hooks/usePresence.ts +45 -6
- package/src/components/hooks/useThread.ts +83 -46
- package/src/components/hooks/useTrajectory.ts +62 -5
- package/src/components/hooks/useWebSocket.test.ts +358 -0
- package/src/components/hooks/useWebSocket.ts +243 -5
- package/src/components/index.ts +2 -14
- package/src/components/layout/Header.tsx +9 -15
- package/src/components/layout/Sidebar.tsx +1 -8
- package/src/components/settings/SettingsPage.tsx +108 -47
- package/src/components/settings/index.ts +0 -3
- package/src/landing/blogData.ts +1 -1
- package/src/lib/agent-merge.test.ts +2 -2
- package/src/lib/api.ts +8 -38
- package/src/lib/identity.test.ts +139 -0
- package/src/lib/identity.ts +48 -0
- package/src/lib/relaycastMessageAdapters.test.ts +182 -0
- package/src/lib/relaycastMessageAdapters.ts +105 -0
- package/src/lib/sanitize-logs.test.ts +227 -0
- package/src/lib/sanitize-logs.ts +202 -0
- package/src/providers/AgentProvider.tsx +799 -0
- package/src/providers/ChannelProvider.tsx +528 -0
- package/src/providers/CloudWorkspaceProvider.tsx +402 -0
- package/src/providers/MessageProvider.tsx +875 -0
- package/src/providers/RelayConfigProvider.tsx +94 -0
- package/src/providers/SendProvider.tsx +497 -0
- package/src/providers/SettingsProvider.tsx +247 -0
- package/src/providers/index.ts +26 -0
- package/src/types/index.ts +10 -10
- package/out/_next/static/chunks/11-9a2993a37266dcb3.js +0 -9
- package/out/_next/static/chunks/118-ae2b650136a5a5fc.js +0 -1
- package/out/_next/static/chunks/1dd3208c-40ab0fc0f60392b8.js +0 -1
- package/out/_next/static/chunks/202-fc0763dd7488e58f.js +0 -1
- package/out/_next/static/chunks/259-83b77fa1b91ba5aa.js +0 -1
- package/out/_next/static/chunks/407-0c82986cf79c8ecb.js +0 -1
- package/out/_next/static/chunks/528-f5f676996d613c25.js +0 -2
- package/out/_next/static/chunks/663-ddb04081febc3678.js +0 -1
- package/out/_next/static/chunks/687-88b6b139a6bb0e2e.js +0 -1
- package/out/_next/static/chunks/695-51d25b1988644374.js +0 -1
- package/out/_next/static/chunks/773-54a2641043c81e55.js +0 -1
- package/out/_next/static/chunks/app/_not-found/page-6da9b72091e5b511.js +0 -1
- package/out/_next/static/chunks/app/about/page-fff7c6457683f243.js +0 -1
- package/out/_next/static/chunks/app/app/[[...slug]]/page-f7eca1b66fb4249b.js +0 -1
- package/out/_next/static/chunks/app/app/onboarding/page-129abc5da2e67971.js +0 -1
- package/out/_next/static/chunks/app/blog/go-to-bed-wake-up-to-a-finished-product/page-5d5f28fd126b692f.js +0 -1
- package/out/_next/static/chunks/app/blog/let-them-cook-multi-agent-orchestration/page-b194f207fbd91862.js +0 -1
- package/out/_next/static/chunks/app/blog/page-b9bd9d8703fca76a.js +0 -1
- package/out/_next/static/chunks/app/careers/page-a4bd8d5f4de8f4eb.js +0 -1
- package/out/_next/static/chunks/app/changelog/page-9a1f6ad1743d63c5.js +0 -1
- package/out/_next/static/chunks/app/cloud/link/page-0844c5699b027c3b.js +0 -1
- package/out/_next/static/chunks/app/complete-profile/page-39ed5a67916beb87.js +0 -1
- package/out/_next/static/chunks/app/connect-repos/page-297eddee0c39f2a3.js +0 -1
- package/out/_next/static/chunks/app/contact/page-3c1dd8690217fade.js +0 -1
- package/out/_next/static/chunks/app/docs/page-1875e981f2c3fd13.js +0 -1
- package/out/_next/static/chunks/app/history/page-2d5c5695c9e8b40c.js +0 -1
- package/out/_next/static/chunks/app/layout-0a4b99656da25511.js +0 -1
- package/out/_next/static/chunks/app/login/page-f69c076f5a6fc520.js +0 -1
- package/out/_next/static/chunks/app/metrics/page-bebbee055669a17e.js +0 -1
- package/out/_next/static/chunks/app/page-0ee604f7070d14c0.js +0 -1
- package/out/_next/static/chunks/app/pricing/page-eeae7d594af333b6.js +0 -1
- package/out/_next/static/chunks/app/providers/setup/[provider]/page-daf9b3e05e77ae19.js +0 -1
- package/out/_next/static/chunks/app/security/page-cd562730fe84a0a2.js +0 -1
- package/out/_next/static/chunks/app/signup/page-c242ca08101a84ff.js +0 -1
- package/out/_next/static/chunks/app/terms/page-c7001720e7941dc6.js +0 -1
- package/out/_next/static/chunks/framework-3664cab31236a9fa.js +0 -1
- package/out/_next/static/chunks/main-app-7f73a939a312a228.js +0 -1
- package/out/_next/static/chunks/pages/_app-10a93ab5b7c32eb3.js +0 -1
- package/out/_next/static/chunks/pages/_error-2d792b2a41857be4.js +0 -1
- package/out/_next/static/css/8968d98ed4c4d33f.css +0 -1
- package/src/components/BillingResult.tsx +0 -447
- package/src/components/CloudSessionProvider.tsx +0 -130
- package/src/components/SessionExpiredModal.tsx +0 -128
- package/src/components/WorkspaceStatusIndicator.tsx +0 -396
- package/src/components/hooks/useSession.ts +0 -209
- package/src/components/hooks/useWorkspaceMembers.ts +0 -132
- package/src/components/hooks/useWorkspaceStatus.ts +0 -237
- package/src/components/settings/BillingSettingsPanel.tsx +0 -564
- package/src/components/settings/TeamSettingsPanel.tsx +0 -560
- package/src/components/settings/WorkspaceSettingsPanel.tsx +0 -1368
- package/src/lib/cloudApi.ts +0 -893
- /package/out/_next/static/{IxfA6RZu4trcsEMYlkQra → g3G0LMdB7lxcrU5mdM54m}/_ssgManifest.js +0 -0
|
@@ -1,209 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useSession Hook
|
|
3
|
-
*
|
|
4
|
-
* React hook for managing cloud session state.
|
|
5
|
-
* Automatically detects session expiration and triggers re-login flow.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
9
|
-
import {
|
|
10
|
-
cloudApi,
|
|
11
|
-
onSessionExpired,
|
|
12
|
-
getCsrfToken,
|
|
13
|
-
type CloudUser,
|
|
14
|
-
type SessionError,
|
|
15
|
-
type SessionStatus,
|
|
16
|
-
} from '../../lib/cloudApi';
|
|
17
|
-
|
|
18
|
-
export interface UseSessionOptions {
|
|
19
|
-
/** Check session on mount (default: true) */
|
|
20
|
-
checkOnMount?: boolean;
|
|
21
|
-
/** Interval to periodically check session in ms (default: 60000, set to 0 to disable) */
|
|
22
|
-
checkInterval?: number;
|
|
23
|
-
/** Callback when session expires */
|
|
24
|
-
onExpired?: (error: SessionError) => void;
|
|
25
|
-
}
|
|
26
|
-
|
|
27
|
-
export interface UseSessionReturn {
|
|
28
|
-
/** Current user data (null if not authenticated) */
|
|
29
|
-
user: CloudUser | null;
|
|
30
|
-
/** Whether the session check is in progress */
|
|
31
|
-
isLoading: boolean;
|
|
32
|
-
/** Whether user is authenticated */
|
|
33
|
-
isAuthenticated: boolean;
|
|
34
|
-
/** Whether session has expired (requires re-login) */
|
|
35
|
-
isExpired: boolean;
|
|
36
|
-
/** Session error if any */
|
|
37
|
-
error: SessionError | null;
|
|
38
|
-
/** CSRF token for API requests */
|
|
39
|
-
csrfToken: string | null;
|
|
40
|
-
/** Manually check session status */
|
|
41
|
-
checkSession: () => Promise<SessionStatus>;
|
|
42
|
-
/** Clear the expired state (e.g., after dismissing modal) */
|
|
43
|
-
clearExpired: () => void;
|
|
44
|
-
/** Redirect to login page */
|
|
45
|
-
redirectToLogin: () => void;
|
|
46
|
-
/** Logout the current user */
|
|
47
|
-
logout: () => Promise<void>;
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const DEFAULT_OPTIONS: Required<UseSessionOptions> = {
|
|
51
|
-
checkOnMount: true,
|
|
52
|
-
checkInterval: 60000, // 1 minute
|
|
53
|
-
onExpired: () => {},
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
export function useSession(options: UseSessionOptions = {}): UseSessionReturn {
|
|
57
|
-
const opts = { ...DEFAULT_OPTIONS, ...options };
|
|
58
|
-
|
|
59
|
-
const [user, setUser] = useState<CloudUser | null>(null);
|
|
60
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
61
|
-
const [isExpired, setIsExpired] = useState(false);
|
|
62
|
-
const [error, setError] = useState<SessionError | null>(null);
|
|
63
|
-
|
|
64
|
-
const intervalRef = useRef<NodeJS.Timeout | null>(null);
|
|
65
|
-
const mountedRef = useRef(true);
|
|
66
|
-
|
|
67
|
-
// Check session status
|
|
68
|
-
const checkSession = useCallback(async (): Promise<SessionStatus> => {
|
|
69
|
-
try {
|
|
70
|
-
const status = await cloudApi.checkSession();
|
|
71
|
-
|
|
72
|
-
if (!mountedRef.current) return status;
|
|
73
|
-
|
|
74
|
-
if (!status.authenticated) {
|
|
75
|
-
setUser(null);
|
|
76
|
-
if (status.code) {
|
|
77
|
-
const sessionError: SessionError = {
|
|
78
|
-
error: 'Session expired',
|
|
79
|
-
code: status.code,
|
|
80
|
-
message: status.message || 'Your session has expired. Please log in again.',
|
|
81
|
-
};
|
|
82
|
-
setError(sessionError);
|
|
83
|
-
setIsExpired(true);
|
|
84
|
-
opts.onExpired(sessionError);
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
|
|
88
|
-
return status;
|
|
89
|
-
} catch (_e) {
|
|
90
|
-
return {
|
|
91
|
-
authenticated: false,
|
|
92
|
-
code: 'SESSION_ERROR',
|
|
93
|
-
message: 'Failed to check session',
|
|
94
|
-
};
|
|
95
|
-
}
|
|
96
|
-
}, [opts]);
|
|
97
|
-
|
|
98
|
-
// Fetch user data
|
|
99
|
-
const fetchUser = useCallback(async () => {
|
|
100
|
-
setIsLoading(true);
|
|
101
|
-
try {
|
|
102
|
-
const result = await cloudApi.getMe();
|
|
103
|
-
|
|
104
|
-
if (!mountedRef.current) return;
|
|
105
|
-
|
|
106
|
-
if (result.success) {
|
|
107
|
-
setUser(result.data);
|
|
108
|
-
setIsExpired(false);
|
|
109
|
-
setError(null);
|
|
110
|
-
} else if (result.sessionExpired) {
|
|
111
|
-
setUser(null);
|
|
112
|
-
setIsExpired(true);
|
|
113
|
-
} else {
|
|
114
|
-
setError({
|
|
115
|
-
error: result.error,
|
|
116
|
-
code: 'SESSION_ERROR',
|
|
117
|
-
message: result.error,
|
|
118
|
-
});
|
|
119
|
-
}
|
|
120
|
-
} finally {
|
|
121
|
-
if (mountedRef.current) {
|
|
122
|
-
setIsLoading(false);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}, []);
|
|
126
|
-
|
|
127
|
-
// Handle session expiration from any API call
|
|
128
|
-
useEffect(() => {
|
|
129
|
-
const unsubscribe = onSessionExpired((sessionError) => {
|
|
130
|
-
if (!mountedRef.current) return;
|
|
131
|
-
|
|
132
|
-
setUser(null);
|
|
133
|
-
setIsExpired(true);
|
|
134
|
-
setError(sessionError);
|
|
135
|
-
opts.onExpired(sessionError);
|
|
136
|
-
});
|
|
137
|
-
|
|
138
|
-
return unsubscribe;
|
|
139
|
-
}, [opts]);
|
|
140
|
-
|
|
141
|
-
// Check session on mount
|
|
142
|
-
useEffect(() => {
|
|
143
|
-
mountedRef.current = true;
|
|
144
|
-
|
|
145
|
-
if (opts.checkOnMount) {
|
|
146
|
-
fetchUser();
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
return () => {
|
|
150
|
-
mountedRef.current = false;
|
|
151
|
-
};
|
|
152
|
-
}, [opts.checkOnMount, fetchUser]);
|
|
153
|
-
|
|
154
|
-
// Periodic session check
|
|
155
|
-
useEffect(() => {
|
|
156
|
-
if (opts.checkInterval <= 0) return;
|
|
157
|
-
|
|
158
|
-
intervalRef.current = setInterval(() => {
|
|
159
|
-
// Only check if we think we're authenticated
|
|
160
|
-
if (user) {
|
|
161
|
-
checkSession();
|
|
162
|
-
}
|
|
163
|
-
}, opts.checkInterval);
|
|
164
|
-
|
|
165
|
-
return () => {
|
|
166
|
-
if (intervalRef.current) {
|
|
167
|
-
clearInterval(intervalRef.current);
|
|
168
|
-
intervalRef.current = null;
|
|
169
|
-
}
|
|
170
|
-
};
|
|
171
|
-
}, [opts.checkInterval, user, checkSession]);
|
|
172
|
-
|
|
173
|
-
// Clear expired state
|
|
174
|
-
const clearExpired = useCallback(() => {
|
|
175
|
-
setIsExpired(false);
|
|
176
|
-
setError(null);
|
|
177
|
-
}, []);
|
|
178
|
-
|
|
179
|
-
// Redirect to login
|
|
180
|
-
const redirectToLogin = useCallback(() => {
|
|
181
|
-
// Preserve current path for redirect after login
|
|
182
|
-
const returnTo = encodeURIComponent(window.location.pathname + window.location.search);
|
|
183
|
-
window.location.href = `/login?returnTo=${returnTo}`;
|
|
184
|
-
}, []);
|
|
185
|
-
|
|
186
|
-
// Logout
|
|
187
|
-
const logout = useCallback(async () => {
|
|
188
|
-
await cloudApi.logout();
|
|
189
|
-
setUser(null);
|
|
190
|
-
setIsExpired(false);
|
|
191
|
-
setError(null);
|
|
192
|
-
window.location.href = '/login';
|
|
193
|
-
}, []);
|
|
194
|
-
|
|
195
|
-
return {
|
|
196
|
-
user,
|
|
197
|
-
isLoading,
|
|
198
|
-
isAuthenticated: user !== null,
|
|
199
|
-
isExpired,
|
|
200
|
-
error,
|
|
201
|
-
csrfToken: getCsrfToken(),
|
|
202
|
-
checkSession,
|
|
203
|
-
clearExpired,
|
|
204
|
-
redirectToLogin,
|
|
205
|
-
logout,
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
export type { SessionError, CloudUser };
|
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useWorkspaceMembers Hook
|
|
3
|
-
*
|
|
4
|
-
* Fetches and caches workspace members for filtering online users.
|
|
5
|
-
* Returns the set of usernames that have access to the workspace.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { useState, useEffect, useCallback, useMemo } from 'react';
|
|
9
|
-
import { cloudApi } from '../../lib/cloudApi';
|
|
10
|
-
import type { UserPresence } from './usePresence';
|
|
11
|
-
|
|
12
|
-
interface WorkspaceMember {
|
|
13
|
-
id: string;
|
|
14
|
-
userId: string;
|
|
15
|
-
role: string;
|
|
16
|
-
isPending: boolean;
|
|
17
|
-
user?: {
|
|
18
|
-
githubUsername?: string;
|
|
19
|
-
displayName?: string;
|
|
20
|
-
email?: string;
|
|
21
|
-
avatarUrl?: string;
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface UseWorkspaceMembersOptions {
|
|
26
|
-
/** The workspace ID to fetch members for */
|
|
27
|
-
workspaceId?: string;
|
|
28
|
-
/** Whether to enable fetching (e.g., only in cloud mode) */
|
|
29
|
-
enabled?: boolean;
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
export interface UseWorkspaceMembersReturn {
|
|
33
|
-
/** Set of usernames with workspace access (lowercase for comparison) */
|
|
34
|
-
memberUsernames: Set<string>;
|
|
35
|
-
/** Whether members are currently loading */
|
|
36
|
-
isLoading: boolean;
|
|
37
|
-
/** Error message if fetch failed */
|
|
38
|
-
error: string | null;
|
|
39
|
-
/** Refetch workspace members */
|
|
40
|
-
refetch: () => Promise<void>;
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
/**
|
|
44
|
-
* Hook to fetch workspace members and provide a set of usernames with access.
|
|
45
|
-
* Used to filter online users to show only those with workspace access.
|
|
46
|
-
*/
|
|
47
|
-
export function useWorkspaceMembers(
|
|
48
|
-
options: UseWorkspaceMembersOptions = {}
|
|
49
|
-
): UseWorkspaceMembersReturn {
|
|
50
|
-
const { workspaceId, enabled = true } = options;
|
|
51
|
-
|
|
52
|
-
const [members, setMembers] = useState<WorkspaceMember[]>([]);
|
|
53
|
-
const [isLoading, setIsLoading] = useState(false);
|
|
54
|
-
const [error, setError] = useState<string | null>(null);
|
|
55
|
-
|
|
56
|
-
const fetchMembers = useCallback(async () => {
|
|
57
|
-
if (!workspaceId || !enabled) {
|
|
58
|
-
setMembers([]);
|
|
59
|
-
return;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
setIsLoading(true);
|
|
63
|
-
setError(null);
|
|
64
|
-
|
|
65
|
-
try {
|
|
66
|
-
const result = await cloudApi.getWorkspaceMembers(workspaceId);
|
|
67
|
-
if (result.success) {
|
|
68
|
-
setMembers(result.data.members as WorkspaceMember[]);
|
|
69
|
-
} else {
|
|
70
|
-
setError(result.error);
|
|
71
|
-
setMembers([]);
|
|
72
|
-
}
|
|
73
|
-
} catch (err) {
|
|
74
|
-
setError(err instanceof Error ? err.message : 'Failed to fetch members');
|
|
75
|
-
setMembers([]);
|
|
76
|
-
} finally {
|
|
77
|
-
setIsLoading(false);
|
|
78
|
-
}
|
|
79
|
-
}, [workspaceId, enabled]);
|
|
80
|
-
|
|
81
|
-
// Fetch members when workspace changes
|
|
82
|
-
useEffect(() => {
|
|
83
|
-
fetchMembers();
|
|
84
|
-
}, [fetchMembers]);
|
|
85
|
-
|
|
86
|
-
// Build set of member usernames (lowercase for case-insensitive comparison)
|
|
87
|
-
// Include githubUsername, displayName, and email prefix to match all possible presence usernames
|
|
88
|
-
// (The auth API uses: displayName || githubUsername || email.split('@')[0])
|
|
89
|
-
const memberUsernames = useMemo(() => {
|
|
90
|
-
const usernames = new Set<string>();
|
|
91
|
-
for (const member of members) {
|
|
92
|
-
// Add GitHub username if available
|
|
93
|
-
if (member.user?.githubUsername) {
|
|
94
|
-
usernames.add(member.user.githubUsername.toLowerCase());
|
|
95
|
-
}
|
|
96
|
-
// Also add displayName for email-only users who don't have a GitHub username
|
|
97
|
-
if (member.user?.displayName) {
|
|
98
|
-
usernames.add(member.user.displayName.toLowerCase());
|
|
99
|
-
}
|
|
100
|
-
// Also add email prefix for email-only users without displayName
|
|
101
|
-
if (member.user?.email) {
|
|
102
|
-
usernames.add(member.user.email.split('@')[0].toLowerCase());
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
return usernames;
|
|
106
|
-
}, [members]);
|
|
107
|
-
|
|
108
|
-
return {
|
|
109
|
-
memberUsernames,
|
|
110
|
-
isLoading,
|
|
111
|
-
error,
|
|
112
|
-
refetch: fetchMembers,
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
|
|
116
|
-
/**
|
|
117
|
-
* Filter online users to only include those with workspace access.
|
|
118
|
-
* If no members are loaded (non-cloud mode or error), returns all users.
|
|
119
|
-
*/
|
|
120
|
-
export function filterOnlineUsersByWorkspace(
|
|
121
|
-
onlineUsers: UserPresence[],
|
|
122
|
-
memberUsernames: Set<string>
|
|
123
|
-
): UserPresence[] {
|
|
124
|
-
// If no members loaded, show all users (non-cloud mode fallback)
|
|
125
|
-
if (memberUsernames.size === 0) {
|
|
126
|
-
return onlineUsers;
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
return onlineUsers.filter((user) =>
|
|
130
|
-
memberUsernames.has(user.username.toLowerCase())
|
|
131
|
-
);
|
|
132
|
-
}
|
|
@@ -1,237 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* useWorkspaceStatus Hook
|
|
3
|
-
*
|
|
4
|
-
* React hook for monitoring workspace status with auto-wakeup capability.
|
|
5
|
-
* Polls for status updates and can automatically restart stopped workspaces.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { useState, useEffect, useCallback, useRef } from 'react';
|
|
9
|
-
import { cloudApi } from '../../lib/cloudApi';
|
|
10
|
-
|
|
11
|
-
export interface WorkspaceStatus {
|
|
12
|
-
id: string;
|
|
13
|
-
name: string;
|
|
14
|
-
status: string;
|
|
15
|
-
publicUrl?: string;
|
|
16
|
-
isStopped: boolean;
|
|
17
|
-
isRunning: boolean;
|
|
18
|
-
isProvisioning: boolean;
|
|
19
|
-
hasError: boolean;
|
|
20
|
-
config: {
|
|
21
|
-
providers: string[];
|
|
22
|
-
repositories: string[];
|
|
23
|
-
};
|
|
24
|
-
}
|
|
25
|
-
|
|
26
|
-
export interface UseWorkspaceStatusOptions {
|
|
27
|
-
/** Poll for status updates (default: true) */
|
|
28
|
-
autoRefresh?: boolean;
|
|
29
|
-
/** Interval to poll for status in ms (default: 30000) */
|
|
30
|
-
refreshInterval?: number;
|
|
31
|
-
/** Auto-wakeup when workspace is stopped (default: false) */
|
|
32
|
-
autoWakeup?: boolean;
|
|
33
|
-
/** Callback when workspace status changes */
|
|
34
|
-
onStatusChange?: (status: string, wasRestarted: boolean) => void;
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
export interface UseWorkspaceStatusReturn {
|
|
38
|
-
/** Current workspace data (null if no workspace) */
|
|
39
|
-
workspace: WorkspaceStatus | null;
|
|
40
|
-
/** Whether workspace exists */
|
|
41
|
-
exists: boolean;
|
|
42
|
-
/** Whether the status check is in progress */
|
|
43
|
-
isLoading: boolean;
|
|
44
|
-
/** Whether a wakeup is in progress */
|
|
45
|
-
isWakingUp: boolean;
|
|
46
|
-
/** Status message for display */
|
|
47
|
-
statusMessage: string;
|
|
48
|
-
/** Action needed (wakeup, check_error, etc) */
|
|
49
|
-
actionNeeded: 'wakeup' | 'check_error' | null;
|
|
50
|
-
/** Error if any */
|
|
51
|
-
error: string | null;
|
|
52
|
-
/** Manually refresh status */
|
|
53
|
-
refresh: () => Promise<void>;
|
|
54
|
-
/** Manually wake up workspace */
|
|
55
|
-
wakeup: () => Promise<{ success: boolean; message: string }>;
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const DEFAULT_OPTIONS: Required<UseWorkspaceStatusOptions> = {
|
|
59
|
-
autoRefresh: true,
|
|
60
|
-
refreshInterval: 30000, // 30 seconds
|
|
61
|
-
autoWakeup: false,
|
|
62
|
-
onStatusChange: () => {},
|
|
63
|
-
};
|
|
64
|
-
|
|
65
|
-
export function useWorkspaceStatus(
|
|
66
|
-
options: UseWorkspaceStatusOptions = {}
|
|
67
|
-
): UseWorkspaceStatusReturn {
|
|
68
|
-
// Stabilize options to prevent infinite re-renders
|
|
69
|
-
// Use refs for callbacks and useMemo for primitive values
|
|
70
|
-
const autoRefresh = options.autoRefresh ?? DEFAULT_OPTIONS.autoRefresh;
|
|
71
|
-
const refreshInterval = options.refreshInterval ?? DEFAULT_OPTIONS.refreshInterval;
|
|
72
|
-
const autoWakeup = options.autoWakeup ?? DEFAULT_OPTIONS.autoWakeup;
|
|
73
|
-
|
|
74
|
-
// Store callback in ref to avoid recreating refresh on every render
|
|
75
|
-
const onStatusChangeRef = useRef(options.onStatusChange ?? DEFAULT_OPTIONS.onStatusChange);
|
|
76
|
-
onStatusChangeRef.current = options.onStatusChange ?? DEFAULT_OPTIONS.onStatusChange;
|
|
77
|
-
|
|
78
|
-
const [workspace, setWorkspace] = useState<WorkspaceStatus | null>(null);
|
|
79
|
-
const [exists, setExists] = useState(false);
|
|
80
|
-
const [isLoading, setIsLoading] = useState(true);
|
|
81
|
-
const [isWakingUp, setIsWakingUp] = useState(false);
|
|
82
|
-
const [statusMessage, setStatusMessage] = useState('');
|
|
83
|
-
const [actionNeeded, setActionNeeded] = useState<'wakeup' | 'check_error' | null>(null);
|
|
84
|
-
const [error, setError] = useState<string | null>(null);
|
|
85
|
-
|
|
86
|
-
const intervalRef = useRef<NodeJS.Timeout | null>(null);
|
|
87
|
-
const mountedRef = useRef(true);
|
|
88
|
-
const previousStatusRef = useRef<string | null>(null);
|
|
89
|
-
|
|
90
|
-
// Fetch workspace status
|
|
91
|
-
const refresh = useCallback(async () => {
|
|
92
|
-
try {
|
|
93
|
-
setIsLoading(true);
|
|
94
|
-
setError(null);
|
|
95
|
-
|
|
96
|
-
const result = await cloudApi.getPrimaryWorkspace();
|
|
97
|
-
|
|
98
|
-
if (!mountedRef.current) return;
|
|
99
|
-
|
|
100
|
-
if (result.success) {
|
|
101
|
-
setExists(result.data.exists);
|
|
102
|
-
setStatusMessage(result.data.statusMessage);
|
|
103
|
-
setActionNeeded(result.data.actionNeeded || null);
|
|
104
|
-
|
|
105
|
-
if (result.data.workspace) {
|
|
106
|
-
const ws = result.data.workspace;
|
|
107
|
-
setWorkspace(ws);
|
|
108
|
-
|
|
109
|
-
// Check for status change
|
|
110
|
-
if (previousStatusRef.current && previousStatusRef.current !== ws.status) {
|
|
111
|
-
onStatusChangeRef.current(ws.status, false);
|
|
112
|
-
}
|
|
113
|
-
previousStatusRef.current = ws.status;
|
|
114
|
-
} else {
|
|
115
|
-
setWorkspace(null);
|
|
116
|
-
}
|
|
117
|
-
} else {
|
|
118
|
-
setError(result.error);
|
|
119
|
-
}
|
|
120
|
-
} catch (_e) {
|
|
121
|
-
if (mountedRef.current) {
|
|
122
|
-
setError('Failed to fetch workspace status');
|
|
123
|
-
}
|
|
124
|
-
} finally {
|
|
125
|
-
if (mountedRef.current) {
|
|
126
|
-
setIsLoading(false);
|
|
127
|
-
}
|
|
128
|
-
}
|
|
129
|
-
}, []); // No dependencies - uses refs for callbacks
|
|
130
|
-
|
|
131
|
-
// Store refresh interval in ref for wakeup callback
|
|
132
|
-
const refreshIntervalRef = useRef(refreshInterval);
|
|
133
|
-
refreshIntervalRef.current = refreshInterval;
|
|
134
|
-
const autoRefreshRef = useRef(autoRefresh);
|
|
135
|
-
autoRefreshRef.current = autoRefresh;
|
|
136
|
-
|
|
137
|
-
// Wake up workspace
|
|
138
|
-
const wakeup = useCallback(async (): Promise<{ success: boolean; message: string }> => {
|
|
139
|
-
if (!workspace?.id) {
|
|
140
|
-
return { success: false, message: 'No workspace to wake up' };
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
try {
|
|
144
|
-
setIsWakingUp(true);
|
|
145
|
-
setError(null);
|
|
146
|
-
|
|
147
|
-
const result = await cloudApi.wakeupWorkspace(workspace.id);
|
|
148
|
-
|
|
149
|
-
if (!mountedRef.current) {
|
|
150
|
-
return { success: false, message: 'Component unmounted' };
|
|
151
|
-
}
|
|
152
|
-
|
|
153
|
-
if (result.success) {
|
|
154
|
-
// Update local state
|
|
155
|
-
if (result.data.wasRestarted) {
|
|
156
|
-
setStatusMessage(result.data.message);
|
|
157
|
-
setActionNeeded(null);
|
|
158
|
-
onStatusChangeRef.current('starting', true);
|
|
159
|
-
|
|
160
|
-
// Start more frequent polling to catch when workspace is ready
|
|
161
|
-
if (intervalRef.current) {
|
|
162
|
-
clearInterval(intervalRef.current);
|
|
163
|
-
}
|
|
164
|
-
intervalRef.current = setInterval(refresh, 5000); // Poll every 5s during startup
|
|
165
|
-
|
|
166
|
-
// Reset to normal interval after 2 minutes
|
|
167
|
-
setTimeout(() => {
|
|
168
|
-
if (mountedRef.current && intervalRef.current) {
|
|
169
|
-
clearInterval(intervalRef.current);
|
|
170
|
-
if (autoRefreshRef.current) {
|
|
171
|
-
intervalRef.current = setInterval(refresh, refreshIntervalRef.current);
|
|
172
|
-
}
|
|
173
|
-
}
|
|
174
|
-
}, 120000);
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
return { success: true, message: result.data.message };
|
|
178
|
-
} else {
|
|
179
|
-
setError(result.error);
|
|
180
|
-
return { success: false, message: result.error };
|
|
181
|
-
}
|
|
182
|
-
} catch (e) {
|
|
183
|
-
const message = e instanceof Error ? e.message : 'Failed to wake up workspace';
|
|
184
|
-
if (mountedRef.current) {
|
|
185
|
-
setError(message);
|
|
186
|
-
}
|
|
187
|
-
return { success: false, message };
|
|
188
|
-
} finally {
|
|
189
|
-
if (mountedRef.current) {
|
|
190
|
-
setIsWakingUp(false);
|
|
191
|
-
}
|
|
192
|
-
}
|
|
193
|
-
}, [workspace?.id, refresh]);
|
|
194
|
-
|
|
195
|
-
// Initial fetch
|
|
196
|
-
useEffect(() => {
|
|
197
|
-
mountedRef.current = true;
|
|
198
|
-
refresh();
|
|
199
|
-
|
|
200
|
-
return () => {
|
|
201
|
-
mountedRef.current = false;
|
|
202
|
-
};
|
|
203
|
-
}, [refresh]);
|
|
204
|
-
|
|
205
|
-
// Auto-refresh polling
|
|
206
|
-
useEffect(() => {
|
|
207
|
-
if (!autoRefresh) return;
|
|
208
|
-
|
|
209
|
-
intervalRef.current = setInterval(refresh, refreshInterval);
|
|
210
|
-
|
|
211
|
-
return () => {
|
|
212
|
-
if (intervalRef.current) {
|
|
213
|
-
clearInterval(intervalRef.current);
|
|
214
|
-
intervalRef.current = null;
|
|
215
|
-
}
|
|
216
|
-
};
|
|
217
|
-
}, [autoRefresh, refreshInterval, refresh]);
|
|
218
|
-
|
|
219
|
-
// Auto-wakeup when workspace is stopped
|
|
220
|
-
useEffect(() => {
|
|
221
|
-
if (autoWakeup && workspace?.isStopped && !isWakingUp) {
|
|
222
|
-
wakeup();
|
|
223
|
-
}
|
|
224
|
-
}, [autoWakeup, workspace?.isStopped, isWakingUp, wakeup]);
|
|
225
|
-
|
|
226
|
-
return {
|
|
227
|
-
workspace,
|
|
228
|
-
exists,
|
|
229
|
-
isLoading,
|
|
230
|
-
isWakingUp,
|
|
231
|
-
statusMessage,
|
|
232
|
-
actionNeeded,
|
|
233
|
-
error,
|
|
234
|
-
refresh,
|
|
235
|
-
wakeup,
|
|
236
|
-
};
|
|
237
|
-
}
|