Improve Supabase connection testing
Enhance connection testing to provide more detailed error information.
This commit is contained in:
@@ -3,10 +3,19 @@
|
|||||||
export const getSupabaseUrl = () => {
|
export const getSupabaseUrl = () => {
|
||||||
// 로컬 스토리지에서 설정된 URL을 우선 사용
|
// 로컬 스토리지에서 설정된 URL을 우선 사용
|
||||||
const storedUrl = localStorage.getItem('supabase_url');
|
const storedUrl = localStorage.getItem('supabase_url');
|
||||||
if (storedUrl) return storedUrl;
|
if (storedUrl) {
|
||||||
|
// CORS 프록시 설정 확인
|
||||||
|
const useProxy = localStorage.getItem('use_cors_proxy') === 'true';
|
||||||
|
if (useProxy && storedUrl.startsWith('http://')) {
|
||||||
|
// CORS 프록시 URL로 변환
|
||||||
|
return `https://corsproxy.io/?${encodeURIComponent(storedUrl)}`;
|
||||||
|
}
|
||||||
|
return storedUrl;
|
||||||
|
}
|
||||||
|
|
||||||
// 환경 변수 또는 기본값 사용
|
// 환경 변수 또는 기본값 사용
|
||||||
return process.env.SUPABASE_URL || 'http://a11.ism.kr:8000';
|
const defaultUrl = process.env.SUPABASE_URL || 'http://a11.ism.kr:8000';
|
||||||
|
return defaultUrl;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const getSupabaseKey = () => {
|
export const getSupabaseKey = () => {
|
||||||
@@ -18,12 +27,29 @@ export const getSupabaseKey = () => {
|
|||||||
return process.env.SUPABASE_ANON_KEY || 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlIiwiaWF0IjoxNzQyMDM5MzU4LCJleHAiOjQ4OTU2MzkzNTh9.XK0vetdwv_H2MHj4ewTfZGtSbrbSNDaV5xJhNo_Hdp8';
|
return process.env.SUPABASE_ANON_KEY || 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlIiwiaWF0IjoxNzQyMDM5MzU4LCJleHAiOjQ4OTU2MzkzNTh9.XK0vetdwv_H2MHj4ewTfZGtSbrbSNDaV5xJhNo_Hdp8';
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// CORS 프록시 사용 여부 설정
|
||||||
|
export const useCorsProxy = (enabled: boolean) => {
|
||||||
|
localStorage.setItem('use_cors_proxy', enabled.toString());
|
||||||
|
};
|
||||||
|
|
||||||
|
// CORS 프록시 사용 여부 확인
|
||||||
|
export const isCorsProxyEnabled = () => {
|
||||||
|
return localStorage.getItem('use_cors_proxy') === 'true';
|
||||||
|
};
|
||||||
|
|
||||||
// 온프레미스 연결을 위한 설정 도우미 함수
|
// 온프레미스 연결을 위한 설정 도우미 함수
|
||||||
export const configureSupabase = (url: string, key: string) => {
|
export const configureSupabase = (url: string, key: string, useProxy: boolean = false) => {
|
||||||
// 로컬 스토리지에 설정 저장
|
// 로컬 스토리지에 설정 저장
|
||||||
localStorage.setItem('supabase_url', url);
|
localStorage.setItem('supabase_url', url);
|
||||||
localStorage.setItem('supabase_key', key);
|
localStorage.setItem('supabase_key', key);
|
||||||
|
localStorage.setItem('use_cors_proxy', useProxy.toString());
|
||||||
|
|
||||||
// 페이지 새로고침 - 새로운 설정으로 Supabase 클라이언트 초기화
|
// 페이지 새로고침 - 새로운 설정으로 Supabase 클라이언트 초기화
|
||||||
window.location.reload();
|
window.location.reload();
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// 원본 URL 반환 (프록시 없는 URL)
|
||||||
|
export const getOriginalSupabaseUrl = () => {
|
||||||
|
const storedUrl = localStorage.getItem('supabase_url');
|
||||||
|
return storedUrl || process.env.SUPABASE_URL || 'http://a11.ism.kr:8000';
|
||||||
|
};
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
|
|
||||||
import { supabase } from './client';
|
import { supabase } from './client';
|
||||||
|
import { getSupabaseUrl, getSupabaseKey, getOriginalSupabaseUrl, isCorsProxyEnabled } from './config';
|
||||||
|
|
||||||
// 테스트용 직접 로그인 함수 (디버깅 전용)
|
// 테스트용 직접 로그인 함수 (디버깅 전용)
|
||||||
export const testSupabaseLogin = async (email: string, password: string) => {
|
export const testSupabaseLogin = async (email: string, password: string) => {
|
||||||
@@ -27,7 +28,9 @@ export const testSupabaseLogin = async (email: string, password: string) => {
|
|||||||
// API 테스트 도우미 함수
|
// API 테스트 도우미 함수
|
||||||
export const testSupabaseConnection = async () => {
|
export const testSupabaseConnection = async () => {
|
||||||
const results = {
|
const results = {
|
||||||
url: getSupabaseUrl(),
|
url: getOriginalSupabaseUrl(), // 원본 URL
|
||||||
|
proxyUrl: getSupabaseUrl(), // 프록시 적용된 URL
|
||||||
|
usingProxy: isCorsProxyEnabled(), // 프록시 사용 여부
|
||||||
client: !!supabase,
|
client: !!supabase,
|
||||||
restApi: false,
|
restApi: false,
|
||||||
auth: false,
|
auth: false,
|
||||||
@@ -39,7 +42,10 @@ export const testSupabaseConnection = async () => {
|
|||||||
// 1. REST API 접근 테스트
|
// 1. REST API 접근 테스트
|
||||||
try {
|
try {
|
||||||
console.log('REST API 테스트 시작...');
|
console.log('REST API 테스트 시작...');
|
||||||
const response = await fetch(`${results.url}/rest/v1/`, {
|
const apiUrl = `${results.proxyUrl}/rest/v1/`;
|
||||||
|
console.log('REST API 테스트 URL:', apiUrl);
|
||||||
|
|
||||||
|
const response = await fetch(apiUrl, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
headers: {
|
headers: {
|
||||||
'Content-Type': 'application/json',
|
'Content-Type': 'application/json',
|
||||||
@@ -108,12 +114,3 @@ export const testSupabaseConnection = async () => {
|
|||||||
console.log('Supabase 연결 테스트 결과:', results);
|
console.log('Supabase 연결 테스트 결과:', results);
|
||||||
return results;
|
return results;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 필요한 함수 불러오기
|
|
||||||
function getSupabaseUrl() {
|
|
||||||
return localStorage.getItem('supabase_url') || process.env.SUPABASE_URL || 'http://a11.ism.kr:8000';
|
|
||||||
}
|
|
||||||
|
|
||||||
function getSupabaseKey() {
|
|
||||||
return localStorage.getItem('supabase_key') || process.env.SUPABASE_ANON_KEY || 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJyb2xlIjoiYW5vbiIsImlzcyI6InN1cGFiYXNlIiwiaWF0IjoxNzQyMDM5MzU4LCJleHAiOjQ4OTU2MzkzNTh9.XK0vetdwv_H2MHj4ewTfZGtSbrbSNDaV5xJhNo_Hdp8';
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { Link, useNavigate } from "react-router-dom";
|
|||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { ArrowRight, Mail, KeyRound, Eye, EyeOff, RefreshCw } from "lucide-react";
|
import { ArrowRight, Mail, KeyRound, Eye, EyeOff, RefreshCw, Shield } from "lucide-react";
|
||||||
import { useToast } from "@/hooks/useToast.wrapper";
|
import { useToast } from "@/hooks/useToast.wrapper";
|
||||||
import { useAuth } from "@/contexts/auth";
|
import { useAuth } from "@/contexts/auth";
|
||||||
import { testSupabaseConnection } from "@/lib/supabase";
|
import { testSupabaseConnection } from "@/lib/supabase";
|
||||||
@@ -228,6 +228,15 @@ const Login = () => {
|
|||||||
<p className="font-medium">Supabase URL:</p>
|
<p className="font-medium">Supabase URL:</p>
|
||||||
<p className="break-all bg-gray-100 p-1 rounded">{testResults?.url || '알 수 없음'}</p>
|
<p className="break-all bg-gray-100 p-1 rounded">{testResults?.url || '알 수 없음'}</p>
|
||||||
</div>
|
</div>
|
||||||
|
{testResults?.usingProxy && (
|
||||||
|
<div>
|
||||||
|
<p className="font-medium">CORS 프록시:</p>
|
||||||
|
<div className="flex items-center gap-1">
|
||||||
|
<Shield className="h-3 w-3 text-blue-500" />
|
||||||
|
<p className="break-all text-blue-500">활성화됨 - {testResults.proxyUrl}</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<div>
|
<div>
|
||||||
<p className="font-medium">클라이언트 초기화:</p>
|
<p className="font-medium">클라이언트 초기화:</p>
|
||||||
<p>{testResults?.client ? '성공' : '실패'}</p>
|
<p>{testResults?.client ? '성공' : '실패'}</p>
|
||||||
@@ -247,6 +256,15 @@ const Login = () => {
|
|||||||
<p>데이터베이스: {testResults.database ? '✅' : '❌'}</p>
|
<p>데이터베이스: {testResults.database ? '✅' : '❌'}</p>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
<div className="text-right pt-1">
|
||||||
|
<Link
|
||||||
|
to="/supabase-settings"
|
||||||
|
className="inline-flex items-center text-neuro-income hover:underline"
|
||||||
|
>
|
||||||
|
<span>Supabase 설정 변경</span>
|
||||||
|
<ArrowRight className="h-3 w-3 ml-1" />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</details>
|
</details>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -3,23 +3,30 @@ import React, { useState, useEffect } from 'react';
|
|||||||
import { Input } from "@/components/ui/input";
|
import { Input } from "@/components/ui/input";
|
||||||
import { Button } from "@/components/ui/button";
|
import { Button } from "@/components/ui/button";
|
||||||
import { Label } from "@/components/ui/label";
|
import { Label } from "@/components/ui/label";
|
||||||
import { DatabaseIcon, Save, RefreshCw } from "lucide-react";
|
import { DatabaseIcon, Save, RefreshCw, Shield } from "lucide-react";
|
||||||
import { toast } from "@/hooks/useToast.wrapper";
|
import { toast } from "@/hooks/useToast.wrapper";
|
||||||
import { configureSupabase } from "@/lib/supabase";
|
import { configureSupabase, isCorsProxyEnabled } from "@/lib/supabase/config";
|
||||||
|
import { Switch } from "@/components/ui/switch";
|
||||||
|
import { testSupabaseConnection } from "@/lib/supabase";
|
||||||
import NavBar from '@/components/NavBar';
|
import NavBar from '@/components/NavBar';
|
||||||
|
|
||||||
const SupabaseSettings = () => {
|
const SupabaseSettings = () => {
|
||||||
const [supabaseUrl, setSupabaseUrl] = useState('');
|
const [supabaseUrl, setSupabaseUrl] = useState('');
|
||||||
const [supabaseKey, setSupabaseKey] = useState('');
|
const [supabaseKey, setSupabaseKey] = useState('');
|
||||||
|
const [useProxy, setUseProxy] = useState(false);
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
|
const [testResults, setTestResults] = useState<any>(null);
|
||||||
|
const [isTesting, setIsTesting] = useState(false);
|
||||||
|
|
||||||
// 저장된 설정 불러오기
|
// 저장된 설정 불러오기
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const savedUrl = localStorage.getItem('supabase_url');
|
const savedUrl = localStorage.getItem('supabase_url');
|
||||||
const savedKey = localStorage.getItem('supabase_key');
|
const savedKey = localStorage.getItem('supabase_key');
|
||||||
|
const proxyEnabled = isCorsProxyEnabled();
|
||||||
|
|
||||||
if (savedUrl) setSupabaseUrl(savedUrl);
|
if (savedUrl) setSupabaseUrl(savedUrl);
|
||||||
if (savedKey) setSupabaseKey(savedKey);
|
if (savedKey) setSupabaseKey(savedKey);
|
||||||
|
setUseProxy(proxyEnabled);
|
||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
const handleSave = () => {
|
const handleSave = () => {
|
||||||
@@ -36,7 +43,7 @@ const SupabaseSettings = () => {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
// Supabase 설정 적용
|
// Supabase 설정 적용
|
||||||
configureSupabase(supabaseUrl, supabaseKey);
|
configureSupabase(supabaseUrl, supabaseKey, useProxy);
|
||||||
|
|
||||||
toast({
|
toast({
|
||||||
title: "설정 저장 완료",
|
title: "설정 저장 완료",
|
||||||
@@ -53,6 +60,36 @@ const SupabaseSettings = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const runConnectionTest = async () => {
|
||||||
|
setIsTesting(true);
|
||||||
|
try {
|
||||||
|
const results = await testSupabaseConnection();
|
||||||
|
setTestResults(results);
|
||||||
|
|
||||||
|
if (results.errors.length === 0 || (results.auth && results.restApi && results.database)) {
|
||||||
|
toast({
|
||||||
|
title: "연결 테스트 성공",
|
||||||
|
description: "Supabase 서버 연결이 정상입니다.",
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
toast({
|
||||||
|
title: "연결 테스트 실패",
|
||||||
|
description: `오류 발생: ${results.errors[0]}`,
|
||||||
|
variant: "destructive"
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (error: any) {
|
||||||
|
console.error("연결 테스트 중 오류:", error);
|
||||||
|
toast({
|
||||||
|
title: "연결 테스트 오류",
|
||||||
|
description: "테스트 실행 중 예상치 못한 오류가 발생했습니다.",
|
||||||
|
variant: "destructive"
|
||||||
|
});
|
||||||
|
} finally {
|
||||||
|
setIsTesting(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="min-h-screen bg-neuro-background pb-24">
|
<div className="min-h-screen bg-neuro-background pb-24">
|
||||||
<div className="max-w-md mx-auto px-6">
|
<div className="max-w-md mx-auto px-6">
|
||||||
@@ -101,6 +138,27 @@ const SupabaseSettings = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* CORS 프록시 설정 추가 */}
|
||||||
|
<div className="flex items-center justify-between space-x-2 py-2">
|
||||||
|
<div className="space-y-0.5">
|
||||||
|
<Label htmlFor="cors-proxy" className="cursor-pointer">
|
||||||
|
<div className="flex items-center">
|
||||||
|
<Shield className="h-4 w-4 mr-2 text-neuro-income" />
|
||||||
|
<span>CORS 프록시 사용</span>
|
||||||
|
</div>
|
||||||
|
<p className="text-xs text-gray-500">
|
||||||
|
HTTP URL에 접근 문제가 있는 경우 활성화하세요
|
||||||
|
</p>
|
||||||
|
</Label>
|
||||||
|
</div>
|
||||||
|
<Switch
|
||||||
|
id="cors-proxy"
|
||||||
|
checked={useProxy}
|
||||||
|
onCheckedChange={setUseProxy}
|
||||||
|
className="data-[state=checked]:bg-neuro-income"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
<Button
|
<Button
|
||||||
onClick={handleSave}
|
onClick={handleSave}
|
||||||
disabled={isSaving}
|
disabled={isSaving}
|
||||||
@@ -121,6 +179,67 @@ const SupabaseSettings = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
{/* 연결 테스트 섹션 */}
|
||||||
|
<div className="neuro-card p-6 mb-6">
|
||||||
|
<h2 className="text-lg font-semibold mb-4">연결 테스트</h2>
|
||||||
|
|
||||||
|
<Button
|
||||||
|
onClick={runConnectionTest}
|
||||||
|
disabled={isTesting}
|
||||||
|
variant="outline"
|
||||||
|
className="w-full mb-4"
|
||||||
|
>
|
||||||
|
{isTesting ? (
|
||||||
|
<>
|
||||||
|
<RefreshCw className="mr-2 h-4 w-4 animate-spin" />
|
||||||
|
테스트 중...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<RefreshCw className="mr-2 h-4 w-4" />
|
||||||
|
연결 테스트 실행
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
|
||||||
|
{testResults && (
|
||||||
|
<div className="mt-4 space-y-3 text-sm">
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span className="font-medium">REST API:</span>
|
||||||
|
<span className={testResults.restApi ? 'text-green-500' : 'text-red-500'}>
|
||||||
|
{testResults.restApi ? '✅ 성공' : '❌ 실패'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span className="font-medium">인증:</span>
|
||||||
|
<span className={testResults.auth ? 'text-green-500' : 'text-red-500'}>
|
||||||
|
{testResults.auth ? '✅ 성공' : '❌ 실패'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="flex justify-between">
|
||||||
|
<span className="font-medium">데이터베이스:</span>
|
||||||
|
<span className={testResults.database ? 'text-green-500' : 'text-red-500'}>
|
||||||
|
{testResults.database ? '✅ 성공' : '❌ 실패'}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{testResults.usingProxy && (
|
||||||
|
<div className="bg-blue-50 border border-blue-200 rounded p-2 mt-2">
|
||||||
|
<p className="text-xs">CORS 프록시 사용 중: {testResults.proxyUrl}</p>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{testResults.errors.length > 0 && (
|
||||||
|
<div className="bg-red-50 border border-red-200 rounded p-2 mt-2">
|
||||||
|
{testResults.errors.map((error: string, index: number) => (
|
||||||
|
<p key={index} className="text-xs text-red-600">{error}</p>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
<div className="neuro-card p-6">
|
<div className="neuro-card p-6">
|
||||||
<h2 className="text-lg font-semibold mb-2">도움말</h2>
|
<h2 className="text-lg font-semibold mb-2">도움말</h2>
|
||||||
<p className="text-gray-500 text-sm mb-4">
|
<p className="text-gray-500 text-sm mb-4">
|
||||||
@@ -134,6 +253,9 @@ const SupabaseSettings = () => {
|
|||||||
<li>
|
<li>
|
||||||
<strong>Anon Key</strong>: Supabase 대시보드의 API 설정에서 찾을 수 있는 anon/public 키를 입력합니다.
|
<strong>Anon Key</strong>: Supabase 대시보드의 API 설정에서 찾을 수 있는 anon/public 키를 입력합니다.
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>CORS 프록시</strong>: HTTP URL에 대한 브라우저 보안 제한을 우회하기 위해 사용합니다. HTTPS가 아닌 URL에 접근 문제가 있는 경우 활성화하세요.
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
Reference in New Issue
Block a user