rest api 설정 오류 해결
This commit is contained in:
parent
c8f66a9f18
commit
8799568cc6
|
|
@ -1,7 +1,7 @@
|
|||
"use client";
|
||||
|
||||
import React, { useState } from "react";
|
||||
import { ChartDataSource, QueryResult, ApiResponse } from "../types";
|
||||
import { ChartDataSource, QueryResult, KeyValuePair } from "../types";
|
||||
import { Card } from "@/components/ui/card";
|
||||
import { Label } from "@/components/ui/label";
|
||||
import { Input } from "@/components/ui/input";
|
||||
|
|
@ -25,48 +25,72 @@ export function ApiConfig({ dataSource, onChange, onTestResult }: ApiConfigProps
|
|||
const [testResult, setTestResult] = useState<QueryResult | null>(null);
|
||||
const [testError, setTestError] = useState<string | null>(null);
|
||||
|
||||
// 헤더를 배열로 정규화 (객체 형식 호환)
|
||||
const normalizeHeaders = (): KeyValuePair[] => {
|
||||
if (!dataSource.headers) return [];
|
||||
if (Array.isArray(dataSource.headers)) return dataSource.headers;
|
||||
// 객체 형식이면 배열로 변환
|
||||
return Object.entries(dataSource.headers as Record<string, string>).map(([key, value]) => ({
|
||||
id: `header_${Date.now()}_${Math.random()}`,
|
||||
key,
|
||||
value,
|
||||
}));
|
||||
};
|
||||
|
||||
// 헤더 추가
|
||||
const addHeader = () => {
|
||||
const headers = dataSource.headers || {};
|
||||
const newKey = `header_${Object.keys(headers).length + 1}`;
|
||||
onChange({ headers: { ...headers, [newKey]: "" } });
|
||||
const headers = normalizeHeaders();
|
||||
onChange({
|
||||
headers: [...headers, { id: `header_${Date.now()}`, key: "", value: "" }],
|
||||
});
|
||||
};
|
||||
|
||||
// 헤더 제거
|
||||
const removeHeader = (key: string) => {
|
||||
const headers = { ...dataSource.headers };
|
||||
delete headers[key];
|
||||
onChange({ headers });
|
||||
const removeHeader = (id: string) => {
|
||||
const headers = normalizeHeaders();
|
||||
onChange({ headers: headers.filter((h) => h.id !== id) });
|
||||
};
|
||||
|
||||
// 헤더 업데이트
|
||||
const updateHeader = (oldKey: string, newKey: string, value: string) => {
|
||||
const headers = { ...dataSource.headers };
|
||||
delete headers[oldKey];
|
||||
headers[newKey] = value;
|
||||
onChange({ headers });
|
||||
const updateHeader = (id: string, updates: Partial<KeyValuePair>) => {
|
||||
const headers = normalizeHeaders();
|
||||
onChange({
|
||||
headers: headers.map((h) => (h.id === id ? { ...h, ...updates } : h)),
|
||||
});
|
||||
};
|
||||
|
||||
// 쿼리 파라미터를 배열로 정규화 (객체 형식 호환)
|
||||
const normalizeQueryParams = (): KeyValuePair[] => {
|
||||
if (!dataSource.queryParams) return [];
|
||||
if (Array.isArray(dataSource.queryParams)) return dataSource.queryParams;
|
||||
// 객체 형식이면 배열로 변환
|
||||
return Object.entries(dataSource.queryParams as Record<string, string>).map(([key, value]) => ({
|
||||
id: `param_${Date.now()}_${Math.random()}`,
|
||||
key,
|
||||
value,
|
||||
}));
|
||||
};
|
||||
|
||||
// 쿼리 파라미터 추가
|
||||
const addQueryParam = () => {
|
||||
const queryParams = dataSource.queryParams || {};
|
||||
const newKey = `param_${Object.keys(queryParams).length + 1}`;
|
||||
onChange({ queryParams: { ...queryParams, [newKey]: "" } });
|
||||
const queryParams = normalizeQueryParams();
|
||||
onChange({
|
||||
queryParams: [...queryParams, { id: `param_${Date.now()}`, key: "", value: "" }],
|
||||
});
|
||||
};
|
||||
|
||||
// 쿼리 파라미터 제거
|
||||
const removeQueryParam = (key: string) => {
|
||||
const queryParams = { ...dataSource.queryParams };
|
||||
delete queryParams[key];
|
||||
onChange({ queryParams });
|
||||
const removeQueryParam = (id: string) => {
|
||||
const queryParams = normalizeQueryParams();
|
||||
onChange({ queryParams: queryParams.filter((p) => p.id !== id) });
|
||||
};
|
||||
|
||||
// 쿼리 파라미터 업데이트
|
||||
const updateQueryParam = (oldKey: string, newKey: string, value: string) => {
|
||||
const queryParams = { ...dataSource.queryParams };
|
||||
delete queryParams[oldKey];
|
||||
queryParams[newKey] = value;
|
||||
onChange({ queryParams });
|
||||
const updateQueryParam = (id: string, updates: Partial<KeyValuePair>) => {
|
||||
const queryParams = normalizeQueryParams();
|
||||
onChange({
|
||||
queryParams: queryParams.map((p) => (p.id === id ? { ...p, ...updates } : p)),
|
||||
});
|
||||
};
|
||||
|
||||
// API 테스트
|
||||
|
|
@ -82,14 +106,22 @@ export function ApiConfig({ dataSource, onChange, onTestResult }: ApiConfigProps
|
|||
|
||||
try {
|
||||
// 쿼리 파라미터 구성
|
||||
const params = new URLSearchParams();
|
||||
if (dataSource.queryParams) {
|
||||
Object.entries(dataSource.queryParams).forEach(([key, value]) => {
|
||||
if (key && value) {
|
||||
params.append(key, value);
|
||||
}
|
||||
});
|
||||
}
|
||||
const params: Record<string, string> = {};
|
||||
const normalizedQueryParams = normalizeQueryParams();
|
||||
normalizedQueryParams.forEach(({ key, value }) => {
|
||||
if (key && value) {
|
||||
params[key] = value;
|
||||
}
|
||||
});
|
||||
|
||||
// 헤더 구성
|
||||
const headers: Record<string, string> = {};
|
||||
const normalizedHeaders = normalizeHeaders();
|
||||
normalizedHeaders.forEach(({ key, value }) => {
|
||||
if (key && value) {
|
||||
headers[key] = value;
|
||||
}
|
||||
});
|
||||
|
||||
// 백엔드 프록시를 통한 외부 API 호출 (CORS 우회)
|
||||
const response = await fetch("http://localhost:8080/api/dashboards/fetch-external-api", {
|
||||
|
|
@ -100,8 +132,8 @@ export function ApiConfig({ dataSource, onChange, onTestResult }: ApiConfigProps
|
|||
body: JSON.stringify({
|
||||
url: dataSource.endpoint,
|
||||
method: "GET",
|
||||
headers: dataSource.headers || {},
|
||||
queryParams: Object.fromEntries(params),
|
||||
headers: headers,
|
||||
queryParams: params,
|
||||
}),
|
||||
});
|
||||
|
||||
|
|
@ -217,31 +249,34 @@ export function ApiConfig({ dataSource, onChange, onTestResult }: ApiConfigProps
|
|||
</Button>
|
||||
</div>
|
||||
|
||||
{dataSource.queryParams && Object.keys(dataSource.queryParams).length > 0 ? (
|
||||
<div className="space-y-2">
|
||||
{Object.entries(dataSource.queryParams).map(([key, value]) => (
|
||||
<div key={key} className="flex gap-2">
|
||||
<Input
|
||||
placeholder="key"
|
||||
value={key}
|
||||
onChange={(e) => updateQueryParam(key, e.target.value, value)}
|
||||
className="flex-1"
|
||||
/>
|
||||
<Input
|
||||
placeholder="value"
|
||||
value={value}
|
||||
onChange={(e) => updateQueryParam(key, key, e.target.value)}
|
||||
className="flex-1"
|
||||
/>
|
||||
<Button variant="ghost" size="icon" onClick={() => removeQueryParam(key)}>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="py-2 text-center text-sm text-gray-500">추가된 파라미터가 없습니다</p>
|
||||
)}
|
||||
{(() => {
|
||||
const params = normalizeQueryParams();
|
||||
return params.length > 0 ? (
|
||||
<div className="space-y-2">
|
||||
{params.map((param) => (
|
||||
<div key={param.id} className="flex gap-2">
|
||||
<Input
|
||||
placeholder="key"
|
||||
value={param.key}
|
||||
onChange={(e) => updateQueryParam(param.id, { key: e.target.value })}
|
||||
className="flex-1"
|
||||
/>
|
||||
<Input
|
||||
placeholder="value"
|
||||
value={param.value}
|
||||
onChange={(e) => updateQueryParam(param.id, { value: e.target.value })}
|
||||
className="flex-1"
|
||||
/>
|
||||
<Button variant="ghost" size="icon" onClick={() => removeQueryParam(param.id)}>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="py-2 text-center text-sm text-gray-500">추가된 파라미터가 없습니다</p>
|
||||
);
|
||||
})()}
|
||||
|
||||
<p className="text-xs text-gray-500">예: category=electronics, limit=10</p>
|
||||
</Card>
|
||||
|
|
@ -262,8 +297,9 @@ export function ApiConfig({ dataSource, onChange, onTestResult }: ApiConfigProps
|
|||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
const headers = normalizeHeaders();
|
||||
onChange({
|
||||
headers: { ...dataSource.headers, Authorization: "Bearer YOUR_TOKEN" },
|
||||
headers: [...headers, { id: `header_${Date.now()}`, key: "Authorization", value: "Bearer YOUR_TOKEN" }],
|
||||
});
|
||||
}}
|
||||
>
|
||||
|
|
@ -273,8 +309,9 @@ export function ApiConfig({ dataSource, onChange, onTestResult }: ApiConfigProps
|
|||
variant="outline"
|
||||
size="sm"
|
||||
onClick={() => {
|
||||
const headers = normalizeHeaders();
|
||||
onChange({
|
||||
headers: { ...dataSource.headers, "Content-Type": "application/json" },
|
||||
headers: [...headers, { id: `header_${Date.now()}`, key: "Content-Type", value: "application/json" }],
|
||||
});
|
||||
}}
|
||||
>
|
||||
|
|
@ -282,32 +319,35 @@ export function ApiConfig({ dataSource, onChange, onTestResult }: ApiConfigProps
|
|||
</Button>
|
||||
</div>
|
||||
|
||||
{dataSource.headers && Object.keys(dataSource.headers).length > 0 ? (
|
||||
<div className="space-y-2">
|
||||
{Object.entries(dataSource.headers).map(([key, value]) => (
|
||||
<div key={key} className="flex gap-2">
|
||||
<Input
|
||||
placeholder="Header Name"
|
||||
value={key}
|
||||
onChange={(e) => updateHeader(key, e.target.value, value)}
|
||||
className="flex-1"
|
||||
/>
|
||||
<Input
|
||||
placeholder="Header Value"
|
||||
value={value}
|
||||
onChange={(e) => updateHeader(key, key, e.target.value)}
|
||||
className="flex-1"
|
||||
type={key.toLowerCase().includes("auth") ? "password" : "text"}
|
||||
/>
|
||||
<Button variant="ghost" size="icon" onClick={() => removeHeader(key)}>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="py-2 text-center text-sm text-gray-500">추가된 헤더가 없습니다</p>
|
||||
)}
|
||||
{(() => {
|
||||
const headers = normalizeHeaders();
|
||||
return headers.length > 0 ? (
|
||||
<div className="space-y-2">
|
||||
{headers.map((header) => (
|
||||
<div key={header.id} className="flex gap-2">
|
||||
<Input
|
||||
placeholder="Header Name"
|
||||
value={header.key}
|
||||
onChange={(e) => updateHeader(header.id, { key: e.target.value })}
|
||||
className="flex-1"
|
||||
/>
|
||||
<Input
|
||||
placeholder="Header Value"
|
||||
value={header.value}
|
||||
onChange={(e) => updateHeader(header.id, { value: e.target.value })}
|
||||
className="flex-1"
|
||||
type={header.key.toLowerCase().includes("auth") ? "password" : "text"}
|
||||
/>
|
||||
<Button variant="ghost" size="icon" onClick={() => removeHeader(header.id)}>
|
||||
<X className="h-4 w-4" />
|
||||
</Button>
|
||||
</div>
|
||||
))}
|
||||
</div>
|
||||
) : (
|
||||
<p className="py-2 text-center text-sm text-gray-500">추가된 헤더가 없습니다</p>
|
||||
);
|
||||
})()}
|
||||
</Card>
|
||||
|
||||
{/* JSON Path */}
|
||||
|
|
|
|||
|
|
@ -66,6 +66,13 @@ export interface ResizeHandle {
|
|||
cursor: string;
|
||||
}
|
||||
|
||||
// 키-값 쌍 인터페이스
|
||||
export interface KeyValuePair {
|
||||
id: string; // 고유 ID
|
||||
key: string; // 키
|
||||
value: string; // 값
|
||||
}
|
||||
|
||||
export interface ChartDataSource {
|
||||
type: "database" | "api"; // 데이터 소스 타입
|
||||
|
||||
|
|
@ -77,8 +84,8 @@ export interface ChartDataSource {
|
|||
// API 관련
|
||||
endpoint?: string; // API URL
|
||||
method?: "GET"; // HTTP 메서드 (GET만 지원)
|
||||
headers?: Record<string, string>; // 커스텀 헤더
|
||||
queryParams?: Record<string, string>; // URL 쿼리 파라미터
|
||||
headers?: KeyValuePair[]; // 커스텀 헤더 (배열)
|
||||
queryParams?: KeyValuePair[]; // URL 쿼리 파라미터 (배열)
|
||||
jsonPath?: string; // JSON 응답에서 데이터 추출 경로 (예: "data.results")
|
||||
|
||||
// 공통
|
||||
|
|
|
|||
Loading…
Reference in New Issue