feat: update connectiontab and datatab security fix (#1614)

* feat: update connectiontab and datatab security fix

# Connection Components and Diagnostics Updates

## GitHub Connection Component Changes
- Updated the disconnect button styling to match Vercel's design:
  - Changed from `<Button>` component to native `<button>` element
  - Added red background (`bg-red-500`) with hover effect (`hover:bg-red-600`)
  - Updated icon from `i-ph:sign-out` to `i-ph:plug`
  - Simplified text to just "Disconnect"
  - Added connection status indicator with check-circle icon and "Connected to GitHub" text

## ConnectionDiagnostics Tab Updates
### Added New Connection Diagnostics
- Implemented diagnostics for Vercel and Supabase connections
- Added new helper function `safeJsonParse` for safer JSON parsing operations

### Diagnostic Checks Added
- **Vercel Diagnostics:**
  - LocalStorage token verification
  - API endpoint connectivity test
  - Connection status validation
  - Reset functionality for Vercel connection

- **Supabase Diagnostics:**
  - LocalStorage credentials verification
  - API endpoint connectivity test
  - Connection status validation
  - Reset functionality for Supabase connection

### UI Enhancements
- Added new status cards for Vercel and Supabase
- Implemented reset buttons with consistent styling
- Added loading states during diagnostics
- Enhanced error handling and user feedback

### Function Updates
- Extended `runDiagnostics` function to include Vercel and Supabase checks
- Added new reset helper functions for each connection type
- Improved error handling and status reporting
- Enhanced toast notifications for better user feedback

### Visual Consistency
- Matched styling of new diagnostic cards with existing GitHub and Netlify cards
- Consistent use of icons and status indicators
- Uniform button styling across all connection types
- Maintained consistent spacing and layout patterns

### Code Structure
- Organized diagnostic checks into clear, separate sections
- Improved error handling and type safety
- Enhanced code readability and maintainability
- Added comprehensive status compilation for all connections

These changes ensure a consistent user experience across all connection types while providing robust diagnostic capabilities for troubleshooting connection issues.

# DataTab.tsx Changes

## Code Cleanup
- Removed unused variables from useDataOperations hook:
  - Removed `handleExportAPIKeys`
  - Removed `handleUndo`
  - Removed `lastOperation`

This change improves code quality by removing unused variables and resolves ESLint warnings without affecting any functionality.

* Test commit to verify pre-commit hook
This commit is contained in:
Stijnus
2025-04-08 13:06:43 +02:00
committed by GitHub
parent 8c70dd6123
commit 552f08acea
11 changed files with 757 additions and 363 deletions

View File

@@ -6,6 +6,20 @@ import { classNames } from '~/utils/classNames';
import { Collapsible, CollapsibleTrigger, CollapsibleContent } from '~/components/ui/Collapsible';
import { CodeBracketIcon, ChevronDownIcon } from '@heroicons/react/24/outline';
// Helper function to safely parse JSON
const safeJsonParse = (item: string | null) => {
if (!item) {
return null;
}
try {
return JSON.parse(item);
} catch (e) {
console.error('Failed to parse JSON from localStorage:', e);
return null;
}
};
/**
* A diagnostics component to help troubleshoot connection issues
*/
@@ -24,6 +38,8 @@ export default function ConnectionDiagnostics() {
const localStorageChecks = {
githubConnection: localStorage.getItem('github_connection'),
netlifyConnection: localStorage.getItem('netlify_connection'),
vercelConnection: localStorage.getItem('vercel_connection'),
supabaseConnection: localStorage.getItem('supabase_connection'),
};
// Get diagnostic data from server
@@ -35,36 +51,25 @@ export default function ConnectionDiagnostics() {
const serverDiagnostics = await response.json();
// Get GitHub token if available
const githubToken = localStorageChecks.githubConnection
? JSON.parse(localStorageChecks.githubConnection)?.token
: null;
const authHeaders = {
// === GitHub Checks ===
const githubConnectionParsed = safeJsonParse(localStorageChecks.githubConnection);
const githubToken = githubConnectionParsed?.token;
const githubAuthHeaders = {
...(githubToken ? { Authorization: `Bearer ${githubToken}` } : {}),
'Content-Type': 'application/json',
};
console.log('Testing GitHub endpoints with token:', githubToken ? 'present' : 'missing');
// Test GitHub API endpoints
const githubEndpoints = [
{ name: 'User', url: '/api/system/git-info?action=getUser' },
{ name: 'Repos', url: '/api/system/git-info?action=getRepos' },
{ name: 'Default', url: '/api/system/git-info' },
];
const githubResults = await Promise.all(
githubEndpoints.map(async (endpoint) => {
try {
const resp = await fetch(endpoint.url, {
headers: authHeaders,
});
return {
endpoint: endpoint.name,
status: resp.status,
ok: resp.ok,
};
const resp = await fetch(endpoint.url, { headers: githubAuthHeaders });
return { endpoint: endpoint.name, status: resp.status, ok: resp.ok };
} catch (error) {
return {
endpoint: endpoint.name,
@@ -75,23 +80,17 @@ export default function ConnectionDiagnostics() {
}),
);
// Check if Netlify token works
// === Netlify Checks ===
const netlifyConnectionParsed = safeJsonParse(localStorageChecks.netlifyConnection);
const netlifyToken = netlifyConnectionParsed?.token;
let netlifyUserCheck = null;
const netlifyToken = localStorageChecks.netlifyConnection
? JSON.parse(localStorageChecks.netlifyConnection || '{"token":""}').token
: '';
if (netlifyToken) {
try {
const netlifyResp = await fetch('https://api.netlify.com/api/v1/user', {
headers: {
Authorization: `Bearer ${netlifyToken}`,
},
headers: { Authorization: `Bearer ${netlifyToken}` },
});
netlifyUserCheck = {
status: netlifyResp.status,
ok: netlifyResp.ok,
};
netlifyUserCheck = { status: netlifyResp.status, ok: netlifyResp.ok };
} catch (error) {
netlifyUserCheck = {
error: error instanceof Error ? error.message : String(error),
@@ -100,22 +99,55 @@ export default function ConnectionDiagnostics() {
}
}
// === Vercel Checks ===
const vercelConnectionParsed = safeJsonParse(localStorageChecks.vercelConnection);
const vercelToken = vercelConnectionParsed?.token;
let vercelUserCheck = null;
if (vercelToken) {
try {
const vercelResp = await fetch('https://api.vercel.com/v2/user', {
headers: { Authorization: `Bearer ${vercelToken}` },
});
vercelUserCheck = { status: vercelResp.status, ok: vercelResp.ok };
} catch (error) {
vercelUserCheck = {
error: error instanceof Error ? error.message : String(error),
ok: false,
};
}
}
// === Supabase Checks ===
const supabaseConnectionParsed = safeJsonParse(localStorageChecks.supabaseConnection);
const supabaseUrl = supabaseConnectionParsed?.projectUrl;
const supabaseAnonKey = supabaseConnectionParsed?.anonKey;
let supabaseCheck = null;
if (supabaseUrl && supabaseAnonKey) {
supabaseCheck = { ok: true, status: 200, message: 'URL and Key present in localStorage' };
} else {
supabaseCheck = { ok: false, message: 'URL or Key missing in localStorage' };
}
// Compile results
const results = {
timestamp: new Date().toISOString(),
localStorage: {
hasGithubConnection: Boolean(localStorageChecks.githubConnection),
hasNetlifyConnection: Boolean(localStorageChecks.netlifyConnection),
githubConnectionParsed: localStorageChecks.githubConnection
? JSON.parse(localStorageChecks.githubConnection)
: null,
netlifyConnectionParsed: localStorageChecks.netlifyConnection
? JSON.parse(localStorageChecks.netlifyConnection)
: null,
hasVercelConnection: Boolean(localStorageChecks.vercelConnection),
hasSupabaseConnection: Boolean(localStorageChecks.supabaseConnection),
githubConnectionParsed,
netlifyConnectionParsed,
vercelConnectionParsed,
supabaseConnectionParsed,
},
apiEndpoints: {
github: githubResults,
netlify: netlifyUserCheck,
vercel: vercelUserCheck,
supabase: supabaseCheck,
},
serverDiagnostics,
};
@@ -131,7 +163,20 @@ export default function ConnectionDiagnostics() {
toast.error('Netlify API connection is failing. Try reconnecting.');
}
if (!results.localStorage.hasGithubConnection && !results.localStorage.hasNetlifyConnection) {
if (results.localStorage.hasVercelConnection && vercelUserCheck && !vercelUserCheck.ok) {
toast.error('Vercel API connection is failing. Try reconnecting.');
}
if (results.localStorage.hasSupabaseConnection && supabaseCheck && !supabaseCheck.ok) {
toast.warning('Supabase connection check failed or missing details. Verify settings.');
}
if (
!results.localStorage.hasGithubConnection &&
!results.localStorage.hasNetlifyConnection &&
!results.localStorage.hasVercelConnection &&
!results.localStorage.hasSupabaseConnection
) {
toast.info('No connection data found in browser storage.');
}
} catch (error) {
@@ -151,6 +196,7 @@ export default function ConnectionDiagnostics() {
document.cookie = 'githubUsername=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
document.cookie = 'git:github.com=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
toast.success('GitHub connection data cleared. Please refresh the page and reconnect.');
setDiagnosticResults(null);
} catch (error) {
console.error('Error clearing GitHub data:', error);
toast.error('Failed to clear GitHub connection data');
@@ -163,12 +209,37 @@ export default function ConnectionDiagnostics() {
localStorage.removeItem('netlify_connection');
document.cookie = 'netlifyToken=; expires=Thu, 01 Jan 1970 00:00:00 UTC; path=/;';
toast.success('Netlify connection data cleared. Please refresh the page and reconnect.');
setDiagnosticResults(null);
} catch (error) {
console.error('Error clearing Netlify data:', error);
toast.error('Failed to clear Netlify connection data');
}
};
// Helper to reset Vercel connection
const resetVercelConnection = () => {
try {
localStorage.removeItem('vercel_connection');
toast.success('Vercel connection data cleared. Please refresh the page and reconnect.');
setDiagnosticResults(null);
} catch (error) {
console.error('Error clearing Vercel data:', error);
toast.error('Failed to clear Vercel connection data');
}
};
// Helper to reset Supabase connection
const resetSupabaseConnection = () => {
try {
localStorage.removeItem('supabase_connection');
toast.success('Supabase connection data cleared. Please refresh the page and reconnect.');
setDiagnosticResults(null);
} catch (error) {
console.error('Error clearing Supabase data:', error);
toast.error('Failed to clear Supabase connection data');
}
};
return (
<div className="flex flex-col gap-6">
{/* Connection Status Cards */}
@@ -303,6 +374,133 @@ export default function ConnectionDiagnostics() {
</div>
)}
</div>
{/* Vercel Connection Card */}
<div className="p-4 rounded-lg bg-bolt-elements-background dark:bg-bolt-elements-background-depth-2 border border-bolt-elements-borderColor dark:border-bolt-elements-borderColor hover:border-bolt-elements-borderColorActive/70 dark:hover:border-bolt-elements-borderColorActive/70 transition-all duration-200 h-[180px] flex flex-col">
<div className="flex items-center gap-2">
<div className="i-si:vercel text-bolt-elements-item-contentAccent dark:text-bolt-elements-item-contentAccent w-4 h-4" />
<div className="text-sm font-medium text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary">
Vercel Connection
</div>
</div>
{diagnosticResults ? (
<>
<div className="flex items-center gap-2 mt-2">
<span
className={classNames(
'text-xl font-semibold',
diagnosticResults.localStorage.hasVercelConnection
? 'text-green-500 dark:text-green-400'
: 'text-red-500 dark:text-red-400',
)}
>
{diagnosticResults.localStorage.hasVercelConnection ? 'Connected' : 'Not Connected'}
</span>
</div>
{diagnosticResults.localStorage.hasVercelConnection && (
<>
<div className="text-xs text-bolt-elements-textSecondary dark:text-bolt-elements-textSecondary mt-2 flex items-center gap-1.5">
<div className="i-ph:user w-3.5 h-3.5 text-bolt-elements-item-contentAccent dark:text-bolt-elements-item-contentAccent" />
User:{' '}
{diagnosticResults.localStorage.vercelConnectionParsed?.user?.username ||
diagnosticResults.localStorage.vercelConnectionParsed?.user?.user?.username ||
'N/A'}
</div>
<div className="text-xs text-bolt-elements-textSecondary dark:text-bolt-elements-textSecondary mt-2 flex items-center gap-1.5">
<div className="i-ph:check-circle w-3.5 h-3.5 text-bolt-elements-item-contentAccent dark:text-bolt-elements-item-contentAccent" />
API Status:{' '}
<Badge
variant={diagnosticResults.apiEndpoints.vercel?.ok ? 'default' : 'destructive'}
className="ml-1"
>
{diagnosticResults.apiEndpoints.vercel?.ok ? 'OK' : 'Failed'}
</Badge>
</div>
</>
)}
{!diagnosticResults.localStorage.hasVercelConnection && (
<Button
onClick={() => window.location.reload()}
variant="outline"
size="sm"
className="mt-auto self-start hover:bg-bolt-elements-item-backgroundActive/10 hover:text-bolt-elements-textPrimary dark:hover:bg-bolt-elements-item-backgroundActive/10 dark:hover:text-bolt-elements-textPrimary transition-colors"
>
<div className="i-ph:plug w-3.5 h-3.5 mr-1" />
Connect Now
</Button>
)}
</>
) : (
<div className="flex items-center justify-center h-full">
<div className="text-sm text-bolt-elements-textSecondary dark:text-bolt-elements-textSecondary flex items-center gap-2">
<div className="i-ph:info w-4 h-4" />
Run diagnostics to check connection status
</div>
</div>
)}
</div>
{/* Supabase Connection Card */}
<div className="p-4 rounded-lg bg-bolt-elements-background dark:bg-bolt-elements-background-depth-2 border border-bolt-elements-borderColor dark:border-bolt-elements-borderColor hover:border-bolt-elements-borderColorActive/70 dark:hover:border-bolt-elements-borderColorActive/70 transition-all duration-200 h-[180px] flex flex-col">
<div className="flex items-center gap-2">
<div className="i-si:supabase text-bolt-elements-item-contentAccent dark:text-bolt-elements-item-contentAccent w-4 h-4" />
<div className="text-sm font-medium text-bolt-elements-textPrimary dark:text-bolt-elements-textPrimary">
Supabase Connection
</div>
</div>
{diagnosticResults ? (
<>
<div className="flex items-center gap-2 mt-2">
<span
className={classNames(
'text-xl font-semibold',
diagnosticResults.localStorage.hasSupabaseConnection
? 'text-green-500 dark:text-green-400'
: 'text-red-500 dark:text-red-400',
)}
>
{diagnosticResults.localStorage.hasSupabaseConnection ? 'Configured' : 'Not Configured'}
</span>
</div>
{diagnosticResults.localStorage.hasSupabaseConnection && (
<>
<div className="text-xs text-bolt-elements-textSecondary dark:text-bolt-elements-textSecondary mt-2 flex items-center gap-1.5 truncate">
<div className="i-ph:link w-3.5 h-3.5 text-bolt-elements-item-contentAccent dark:text-bolt-elements-item-contentAccent flex-shrink-0" />
Project URL: {diagnosticResults.localStorage.supabaseConnectionParsed?.projectUrl || 'N/A'}
</div>
<div className="text-xs text-bolt-elements-textSecondary dark:text-bolt-elements-textSecondary mt-2 flex items-center gap-1.5">
<div className="i-ph:check-circle w-3.5 h-3.5 text-bolt-elements-item-contentAccent dark:text-bolt-elements-item-contentAccent" />
Config Status:{' '}
<Badge
variant={diagnosticResults.apiEndpoints.supabase?.ok ? 'default' : 'destructive'}
className="ml-1"
>
{diagnosticResults.apiEndpoints.supabase?.ok ? 'OK' : 'Check Failed'}
</Badge>
</div>
</>
)}
{!diagnosticResults.localStorage.hasSupabaseConnection && (
<Button
onClick={() => window.location.reload()}
variant="outline"
size="sm"
className="mt-auto self-start hover:bg-bolt-elements-item-backgroundActive/10 hover:text-bolt-elements-textPrimary dark:hover:bg-bolt-elements-item-backgroundActive/10 dark:hover:text-bolt-elements-textPrimary transition-colors"
>
<div className="i-ph:plug w-3.5 h-3.5 mr-1" />
Configure Now
</Button>
)}
</>
) : (
<div className="flex items-center justify-center h-full">
<div className="text-sm text-bolt-elements-textSecondary dark:text-bolt-elements-textSecondary flex items-center gap-2">
<div className="i-ph:info w-4 h-4" />
Run diagnostics to check connection status
</div>
</div>
)}
</div>
</div>
{/* Action Buttons */}
@@ -323,22 +521,42 @@ export default function ConnectionDiagnostics() {
<Button
onClick={resetGitHubConnection}
disabled={isRunning}
disabled={isRunning || !diagnosticResults?.localStorage.hasGithubConnection}
variant="outline"
className="flex items-center gap-2 hover:bg-bolt-elements-item-backgroundActive/10 hover:text-bolt-elements-textPrimary dark:hover:bg-bolt-elements-item-backgroundActive/10 dark:hover:text-bolt-elements-textPrimary transition-colors"
className="flex items-center gap-2 hover:bg-bolt-elements-item-backgroundActive/10 hover:text-bolt-elements-textPrimary dark:hover:bg-bolt-elements-item-backgroundActive/10 dark:hover:text-bolt-elements-textPrimary transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
<div className="i-ph:github-logo w-4 h-4" />
Reset GitHub Connection
Reset GitHub
</Button>
<Button
onClick={resetNetlifyConnection}
disabled={isRunning}
disabled={isRunning || !diagnosticResults?.localStorage.hasNetlifyConnection}
variant="outline"
className="flex items-center gap-2 hover:bg-bolt-elements-item-backgroundActive/10 hover:text-bolt-elements-textPrimary dark:hover:bg-bolt-elements-item-backgroundActive/10 dark:hover:text-bolt-elements-textPrimary transition-colors"
className="flex items-center gap-2 hover:bg-bolt-elements-item-backgroundActive/10 hover:text-bolt-elements-textPrimary dark:hover:bg-bolt-elements-item-backgroundActive/10 dark:hover:text-bolt-elements-textPrimary transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
<div className="i-si:netlify w-4 h-4" />
Reset Netlify Connection
Reset Netlify
</Button>
<Button
onClick={resetVercelConnection}
disabled={isRunning || !diagnosticResults?.localStorage.hasVercelConnection}
variant="outline"
className="flex items-center gap-2 hover:bg-bolt-elements-item-backgroundActive/10 hover:text-bolt-elements-textPrimary dark:hover:bg-bolt-elements-item-backgroundActive/10 dark:hover:text-bolt-elements-textPrimary transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
<div className="i-si:vercel w-4 h-4" />
Reset Vercel
</Button>
<Button
onClick={resetSupabaseConnection}
disabled={isRunning || !diagnosticResults?.localStorage.hasSupabaseConnection}
variant="outline"
className="flex items-center gap-2 hover:bg-bolt-elements-item-backgroundActive/10 hover:text-bolt-elements-textPrimary dark:hover:bg-bolt-elements-item-backgroundActive/10 dark:hover:text-bolt-elements-textPrimary transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
>
<div className="i-si:supabase w-4 h-4" />
Reset Supabase
</Button>
</div>