Refactor SupabaseSettings component
The SupabaseSettings component was refactored into smaller, more manageable components to improve code organization and maintainability. The UI and functionality remain unchanged.
This commit is contained in:
116
src/components/supabase/SupabaseConnectionTest.tsx
Normal file
116
src/components/supabase/SupabaseConnectionTest.tsx
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
|
||||||
|
import React, { useState } from 'react';
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { RefreshCw } from "lucide-react";
|
||||||
|
import { toast } from "@/hooks/useToast.wrapper";
|
||||||
|
import { testSupabaseConnection } from '@/lib/supabase';
|
||||||
|
|
||||||
|
interface TestResult {
|
||||||
|
url: string;
|
||||||
|
proxyUrl: string;
|
||||||
|
usingProxy: boolean;
|
||||||
|
client: boolean;
|
||||||
|
restApi: boolean;
|
||||||
|
auth: boolean;
|
||||||
|
database: boolean;
|
||||||
|
errors: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
const SupabaseConnectionTest: React.FC = () => {
|
||||||
|
const [testResults, setTestResults] = useState<TestResult | null>(null);
|
||||||
|
const [isTesting, setIsTesting] = useState(false);
|
||||||
|
|
||||||
|
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 (
|
||||||
|
<div>
|
||||||
|
<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>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SupabaseConnectionTest;
|
||||||
30
src/components/supabase/SupabaseHelpSection.tsx
Normal file
30
src/components/supabase/SupabaseHelpSection.tsx
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
|
||||||
|
import React from 'react';
|
||||||
|
|
||||||
|
const SupabaseHelpSection: React.FC = () => {
|
||||||
|
return (
|
||||||
|
<div>
|
||||||
|
<h2 className="text-lg font-semibold mb-2">도움말</h2>
|
||||||
|
<p className="text-gray-500 text-sm mb-4">
|
||||||
|
온프레미스 Supabase 설정은 다음과 같이 구성됩니다:
|
||||||
|
</p>
|
||||||
|
<ul className="list-disc pl-5 text-sm text-gray-500 space-y-2">
|
||||||
|
<li>
|
||||||
|
<strong>Supabase URL</strong>: 온프레미스로 설치한 Supabase 인스턴스의 URL을 입력합니다.
|
||||||
|
(예: https://192.168.1.100:8000)
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>Anon Key</strong>: Supabase 대시보드의 API 설정에서 찾을 수 있는 anon/public 키를 입력합니다.
|
||||||
|
</li>
|
||||||
|
<li>
|
||||||
|
<strong>CORS 프록시</strong>: HTTP URL에 대한 브라우저 보안 제한을 우회하기 위해 사용합니다. HTTPS가 아닌 URL에 접근 문제가 있는 경우 활성화하세요.
|
||||||
|
</li>
|
||||||
|
<li className="text-amber-500 font-medium">
|
||||||
|
가능하면 HTTPS URL을 사용하는 것이 좋습니다. HTTP URL을 사용해야 하는 경우 CORS 프록시를 활성화하세요.
|
||||||
|
</li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SupabaseHelpSection;
|
||||||
177
src/components/supabase/SupabaseSettingsForm.tsx
Normal file
177
src/components/supabase/SupabaseSettingsForm.tsx
Normal file
@@ -0,0 +1,177 @@
|
|||||||
|
|
||||||
|
import React, { useState, useEffect } from 'react';
|
||||||
|
import { Input } from "@/components/ui/input";
|
||||||
|
import { Button } from "@/components/ui/button";
|
||||||
|
import { Label } from "@/components/ui/label";
|
||||||
|
import { DatabaseIcon, Save, RefreshCw, Shield, AlertTriangle, Check } from "lucide-react";
|
||||||
|
import { toast } from "@/hooks/useToast.wrapper";
|
||||||
|
import { configureSupabase, isCorsProxyEnabled } from "@/lib/supabase/config";
|
||||||
|
import { Switch } from "@/components/ui/switch";
|
||||||
|
|
||||||
|
interface SupabaseSettingsFormProps {
|
||||||
|
onSaved: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const SupabaseSettingsForm: React.FC<SupabaseSettingsFormProps> = ({ onSaved }) => {
|
||||||
|
const [supabaseUrl, setSupabaseUrl] = useState('');
|
||||||
|
const [supabaseKey, setSupabaseKey] = useState('');
|
||||||
|
const [useProxy, setUseProxy] = useState(false);
|
||||||
|
const [isSaving, setIsSaving] = useState(false);
|
||||||
|
|
||||||
|
// 저장된 설정 불러오기
|
||||||
|
useEffect(() => {
|
||||||
|
const savedUrl = localStorage.getItem('supabase_url');
|
||||||
|
const savedKey = localStorage.getItem('supabase_key');
|
||||||
|
const proxyEnabled = isCorsProxyEnabled();
|
||||||
|
|
||||||
|
if (savedUrl) setSupabaseUrl(savedUrl);
|
||||||
|
if (savedKey) setSupabaseKey(savedKey);
|
||||||
|
setUseProxy(proxyEnabled);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const validateUrl = (url: string): boolean => {
|
||||||
|
// URL 유효성 검사: http:// 또는 https://로 시작하는지 확인
|
||||||
|
return /^https?:\/\/.+/.test(url);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSave = () => {
|
||||||
|
if (!supabaseUrl || !supabaseKey) {
|
||||||
|
toast({
|
||||||
|
title: "입력 오류",
|
||||||
|
description: "Supabase URL과 Anon Key를 모두 입력해주세요.",
|
||||||
|
variant: "destructive"
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!validateUrl(supabaseUrl)) {
|
||||||
|
toast({
|
||||||
|
title: "URL 오류",
|
||||||
|
description: "유효한 URL 형식이 아닙니다. http:// 또는 https://로 시작하는 URL을 입력해주세요.",
|
||||||
|
variant: "destructive"
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
setIsSaving(true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Supabase 설정 적용
|
||||||
|
configureSupabase(supabaseUrl, supabaseKey, useProxy);
|
||||||
|
|
||||||
|
toast({
|
||||||
|
title: "설정 저장 완료",
|
||||||
|
description: "Supabase 설정이 저장되었습니다. 변경사항을 적용합니다.",
|
||||||
|
});
|
||||||
|
|
||||||
|
onSaved();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Supabase 설정 저장 오류:', error);
|
||||||
|
toast({
|
||||||
|
title: "설정 저장 실패",
|
||||||
|
description: "Supabase 설정을 저장하는 중 오류가 발생했습니다.",
|
||||||
|
variant: "destructive"
|
||||||
|
});
|
||||||
|
setIsSaving(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const isHttpsUrl = supabaseUrl.startsWith("https://");
|
||||||
|
const suggestProxy = supabaseUrl.startsWith("http://") && !useProxy;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="space-y-6">
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="supabase-url" className="text-base">Supabase URL</Label>
|
||||||
|
<div className="relative">
|
||||||
|
<DatabaseIcon className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-500 h-5 w-5" />
|
||||||
|
<Input
|
||||||
|
id="supabase-url"
|
||||||
|
placeholder="http://your-supabase-url.com"
|
||||||
|
value={supabaseUrl}
|
||||||
|
onChange={(e) => setSupabaseUrl(e.target.value)}
|
||||||
|
className="pl-10 neuro-pressed"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{/* URL 관련 경고 및 안내 추가 */}
|
||||||
|
{suggestProxy && (
|
||||||
|
<div className="flex items-center text-amber-500 text-xs mt-1 p-2 bg-amber-50 rounded-md">
|
||||||
|
<AlertTriangle className="h-4 w-4 mr-1 flex-shrink-0" />
|
||||||
|
<span>HTTP URL을 사용하고 계십니다. CORS 프록시 활성화를 권장합니다.</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
{isHttpsUrl && (
|
||||||
|
<div className="flex items-center text-green-500 text-xs mt-1 p-2 bg-green-50 rounded-md">
|
||||||
|
<Check className="h-4 w-4 mr-1 flex-shrink-0" />
|
||||||
|
<span>HTTPS URL을 사용하고 있습니다. 권장됩니다.</span>
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="space-y-2">
|
||||||
|
<Label htmlFor="supabase-key" className="text-base">Supabase Anon Key</Label>
|
||||||
|
<div className="relative">
|
||||||
|
<svg
|
||||||
|
className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-500 h-5 w-5"
|
||||||
|
fill="none"
|
||||||
|
stroke="currentColor"
|
||||||
|
viewBox="0 0 24 24"
|
||||||
|
xmlns="http://www.w3.org/2000/svg"
|
||||||
|
>
|
||||||
|
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z" />
|
||||||
|
</svg>
|
||||||
|
<Input
|
||||||
|
id="supabase-key"
|
||||||
|
placeholder="your-anon-key"
|
||||||
|
value={supabaseKey}
|
||||||
|
onChange={(e) => setSupabaseKey(e.target.value)}
|
||||||
|
className="pl-10 neuro-pressed"
|
||||||
|
/>
|
||||||
|
</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
|
||||||
|
onClick={handleSave}
|
||||||
|
disabled={isSaving}
|
||||||
|
className="w-full bg-neuro-income text-white"
|
||||||
|
>
|
||||||
|
{isSaving ? (
|
||||||
|
<>
|
||||||
|
<RefreshCw className="mr-2 h-4 w-4 animate-spin" />
|
||||||
|
저장 중...
|
||||||
|
</>
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
|
<Save className="mr-2 h-4 w-4" />
|
||||||
|
설정 저장
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SupabaseSettingsForm;
|
||||||
@@ -34,7 +34,7 @@ try {
|
|||||||
console.log('Supabase fetch 요청:', url);
|
console.log('Supabase fetch 요청:', url);
|
||||||
|
|
||||||
// 기본 fetch 호출
|
// 기본 fetch 호출
|
||||||
return fetch(...args)
|
return fetch(urlOrRequest, args[1])
|
||||||
.then(response => {
|
.then(response => {
|
||||||
console.log('Supabase 응답 상태:', response.status);
|
console.log('Supabase 응답 상태:', response.status);
|
||||||
return response;
|
return response;
|
||||||
|
|||||||
@@ -1,112 +1,18 @@
|
|||||||
|
|
||||||
import React, { useState, useEffect } from 'react';
|
import React, { useState } from 'react';
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { Label } from "@/components/ui/label";
|
|
||||||
import { DatabaseIcon, Save, RefreshCw, Shield, AlertTriangle, Check } from "lucide-react";
|
|
||||||
import { toast } from "@/hooks/useToast.wrapper";
|
|
||||||
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';
|
||||||
|
import SupabaseSettingsForm from '@/components/supabase/SupabaseSettingsForm';
|
||||||
|
import SupabaseConnectionTest from '@/components/supabase/SupabaseConnectionTest';
|
||||||
|
import SupabaseHelpSection from '@/components/supabase/SupabaseHelpSection';
|
||||||
|
|
||||||
const SupabaseSettings = () => {
|
const SupabaseSettings = () => {
|
||||||
const [supabaseUrl, setSupabaseUrl] = useState('');
|
const [refreshCounter, setRefreshCounter] = useState(0);
|
||||||
const [supabaseKey, setSupabaseKey] = useState('');
|
|
||||||
const [useProxy, setUseProxy] = useState(false);
|
|
||||||
const [isSaving, setIsSaving] = useState(false);
|
|
||||||
const [testResults, setTestResults] = useState<any>(null);
|
|
||||||
const [isTesting, setIsTesting] = useState(false);
|
|
||||||
|
|
||||||
// 저장된 설정 불러오기
|
const handleSettingsSaved = () => {
|
||||||
useEffect(() => {
|
// 설정이 저장될 때 테스트 컴포넌트를 다시 렌더링하기 위한 카운터 증가
|
||||||
const savedUrl = localStorage.getItem('supabase_url');
|
setRefreshCounter(prev => prev + 1);
|
||||||
const savedKey = localStorage.getItem('supabase_key');
|
|
||||||
const proxyEnabled = isCorsProxyEnabled();
|
|
||||||
|
|
||||||
if (savedUrl) setSupabaseUrl(savedUrl);
|
|
||||||
if (savedKey) setSupabaseKey(savedKey);
|
|
||||||
setUseProxy(proxyEnabled);
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
const validateUrl = (url: string): boolean => {
|
|
||||||
// URL 유효성 검사: http:// 또는 https://로 시작하는지 확인
|
|
||||||
return /^https?:\/\/.+/.test(url);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleSave = () => {
|
|
||||||
if (!supabaseUrl || !supabaseKey) {
|
|
||||||
toast({
|
|
||||||
title: "입력 오류",
|
|
||||||
description: "Supabase URL과 Anon Key를 모두 입력해주세요.",
|
|
||||||
variant: "destructive"
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!validateUrl(supabaseUrl)) {
|
|
||||||
toast({
|
|
||||||
title: "URL 오류",
|
|
||||||
description: "유효한 URL 형식이 아닙니다. http:// 또는 https://로 시작하는 URL을 입력해주세요.",
|
|
||||||
variant: "destructive"
|
|
||||||
});
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
setIsSaving(true);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Supabase 설정 적용
|
|
||||||
configureSupabase(supabaseUrl, supabaseKey, useProxy);
|
|
||||||
|
|
||||||
toast({
|
|
||||||
title: "설정 저장 완료",
|
|
||||||
description: "Supabase 설정이 저장되었습니다. 변경사항을 적용합니다.",
|
|
||||||
});
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Supabase 설정 저장 오류:', error);
|
|
||||||
toast({
|
|
||||||
title: "설정 저장 실패",
|
|
||||||
description: "Supabase 설정을 저장하는 중 오류가 발생했습니다.",
|
|
||||||
variant: "destructive"
|
|
||||||
});
|
|
||||||
setIsSaving(false);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
const isHttpsUrl = supabaseUrl.startsWith("https://");
|
|
||||||
const suggestProxy = supabaseUrl.startsWith("http://") && !useProxy;
|
|
||||||
|
|
||||||
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">
|
||||||
@@ -118,180 +24,16 @@ const SupabaseSettings = () => {
|
|||||||
</header>
|
</header>
|
||||||
|
|
||||||
<div className="neuro-card p-6 mb-6">
|
<div className="neuro-card p-6 mb-6">
|
||||||
<div className="space-y-6">
|
<SupabaseSettingsForm onSaved={handleSettingsSaved} />
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="supabase-url" className="text-base">Supabase URL</Label>
|
|
||||||
<div className="relative">
|
|
||||||
<DatabaseIcon className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-500 h-5 w-5" />
|
|
||||||
<Input
|
|
||||||
id="supabase-url"
|
|
||||||
placeholder="http://your-supabase-url.com"
|
|
||||||
value={supabaseUrl}
|
|
||||||
onChange={(e) => setSupabaseUrl(e.target.value)}
|
|
||||||
className="pl-10 neuro-pressed"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{/* URL 관련 경고 및 안내 추가 */}
|
|
||||||
{suggestProxy && (
|
|
||||||
<div className="flex items-center text-amber-500 text-xs mt-1 p-2 bg-amber-50 rounded-md">
|
|
||||||
<AlertTriangle className="h-4 w-4 mr-1 flex-shrink-0" />
|
|
||||||
<span>HTTP URL을 사용하고 계십니다. CORS 프록시 활성화를 권장합니다.</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
|
|
||||||
{isHttpsUrl && (
|
|
||||||
<div className="flex items-center text-green-500 text-xs mt-1 p-2 bg-green-50 rounded-md">
|
|
||||||
<Check className="h-4 w-4 mr-1 flex-shrink-0" />
|
|
||||||
<span>HTTPS URL을 사용하고 있습니다. 권장됩니다.</span>
|
|
||||||
</div>
|
|
||||||
)}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div className="space-y-2">
|
|
||||||
<Label htmlFor="supabase-key" className="text-base">Supabase Anon Key</Label>
|
|
||||||
<div className="relative">
|
|
||||||
<svg
|
|
||||||
className="absolute left-3 top-1/2 transform -translate-y-1/2 text-gray-500 h-5 w-5"
|
|
||||||
fill="none"
|
|
||||||
stroke="currentColor"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
>
|
|
||||||
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z" />
|
|
||||||
</svg>
|
|
||||||
<Input
|
|
||||||
id="supabase-key"
|
|
||||||
placeholder="your-anon-key"
|
|
||||||
value={supabaseKey}
|
|
||||||
onChange={(e) => setSupabaseKey(e.target.value)}
|
|
||||||
className="pl-10 neuro-pressed"
|
|
||||||
/>
|
|
||||||
</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
|
|
||||||
onClick={handleSave}
|
|
||||||
disabled={isSaving}
|
|
||||||
className="w-full bg-neuro-income text-white"
|
|
||||||
>
|
|
||||||
{isSaving ? (
|
|
||||||
<>
|
|
||||||
<RefreshCw className="mr-2 h-4 w-4 animate-spin" />
|
|
||||||
저장 중...
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
<>
|
|
||||||
<Save className="mr-2 h-4 w-4" />
|
|
||||||
설정 저장
|
|
||||||
</>
|
|
||||||
)}
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* 연결 테스트 섹션 */}
|
{/* 연결 테스트 섹션 */}
|
||||||
<div className="neuro-card p-6 mb-6">
|
<div className="neuro-card p-6 mb-6">
|
||||||
<h2 className="text-lg font-semibold mb-4">연결 테스트</h2>
|
<SupabaseConnectionTest key={`test-${refreshCounter}`} />
|
||||||
|
|
||||||
<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>
|
||||||
|
|
||||||
<div className="neuro-card p-6">
|
<div className="neuro-card p-6">
|
||||||
<h2 className="text-lg font-semibold mb-2">도움말</h2>
|
<SupabaseHelpSection />
|
||||||
<p className="text-gray-500 text-sm mb-4">
|
|
||||||
온프레미스 Supabase 설정은 다음과 같이 구성됩니다:
|
|
||||||
</p>
|
|
||||||
<ul className="list-disc pl-5 text-sm text-gray-500 space-y-2">
|
|
||||||
<li>
|
|
||||||
<strong>Supabase URL</strong>: 온프레미스로 설치한 Supabase 인스턴스의 URL을 입력합니다.
|
|
||||||
(예: https://192.168.1.100:8000)
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>Anon Key</strong>: Supabase 대시보드의 API 설정에서 찾을 수 있는 anon/public 키를 입력합니다.
|
|
||||||
</li>
|
|
||||||
<li>
|
|
||||||
<strong>CORS 프록시</strong>: HTTP URL에 대한 브라우저 보안 제한을 우회하기 위해 사용합니다. HTTPS가 아닌 URL에 접근 문제가 있는 경우 활성화하세요.
|
|
||||||
</li>
|
|
||||||
<li className="text-amber-500 font-medium">
|
|
||||||
가능하면 HTTPS URL을 사용하는 것이 좋습니다. HTTP URL을 사용해야 하는 경우 CORS 프록시를 활성화하세요.
|
|
||||||
</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user