외부 REST API 연결 확장

This commit is contained in:
dohyeons
2025-11-28 11:35:36 +09:00
parent b70ed8aaff
commit 39d327fb45
5 changed files with 308 additions and 154 deletions

View File

@@ -6,6 +6,7 @@ import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { Label } from "@/components/ui/label";
import { Select, SelectContent, SelectItem, SelectTrigger, SelectValue } from "@/components/ui/select";
import { Textarea } from "@/components/ui/textarea";
import { Plus, Trash2, Loader2, CheckCircle, XCircle } from "lucide-react";
import { ExternalDbConnectionAPI, ExternalApiConnection } from "@/lib/api/externalDbConnection";
import { getApiUrl } from "@/lib/utils/apiUrl";
@@ -20,7 +21,7 @@ export default function MultiApiConfig({ dataSource, onChange, onTestResult }: M
const [testing, setTesting] = useState(false);
const [testResult, setTestResult] = useState<{ success: boolean; message: string } | null>(null);
const [apiConnections, setApiConnections] = useState<ExternalApiConnection[]>([]);
const [selectedConnectionId, setSelectedConnectionId] = useState<string>("");
const [selectedConnectionId, setSelectedConnectionId] = useState<string>(dataSource.externalConnectionId || "");
const [availableColumns, setAvailableColumns] = useState<string[]>([]); // API 테스트 후 발견된 컬럼 목록
const [columnTypes, setColumnTypes] = useState<Record<string, string>>({}); // 컬럼 타입 정보
const [sampleData, setSampleData] = useState<any[]>([]); // 샘플 데이터 (최대 3개)
@@ -35,6 +36,13 @@ export default function MultiApiConfig({ dataSource, onChange, onTestResult }: M
loadApiConnections();
}, []);
// dataSource.externalConnectionId가 변경되면 selectedConnectionId 업데이트
useEffect(() => {
if (dataSource.externalConnectionId) {
setSelectedConnectionId(dataSource.externalConnectionId);
}
}, [dataSource.externalConnectionId]);
// 외부 커넥션 선택 핸들러
const handleConnectionSelect = async (connectionId: string) => {
setSelectedConnectionId(connectionId);
@@ -58,11 +66,20 @@ export default function MultiApiConfig({ dataSource, onChange, onTestResult }: M
const updates: Partial<ChartDataSource> = {
endpoint: fullEndpoint,
externalConnectionId: connectionId, // 외부 연결 ID 저장
};
const headers: KeyValuePair[] = [];
const queryParams: KeyValuePair[] = [];
// 기본 메서드/바디가 있으면 적용
if (connection.default_method) {
updates.method = connection.default_method as ChartDataSource["method"];
}
if (connection.default_body) {
updates.body = connection.default_body;
}
// 기본 헤더가 있으면 적용
if (connection.default_headers && Object.keys(connection.default_headers).length > 0) {
Object.entries(connection.default_headers).forEach(([key, value]) => {
@@ -210,6 +227,11 @@ export default function MultiApiConfig({ dataSource, onChange, onTestResult }: M
}
});
const bodyPayload =
dataSource.body && dataSource.body.trim().length > 0
? dataSource.body
: undefined;
const response = await fetch(getApiUrl("/api/dashboards/fetch-external-api"), {
method: "POST",
headers: { "Content-Type": "application/json" },
@@ -219,6 +241,8 @@ export default function MultiApiConfig({ dataSource, onChange, onTestResult }: M
method: dataSource.method || "GET",
headers,
queryParams,
body: bodyPayload,
externalConnectionId: dataSource.externalConnectionId, // 외부 연결 ID 전달
}),
});
@@ -415,6 +439,58 @@ export default function MultiApiConfig({ dataSource, onChange, onTestResult }: M
</p>
</div>
{/* HTTP 메서드 */}
<div className="space-y-2">
<Label className="text-xs">HTTP </Label>
<Select
value={dataSource.method || "GET"}
onValueChange={(value) =>
onChange({
method: value as ChartDataSource["method"],
})
}
>
<SelectTrigger className="h-8 text-xs">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="GET" className="text-xs">
GET
</SelectItem>
<SelectItem value="POST" className="text-xs">
POST
</SelectItem>
<SelectItem value="PUT" className="text-xs">
PUT
</SelectItem>
<SelectItem value="DELETE" className="text-xs">
DELETE
</SelectItem>
<SelectItem value="PATCH" className="text-xs">
PATCH
</SelectItem>
</SelectContent>
</Select>
</div>
{/* Request Body (POST/PUT/PATCH 일 때만) */}
{(dataSource.method === "POST" ||
dataSource.method === "PUT" ||
dataSource.method === "PATCH") && (
<div className="space-y-2">
<Label className="text-xs">Request Body ()</Label>
<Textarea
value={dataSource.body || ""}
onChange={(e) => onChange({ body: e.target.value })}
placeholder='{"key": "value"} 또는 원시 페이로드를 그대로 입력하세요'
className="h-24 text-xs font-mono"
/>
<p className="text-[10px] text-muted-foreground">
API Body로 . JSON이 .
</p>
</div>
)}
{/* JSON Path */}
<div className="space-y-2">
<Label htmlFor={`jsonPath-\${dataSource.id}`} className="text-xs">