fix: 테스트 위젯 최종 수정 및 충돌 해결

This commit is contained in:
leeheejin
2025-10-29 13:50:08 +09:00
parent 8edd5e4ca6
commit 398c47618b
7 changed files with 291 additions and 201 deletions

View File

@@ -39,12 +39,12 @@ export default function RiskAlertTestWidget({ element }: RiskAlertTestWidgetProp
const parseTextData = (text: string): any[] => {
// XML 형식 감지
if (text.trim().startsWith("<?xml") || text.trim().startsWith("<result>")) {
console.log("📄 XML 형식 데이터 감지");
// console.log("📄 XML 형식 데이터 감지");
return parseXmlData(text);
}
// CSV 형식 (기상청 특보)
console.log("📄 CSV 형식 데이터 감지");
// console.log("📄 CSV 형식 데이터 감지");
const lines = text.split("\n").filter((line) => {
const trimmed = line.trim();
return trimmed && !trimmed.startsWith("#") && trimmed !== "=";
@@ -98,7 +98,7 @@ export default function RiskAlertTestWidget({ element }: RiskAlertTestWidgetProp
results.push(obj);
}
console.log(`✅ XML 파싱 완료: ${results.length}개 레코드`);
// console.log(`✅ XML 파싱 완료: ${results.length}개 레코드`);
return results;
} catch (error) {
console.error("❌ XML 파싱 실패:", error);
@@ -107,14 +107,78 @@ export default function RiskAlertTestWidget({ element }: RiskAlertTestWidgetProp
};
const loadRestApiData = useCallback(async (source: ChartDataSource) => {
if (!source.endpoint) {
// 🆕 외부 연결 ID가 있으면 먼저 외부 연결 정보를 가져옴
let actualEndpoint = source.endpoint;
let actualQueryParams = source.queryParams;
let actualHeaders = source.headers;
if (source.externalConnectionId) {
// console.log("🔗 외부 연결 ID 감지:", source.externalConnectionId);
try {
const { ExternalDbConnectionAPI } = await import("@/lib/api/externalDbConnection");
const connection = await ExternalDbConnectionAPI.getApiConnectionById(Number(source.externalConnectionId));
if (connection) {
// console.log("✅ 외부 연결 정보 가져옴:", connection);
// 전체 엔드포인트 URL 생성
actualEndpoint = connection.endpoint_path
? `${connection.base_url}${connection.endpoint_path}`
: connection.base_url;
// console.log("📍 실제 엔드포인트:", actualEndpoint);
// 기본 헤더 적용
const headers: any[] = [];
if (connection.default_headers && Object.keys(connection.default_headers).length > 0) {
Object.entries(connection.default_headers).forEach(([key, value]) => {
headers.push({ key, value });
});
}
// 인증 정보 적용
const queryParams: any[] = [];
if (connection.auth_type && connection.auth_type !== "none" && connection.auth_config) {
const authConfig = connection.auth_config;
if (connection.auth_type === "api-key") {
if (authConfig.keyLocation === "header" && authConfig.keyName && authConfig.keyValue) {
headers.push({ key: authConfig.keyName, value: authConfig.keyValue });
// console.log("🔑 API Key 헤더 추가:", authConfig.keyName);
} else if (authConfig.keyLocation === "query" && authConfig.keyName && authConfig.keyValue) {
const actualKeyName = authConfig.keyName === "apiKey" ? "key" : authConfig.keyName;
queryParams.push({ key: actualKeyName, value: authConfig.keyValue });
// console.log("🔑 API Key 쿼리 파라미터 추가:", actualKeyName);
}
} else if (connection.auth_type === "bearer" && authConfig.token) {
headers.push({ key: "Authorization", value: `Bearer ${authConfig.token}` });
// console.log("🔑 Bearer Token 헤더 추가");
} else if (connection.auth_type === "basic" && authConfig.username && authConfig.password) {
const credentials = btoa(`${authConfig.username}:${authConfig.password}`);
headers.push({ key: "Authorization", value: `Basic ${credentials}` });
// console.log("🔑 Basic Auth 헤더 추가");
}
}
actualHeaders = headers;
actualQueryParams = queryParams;
// console.log("✅ 최종 헤더:", actualHeaders);
// console.log("✅ 최종 쿼리 파라미터:", actualQueryParams);
}
} catch (err) {
console.error("❌ 외부 연결 정보 가져오기 실패:", err);
}
}
if (!actualEndpoint) {
throw new Error("API endpoint가 없습니다.");
}
// 쿼리 파라미터 처리
const queryParamsObj: Record<string, string> = {};
if (source.queryParams && Array.isArray(source.queryParams)) {
source.queryParams.forEach((param) => {
if (actualQueryParams && Array.isArray(actualQueryParams)) {
actualQueryParams.forEach((param) => {
if (param.key && param.value) {
queryParamsObj[param.key] = param.value;
}
@@ -123,34 +187,33 @@ export default function RiskAlertTestWidget({ element }: RiskAlertTestWidgetProp
// 헤더 처리
const headersObj: Record<string, string> = {};
if (source.headers && Array.isArray(source.headers)) {
source.headers.forEach((header) => {
if (actualHeaders && Array.isArray(actualHeaders)) {
actualHeaders.forEach((header) => {
if (header.key && header.value) {
headersObj[header.key] = header.value;
}
});
}
console.log("🌐 API 호출 준비:", {
endpoint: source.endpoint,
queryParams: queryParamsObj,
headers: headersObj,
});
console.log("🔍 원본 source.queryParams:", source.queryParams);
console.log("🔍 원본 source.headers:", source.headers);
// console.log("🌐 API 호출 준비:", {
// endpoint: actualEndpoint,
// queryParams: queryParamsObj,
// headers: headersObj,
// });
const response = await fetch(getApiUrl("/api/dashboards/fetch-external-api"), {
method: "POST",
headers: { "Content-Type": "application/json" },
credentials: "include",
body: JSON.stringify({
url: source.endpoint,
url: actualEndpoint,
method: "GET",
headers: headersObj,
queryParams: queryParamsObj,
}),
});
console.log("🌐 API 응답 상태:", response.status);
// console.log("🌐 API 응답 상태:", response.status);
if (!response.ok) {
throw new Error(`HTTP ${response.status}`);
@@ -163,22 +226,22 @@ export default function RiskAlertTestWidget({ element }: RiskAlertTestWidgetProp
let apiData = result.data;
console.log("🔍 API 응답 데이터 타입:", typeof apiData);
console.log("🔍 API 응답 데이터 (처음 500자):", typeof apiData === "string" ? apiData.substring(0, 500) : JSON.stringify(apiData).substring(0, 500));
// console.log("🔍 API 응답 데이터 타입:", typeof apiData);
// console.log("🔍 API 응답 데이터 (처음 500자):", typeof apiData === "string" ? apiData.substring(0, 500) : JSON.stringify(apiData).substring(0, 500));
// 백엔드가 {text: "XML..."} 형태로 감싼 경우 처리
if (apiData && typeof apiData === "object" && apiData.text && typeof apiData.text === "string") {
console.log("📦 백엔드가 text 필드로 감싼 데이터 감지");
// console.log("📦 백엔드가 text 필드로 감싼 데이터 감지");
apiData = parseTextData(apiData.text);
console.log("✅ 파싱 성공:", apiData.length, "개 행");
// console.log("✅ 파싱 성공:", apiData.length, "개 행");
} else if (typeof apiData === "string") {
console.log("📄 텍스트 형식 데이터 감지, 파싱 시도");
// console.log("📄 텍스트 형식 데이터 감지, 파싱 시도");
apiData = parseTextData(apiData);
console.log("✅ 파싱 성공:", apiData.length, "개 행");
// console.log("✅ 파싱 성공:", apiData.length, "개 행");
} else if (Array.isArray(apiData)) {
console.log("✅ 이미 배열 형태의 데이터입니다.");
// console.log("✅ 이미 배열 형태의 데이터입니다.");
} else {
console.log("⚠️ 예상치 못한 데이터 형식입니다. 배열로 변환 시도.");
// console.log("⚠️ 예상치 못한 데이터 형식입니다. 배열로 변환 시도.");
apiData = [apiData];
}
@@ -220,7 +283,7 @@ export default function RiskAlertTestWidget({ element }: RiskAlertTestWidgetProp
}, []);
const convertToAlerts = useCallback((rows: any[], sourceName: string): Alert[] => {
console.log("🔄 convertToAlerts 호출:", rows.length, "개 행");
// console.log("🔄 convertToAlerts 호출:", rows.length, "개 행");
return rows.map((row: any, index: number) => {
// 타입 결정 (UTIC XML 기준)
@@ -325,7 +388,7 @@ export default function RiskAlertTestWidget({ element }: RiskAlertTestWidgetProp
source: sourceName,
};
console.log(` ✅ Alert ${index}:`, alert);
// console.log(` ✅ Alert ${index}:`, alert);
return alert;
});
}, []);
@@ -338,19 +401,19 @@ export default function RiskAlertTestWidget({ element }: RiskAlertTestWidgetProp
setLoading(true);
setError(null);
console.log("🔄 RiskAlertTestWidget 데이터 로딩 시작:", dataSources.length, "개 소스");
// console.log("🔄 RiskAlertTestWidget 데이터 로딩 시작:", dataSources.length, "개 소스");
try {
const results = await Promise.allSettled(
dataSources.map(async (source, index) => {
console.log(`📡 데이터 소스 ${index + 1} 로딩 중:`, source.name, source.type);
// console.log(`📡 데이터 소스 ${index + 1} 로딩 중:`, source.name, source.type);
if (source.type === "api") {
const alerts = await loadRestApiData(source);
console.log(`✅ 데이터 소스 ${index + 1} 완료:`, alerts.length, "개 알림");
// console.log(`✅ 데이터 소스 ${index + 1} 완료:`, alerts.length, "개 알림");
return alerts;
} else {
const alerts = await loadDatabaseData(source);
console.log(`✅ 데이터 소스 ${index + 1} 완료:`, alerts.length, "개 알림");
// console.log(`✅ 데이터 소스 ${index + 1} 완료:`, alerts.length, "개 알림");
return alerts;
}
})
@@ -359,14 +422,14 @@ export default function RiskAlertTestWidget({ element }: RiskAlertTestWidgetProp
const allAlerts: Alert[] = [];
results.forEach((result, index) => {
if (result.status === "fulfilled") {
console.log(`✅ 결과 ${index + 1} 병합:`, result.value.length, "개 알림");
// console.log(`✅ 결과 ${index + 1} 병합:`, result.value.length, "개 알림");
allAlerts.push(...result.value);
} else {
console.error(`❌ 결과 ${index + 1} 실패:`, result.reason);
}
});
console.log("✅ 총", allAlerts.length, "개 알림 로딩 완료");
// console.log("✅ 총", allAlerts.length, "개 알림 로딩 완료");
allAlerts.sort((a, b) => new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime());
setAlerts(allAlerts);
setLastRefreshTime(new Date());
@@ -380,7 +443,7 @@ export default function RiskAlertTestWidget({ element }: RiskAlertTestWidgetProp
// 수동 새로고침 핸들러
const handleManualRefresh = useCallback(() => {
console.log("🔄 수동 새로고침 버튼 클릭");
// console.log("🔄 수동 새로고침 버튼 클릭");
loadMultipleDataSources();
}, [loadMultipleDataSources]);
@@ -403,15 +466,15 @@ export default function RiskAlertTestWidget({ element }: RiskAlertTestWidgetProp
if (intervals.length === 0) return;
const minInterval = Math.min(...intervals);
console.log(`⏱️ 자동 새로고침 설정: ${minInterval}초마다`);
// console.log(`⏱️ 자동 새로고침 설정: ${minInterval}초마다`);
const intervalId = setInterval(() => {
console.log("🔄 자동 새로고침 실행");
// console.log("🔄 자동 새로고침 실행");
loadMultipleDataSources();
}, minInterval * 1000);
return () => {
console.log("⏹️ 자동 새로고침 정리");
// console.log("⏹️ 자동 새로고침 정리");
clearInterval(intervalId);
};
}, [dataSources, loadMultipleDataSources]);
@@ -516,7 +579,7 @@ export default function RiskAlertTestWidget({ element }: RiskAlertTestWidgetProp
</div>
{/* 컨텐츠 */}
<div className="flex-1 overflow-hidden p-2">
<div className="flex flex-1 flex-col overflow-hidden p-2">
<div className="mb-2 flex gap-1 overflow-x-auto">
<Button
variant={filter === "all" ? "default" : "outline"}
@@ -545,7 +608,7 @@ export default function RiskAlertTestWidget({ element }: RiskAlertTestWidgetProp
})}
</div>
<div className="flex-1 space-y-1.5 overflow-y-auto">
<div className="flex-1 space-y-1.5 overflow-y-auto pr-1">
{filteredAlerts.length === 0 ? (
<div className="flex h-full items-center justify-center text-gray-500">
<p className="text-sm"> </p>