-
Notifications
You must be signed in to change notification settings - Fork 0
feat: Unified Agent Auth - Dashboard UI #34
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Phase 2 of Unified Agent Auth feature: - Add IntegrationConnect.tsx: Provider grid with 15 integrations organized by category (project, communication, monitoring, deploy, storage). Supports Nango OAuth connect/disconnect, tier badges, category filters, and compact mode. - Add IntegrationPolicyEditor.tsx: Policy editor for allowedIntegrations field with per-provider scope selection (read/write), wildcard access toggle, rate limit configuration, and quick presets. - Integrate IntegrationConnect into WorkspaceSettingsPanel with new "Integrations" tab between AI Providers and Repositories. - Export new components from components/index.ts Ref: unified-agent-auth-spec.md Co-authored-by: Cursor <[email protected]>
Phase 3 of Unified Agent Auth feature: - Add AuditLogViewer.tsx: Table view of integration proxy requests with filters (provider, agent, time range), pagination, and relative timestamps - Add ApprovalRequestPanel.tsx: Human-in-the-loop approval UI showing pending access requests from agents with approve/deny actions - Add cloudApi methods: getAuditLogs, getApprovalRequests, approveRequest, denyRequest - Add "Audit Log" section to WorkspaceSettingsPanel navigation - Integrate ApprovalRequestPanel into Integrations settings tab Ref: unified-agent-auth-spec.md Co-authored-by: Cursor <[email protected]>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| setTimeout(() => { | ||
| clearInterval(pollInterval); | ||
| if (connectingProvider === provider.id) { | ||
| setConnectingProvider(null); | ||
| setError('Connection timed out'); | ||
| } | ||
| }, 300000); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔴 Stale closure: timeout never clears connecting state on connection timeout
The 5-minute timeout callback at line 202 compares connectingProvider === provider.id, but connectingProvider is captured from the closure at the time handleConnect was created. Since setConnectingProvider(provider.id) was called on line 131 (a state update that hasn't been reflected in the closure), the captured connectingProvider is the previous value (likely null). This means the condition connectingProvider === provider.id will always be false, so the timeout will never reset the connecting state or show the timeout error.
Root Cause: stale closure over React state in setTimeout
When handleConnect is invoked:
- Line 131:
setConnectingProvider(provider.id)— schedules a state update - Line 200-206: A
setTimeoutis created that capturesconnectingProviderfrom the current closure - The closure's
connectingProvideris the value before thesetConnectingProvidercall (the old state), notprovider.id
So when the timeout fires after 5 minutes:
if (connectingProvider === provider.id) { // always false
setConnectingProvider(null);
setError('Connection timed out');
}The condition is always false. The interval is cleared (line 201), but the UI remains stuck in the "Connecting..." state forever, and no timeout error is shown to the user.
Impact: If the OAuth flow takes longer than 5 minutes or silently fails without returning an error status, the user sees an infinite "Connecting..." spinner with no way to recover except refreshing the page.
| setTimeout(() => { | |
| clearInterval(pollInterval); | |
| if (connectingProvider === provider.id) { | |
| setConnectingProvider(null); | |
| setError('Connection timed out'); | |
| } | |
| }, 300000); | |
| setTimeout(() => { | |
| clearInterval(pollInterval); | |
| setConnectingProvider(prev => { | |
| if (prev === provider.id) { | |
| setError('Connection timed out'); | |
| return null; | |
| } | |
| return prev; | |
| }); | |
| }, 300000); | |
Was this helpful? React with 👍 or 👎 to provide feedback.
| const handleClearFilters = () => { | ||
| setFilters({ provider: '', agent: '', startTime: '', endTime: '' }); | ||
| setCursor(null); | ||
| fetchEntries(true); | ||
| setShowFilters(false); | ||
| }; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔴 handleClearFilters fetches with old (stale) filter values instead of cleared filters
handleClearFilters calls setFilters(...) to clear filters and then immediately calls fetchEntries(true), but fetchEntries is a useCallback that closes over the current filters state. Since React state updates are asynchronous, fetchEntries still uses the old filter values.
Root Cause: stale closure over filters state
In handleClearFilters (line 142-147):
const handleClearFilters = () => {
setFilters({ provider: '', agent: '', startTime: '', endTime: '' });
setCursor(null);
fetchEntries(true); // uses OLD filters from closure
setShowFilters(false);
};fetchEntries is defined with useCallback(..., [workspaceId, filters, cursor]) at line 104-127. When handleClearFilters calls setFilters(...), the state update is batched and hasn't taken effect yet. The fetchEntries function still references the old filters object from its closure.
So when the user clicks "Clear" after setting filters (e.g., provider='github'), the API request is still made with provider=github instead of no filters. The user sees the filter panel close and expects unfiltered results, but gets the same filtered results.
Impact: The "Clear Filters" button doesn't actually clear filters on the current fetch — it fetches with the old filter values. The filters only take effect on the next fetch (e.g., if the user triggers another action).
Prompt for agents
In AuditLogViewer.tsx, the handleClearFilters function at line 142-147 calls setFilters() and then immediately calls fetchEntries(true), but fetchEntries closes over the stale filters state. To fix this, refactor fetchEntries to accept an optional filters parameter that overrides the state-based filters. For example, change the fetchEntries signature to accept (reset: boolean, overrideFilters?: AuditFilters), and when overrideFilters is provided, use those instead of the closed-over filters state. Then in handleClearFilters, pass the empty filters object directly: fetchEntries(true, { provider: '', agent: '', startTime: '', endTime: '' }). Apply the same pattern in handleApplyFilters if needed, passing the current filters explicitly.
Was this helpful? React with 👍 or 👎 to provide feedback.
Summary
Dashboard UI components for Unified Agent Auth system.
Phase 2:
IntegrationConnect.tsx- Provider grid with 15 integrations, Nango OAuth flowIntegrationPolicyEditor.tsx- Policy editor with scope selection (read/write)Phase 3:
/workspaces/[id]/auditTest Plan
Made with Cursor