From e9dcaf78848bafed84f7f0a8298cfbf16808fdc7 Mon Sep 17 00:00:00 2001 From: cyyoon0311 Date: Wed, 20 Aug 2025 21:19:22 +0900 Subject: [PATCH] =?UTF-8?q?feat/#53=20=EC=B1=97=EB=B4=87->=EC=84=B1?= =?UTF-8?q?=EB=8A=A5=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9D=B4=EB=8F=99=20?= =?UTF-8?q?=EB=B2=84=ED=8A=BC=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/api/utils/axios.jsx | 32 +++---- src/components/PerformaceTest/SpecCard.jsx | 73 +++++++--------- src/components/PerformaceTest/TrafficCard.jsx | 62 ++++++++----- src/pages/Dashboard.jsx | 87 +++++++++++++------ src/pages/Home.jsx | 2 + src/pages/PerformanceTest.jsx | 9 +- 6 files changed, 148 insertions(+), 117 deletions(-) diff --git a/src/api/utils/axios.jsx b/src/api/utils/axios.jsx index fa7b02a..97d41e7 100644 --- a/src/api/utils/axios.jsx +++ b/src/api/utils/axios.jsx @@ -2,28 +2,24 @@ import axios from "axios"; const client = axios.create({ baseURL: import.meta.env.VITE_API_URL, - timeout: 10000, + timeout: 20000, }); const clientAI = axios.create({ baseURL: import.meta.env.VITE_AI_API_URL, - timeout: 10000, + timeout: 50000, }); // 요청 인터셉터: 모든 API 요청이 서버로 전송되기 전에 특정 작업을 수행합니다. client.interceptors.request.use( (config) => { // 로컬 스토리지에서 액세스 토큰을 가져옵니다. - // const accessToken = localStorage.getItem('accessToken'); - - // // 토큰이 존재하면, 요청 헤더에 'Authorization' 헤더를 추가합니다. - // if (accessToken) { - // config.headers.Authorization = `Bearer ${accessToken}`; - // } - - config.headers.Authorization = - "Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI0ZjlhMTIzZC02Yzk4LTQ3MWUtODUzNi0wNDA0YWI5ODhmOTUiLCJsb2dpbklkIjoic29ubnkiLCJwYXNzd29yZCI6IntiY3J5cHR9JDJhJDEwJEhoSEJTS1c4LkUzOFplUW9YaVp1Li5OYTJuWDhra1NaS3dueVV1QnRWTmhOaHpobk1BS3RtIiwiYXV0aCI6IlJPTEVfVVNFUiIsImV4cCI6MTc1Nzc3NzgyNX0.3C7ekn4qdGrxTfQjL1FvNH6AWL_vjbSnNHbid13LsII"; + const accessToken = localStorage.getItem('accessToken'); + // 토큰이 존재하면, 요청 헤더에 'Authorization' 헤더를 추가합니다. + if (accessToken) { + config.headers.Authorization = `Bearer ${accessToken}`; + } return config; }, (error) => { @@ -57,16 +53,12 @@ client.interceptors.response.use( clientAI.interceptors.request.use( (config) => { // 로컬 스토리지에서 액세스 토큰을 가져옵니다. - // const accessToken = localStorage.getItem('accessToken'); - - // // 토큰이 존재하면, 요청 헤더에 'Authorization' 헤더를 추가합니다. - // if (accessToken) { - // config.headers.Authorization = `Bearer ${accessToken}`; - // } - - config.headers.Authorization = - "Bearer eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiI0ZjlhMTIzZC02Yzk4LTQ3MWUtODUzNi0wNDA0YWI5ODhmOTUiLCJsb2dpbklkIjoic29ubnkiLCJwYXNzd29yZCI6IntiY3J5cHR9JDJhJDEwJEhoSEJTS1c4LkUzOFplUW9YaVp1Li5OYTJuWDhra1NaS3dueVV1QnRWTmhOaHpobk1BS3RtIiwiYXV0aCI6IlJPTEVfVVNFUiIsImV4cCI6MTc1Nzc3NzgyNX0.3C7ekn4qdGrxTfQjL1FvNH6AWL_vjbSnNHbid13LsII"; + const accessToken = localStorage.getItem('accessToken'); + // 토큰이 존재하면, 요청 헤더에 'Authorization' 헤더를 추가합니다. + if (accessToken) { + config.headers.Authorization = `Bearer ${accessToken}`; + } return config; }, (error) => { diff --git a/src/components/PerformaceTest/SpecCard.jsx b/src/components/PerformaceTest/SpecCard.jsx index fbaf940..042d921 100644 --- a/src/components/PerformaceTest/SpecCard.jsx +++ b/src/components/PerformaceTest/SpecCard.jsx @@ -3,24 +3,25 @@ import MethodBadge from "../common/MethodBadge"; import { CheckCircle, AlertCircle } from "lucide-react"; import "../../styles/css/PerformanceTest.css"; -export default function SpecCard({ method, url, specData }) { - console.log("specData:", method, url, specData); // ✅ 여기! +// 1. 이제 props로 받는 specData는 API 응답의 results 배열에 있는 객체 하나입니다. +export default function SpecCard({ specData }) { - if (!specData) return
데이터가 없습니다.
; + // 2. 데이터가 없거나, 필요한 summary, raw, inputs 객체가 없는 경우를 위한 방어 코드 + if (!specData || !specData.summary || !specData.raw || !specData.inputs) { + return
스펙 데이터를 표시할 수 없습니다.
; + } - const { - latencies = {}, - throughput = 0, - successRatio = "0%", - statusCodes = {}, - } = specData; + // 3. API 응답 구조에 맞게 필요한 값들을 올바른 위치에서 추출합니다. + const { inputs, summary, raw } = specData; + const { method, url } = inputs; + const { successRatioPct, latencyMs, rpsMean, byStatus } = summary; - const isSuccess = - successRatio !== "0%" && successRatio !== "0.00%" && successRatio !== 0; + // 성공률(successRatioPct)을 기반으로 성공/실패 여부를 판단합니다. + const isSuccess = successRatioPct === 100.0; - // 안전하게 접근 - const formatLatency = (value) => - typeof value === "number" ? `${(value / 1e6).toFixed(2)}ms` : "N/A"; + // 이미 밀리초(ms)인 값을 포맷하는 헬퍼 함수 + const formatLatencyMs = (value) => + typeof value === "number" ? `${value.toFixed(2)} ms` : "N/A"; return (
@@ -30,52 +31,42 @@ export default function SpecCard({ method, url, specData }) { {isSuccess ? : } {isSuccess ? "완료" : "실패"} + {/* inputs 객체에서 method 값을 가져옵니다. */}
-
- - {/* 예: "SPEC" or "TRAFFIC" */}SPEC - + SPEC
+ {/* inputs 객체에서 url 값을 가져옵니다. */} API URL : {url}
- 성공률: {successRatio} + {/* summary 객체에서 successRatioPct 값을 가져옵니다. */} + 성공률: {successRatioPct.toFixed(2)}%
-
- 평균 응답 시간: {formatLatency(latencies.mean)} -
-
- 지연 시간 P50: {formatLatency(latencies["50th"])} -
-
- 지연 시간 P95: {formatLatency(latencies["95th"])} -
-
- 지연 시간 Max: {formatLatency(latencies.max)} -
+ {/* summary.latencyMs 객체에서 값을 가져와 표시합니다. */} +
평균 응답 시간: {formatLatencyMs(latencyMs.mean)}
+
지연 시간 P50: {formatLatencyMs(latencyMs.p50)}
+
지연 시간 P95: {formatLatencyMs(latencyMs.p95)}
+
지연 시간 Max: {formatLatencyMs(latencyMs.max)}
- 처리량(RPS):{" "} - - {typeof throughput === "number" ? throughput.toFixed(2) : "N/A"} - + {/* summary 객체에서 rpsMean 값을 가져옵니다. */} + 처리량(RPS): {typeof rpsMean === "number" ? rpsMean.toFixed(2) : "N/A"}
상태코드:{" "} - {Object.keys(statusCodes).length > 0 ? ( - Object.entries(statusCodes).map(([code, count]) => ( - - {code}({count}){" "} - + {/* summary.byStatus 객체를 사용합니다. */} + {Object.keys(byStatus).length > 0 ? ( + Object.entries(byStatus).map(([code, count]) => ( + {code}({count}) )) ) : ( N/A @@ -85,4 +76,4 @@ export default function SpecCard({ method, url, specData }) {
); -} +} \ No newline at end of file diff --git a/src/components/PerformaceTest/TrafficCard.jsx b/src/components/PerformaceTest/TrafficCard.jsx index 97d2d95..48efcbd 100644 --- a/src/components/PerformaceTest/TrafficCard.jsx +++ b/src/components/PerformaceTest/TrafficCard.jsx @@ -1,49 +1,65 @@ import React from "react"; import MethodBadge from "../common/MethodBadge"; -import { Upload, Download, Network } from "lucide-react"; - +import { CheckCircle, AlertCircle } from "lucide-react"; import "../../styles/css/PerformanceTest.css"; -export default function TrafficCard({ method, url, requests, bytes }) { - console.log("trafficData:", method, url, requests, bytes); // ✅ 여기! +export default function TrafficCard({ trafficData }) { + console.log("TrafficCard received trafficData:", trafficData); + + // 데이터 유효성 검사 + if (!trafficData || !trafficData.summary || !trafficData.inputs) { + return
트래픽 데이터를 표시할 수 없습니다.
; + } + + // 1. 전달받은 trafficData 객체에서 inputs와 summary를 꺼냅니다. + const { inputs, summary } = trafficData; + // 2. 각각의 객체에서 필요한 값을 최종적으로 추출합니다. + const { method, url } = inputs; + const { requests, bytes } = summary; + + const isSuccess = requests > 0; + const formatBytes = (value) => { + if (typeof value !== 'number' || value === 0) return "0 B"; + const k = 1024; + const sizes = ['B', 'KB', 'MB', 'GB']; + const i = Math.floor(Math.log(value) / Math.log(k)); + return parseFloat((value / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i]; + }; return ( -
+
+ + {isSuccess ? : } + {isSuccess ? "완료" : "실패"} + + {/* 3. 이제 method 값을 정상적으로 사용할 수 있습니다. */}
- - {/* 예: "SPEC" or "TRAFFIC" */}Traffic - + TRAFFIC
+ + {/* 4. url 값도 정상적으로 사용할 수 있습니다. */} API URL : {url} +
- 총 요청 수: {requests} +
총 요청 수: {requests}
-
- 평균 수신 바이트 : {bytes?.in?.mean ?? 0}B -
-
- 총 수신 바이트 : {bytes?.in?.total ?? 0}B -
+
총 수신량: {formatBytes(bytes.in.total)}
+
평균 수신량: {formatBytes(bytes.in.mean)}
-
-
- 평균 송신 바이트 : {bytes?.out?.mean ?? 0}B -
-
- 총 송신 바이트 : {bytes?.out?.total ?? 0}B -
+
총 송신량: {formatBytes(bytes.out.total)}
+
평균 송신량: {formatBytes(bytes.out.mean)}
); -} +} \ No newline at end of file diff --git a/src/pages/Dashboard.jsx b/src/pages/Dashboard.jsx index 080ce12..79d2f3f 100644 --- a/src/pages/Dashboard.jsx +++ b/src/pages/Dashboard.jsx @@ -42,8 +42,8 @@ const Dashboard = ({ setActiveTab, setSpecData, setTrafficData }) => { }, }; - setSpecData(specData); - setTrafficData(trafficData); + setSpecData(console.log("성능 또 다시 보여줘야돼", specData,), specData); + setTrafficData(console.log("트래픽 또 다시 보여줘야돼", trafficData,), trafficData); setActiveTab("성능 테스트"); }; @@ -86,7 +86,7 @@ const Dashboard = ({ setActiveTab, setSpecData, setTrafficData }) => { // }); const [messages, setMessages] = useState([ - { role: "bot", text: "안녕하세요! 무엇을 도와드릴까요?" }, + { role: "bot", type: "text", content: "안녕하세요! 무엇을 도와드릴까요?" }, ]); const [input, setInput] = useState(""); const messagesEndRef = useRef(null); @@ -104,6 +104,20 @@ const Dashboard = ({ setActiveTab, setSpecData, setTrafficData }) => { scrollToBottom(); }, [messages]); + const handleNavigateToTest = (data) => { + + console.log('--- handleNavigateToTest ---'); + console.log('SpecData로 설정될 데이터:', data.specData); + console.log('TrafficData로 설정될 데이터:', data.trafficData); + console.log('-----------------------------'); + // specData가 있으면 상태를 업데이트하고, 없으면 null로 설정합니다. + setSpecData(data.specData || null); + // trafficData가 있으면 상태를 업데이트하고, 없으면 null로 설정합니다. + setTrafficData(data.trafficData || null); + + setActiveTab("성능 테스트"); + }; + const handleSend = (e) => { e.preventDefault(); if (!input.trim() || isPending) return; // 로딩 중일 때는 전송 방지 @@ -111,7 +125,8 @@ const Dashboard = ({ setActiveTab, setSpecData, setTrafficData }) => { const now = new Date(); const userMessage = { role: "user", - text: input, + type: "text", + content: input, timestamp: now.toLocaleTimeString([], { hour: "2-digit", minute: "2-digit", @@ -122,21 +137,39 @@ const Dashboard = ({ setActiveTab, setSpecData, setTrafficData }) => { const userInput = input; setInput(""); + + // 3. mutate 함수를 호출하여 API 요청을 보냅니다. postPrompt( - { - team_code: teamCode, // 팀 코드 전달", - prompt: userInput, // 사용자 입력 전달 - }, + { team_code: teamCode, prompt: userInput }, { onSuccess: (response) => { - // 4. API 요청 성공 시, 받은 응답으로 봇 메시지를 추가합니다. - // 서버 응답이 { reply: "..." } 형태라고 가정합니다. - const botResponse = { - role: "bot", - text: response.output, - }; - setMessages((prev) => [...prev, botResponse]); + const resultsArray = response.output.results || (response.output ? [response.output] : []); + + // 2. spec 또는 traffic 결과를 찾습니다. 둘 중 하나만 있을 수도 있습니다. + const specResult = resultsArray.find(r => r.testType === 'spec'); + const trafficResult = resultsArray.find(r => r.testType === 'traffic'); + + // 3. 둘 중 하나라도 결과가 있다면 버튼 메시지를 생성합니다. + if (specResult || trafficResult) { + const botResponse = { + role: 'bot', + type: 'performance_test_button', + content: { + specData: specResult, + trafficData: trafficResult + } + }; + setMessages(prev => [...prev, botResponse]); + } else { + // 둘 다 없다면 일반 텍스트 메시지로 처리 + const botResponse = { + role: 'bot', + type: 'text', + content: response.output || "결과를 이해할 수 없습니다." + }; + setMessages(prev => [...prev, botResponse]); + } }, onError: (error) => { // 5. API 요청 실패 시, 에러 메시지를 표시합니다. @@ -157,7 +190,18 @@ const Dashboard = ({ setActiveTab, setSpecData, setTrafficData }) => {
{messages.map((msg, i) => (
-
{msg.text}
+
+ {msg.type === 'text' ? ( + msg.content + ) : msg.type === 'performance_test_button' ? ( + + ) : null} +
{msg.timestamp}
))} @@ -169,17 +213,6 @@ const Dashboard = ({ setActiveTab, setSpecData, setTrafficData }) => {
- {/* 임시 버튼 */} -
- -
-
{ const { registerFront, registerBackend, registerDB } = useServerActions(); const [specData, setSpecData] = useState(null); // 성능 데이터 저장 const [trafficData, setTrafficData] = useState(null); + console.log('부모 컴포넌트의 현재 trafficData 상태:', trafficData); + const handleAddServer = async (newServer) => { try { diff --git a/src/pages/PerformanceTest.jsx b/src/pages/PerformanceTest.jsx index 9c19954..2f6dfa2 100644 --- a/src/pages/PerformanceTest.jsx +++ b/src/pages/PerformanceTest.jsx @@ -43,7 +43,7 @@ const PerformanceTest = ({ specData, trafficData }) => { // }, // }); - if (!specData || !trafficData) return
데이터가 없습니다.
; + //if (!specData || !trafficData) return
데이터가 없습니다.
; return (
@@ -52,11 +52,8 @@ const PerformanceTest = ({ specData, trafficData }) => {
- {/*

성능 지표 (Spec)

*/} - - - {/*

트래픽 지표 (Traffic)

*/} - + +
);