Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 12 additions & 20 deletions src/api/utils/axios.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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) => {
Expand Down Expand Up @@ -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) => {
Expand Down
73 changes: 32 additions & 41 deletions src/components/PerformaceTest/SpecCard.jsx
Original file line number Diff line number Diff line change
Expand Up @@ -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 <div>데이터가 없습니다.</div>;
// 2. 데이터가 없거나, 필요한 summary, raw, inputs 객체가 없는 경우를 위한 방어 코드
if (!specData || !specData.summary || !specData.raw || !specData.inputs) {
return <div>스펙 데이터를 표시할 수 없습니다.</div>;
}

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 (
<div className={`performance-card ${isSuccess ? "success" : "failure"}`}>
Expand All @@ -30,52 +31,42 @@ export default function SpecCard({ method, url, specData }) {
{isSuccess ? <CheckCircle size={14} /> : <AlertCircle size={14} />}
<span className="status-text">{isSuccess ? "완료" : "실패"}</span>
</span>
{/* inputs 객체에서 method 값을 가져옵니다. */}
<MethodBadge type="method" value={method} />
</div>

<div className="badges-right">
<span className="badge type-badge-spec">
{/* 예: "SPEC" or "TRAFFIC" */}SPEC
</span>
<span className="badge type-badge-spec">SPEC</span>
</div>
</div>

<span className="request-header">
{/* inputs 객체에서 url 값을 가져옵니다. */}
API URL : <span className="request-url">{url}</span>
</span>

<div className="response-meta">
<div className="response-meta-row">
성공률: <strong>{successRatio}</strong>
{/* summary 객체에서 successRatioPct 값을 가져옵니다. */}
성공률: <strong>{successRatioPct.toFixed(2)}%</strong>
</div>
<div className="response-meta-row">
<div>
평균 응답 시간: <strong>{formatLatency(latencies.mean)}</strong>
</div>
<div>
지연 시간 P50: <strong>{formatLatency(latencies["50th"])}</strong>
</div>
<div>
지연 시간 P95: <strong>{formatLatency(latencies["95th"])}</strong>
</div>
<div>
지연 시간 Max: <strong>{formatLatency(latencies.max)}</strong>
</div>
{/* summary.latencyMs 객체에서 값을 가져와 표시합니다. */}
<div>평균 응답 시간: <strong>{formatLatencyMs(latencyMs.mean)}</strong></div>
<div>지연 시간 P50: <strong>{formatLatencyMs(latencyMs.p50)}</strong></div>
<div>지연 시간 P95: <strong>{formatLatencyMs(latencyMs.p95)}</strong></div>
<div>지연 시간 Max: <strong>{formatLatencyMs(latencyMs.max)}</strong></div>
</div>
<div className="response-meta-row">
<div>
처리량(RPS):{" "}
<strong>
{typeof throughput === "number" ? throughput.toFixed(2) : "N/A"}
</strong>
{/* summary 객체에서 rpsMean 값을 가져옵니다. */}
처리량(RPS): <strong>{typeof rpsMean === "number" ? rpsMean.toFixed(2) : "N/A"}</strong>
</div>
<div>
상태코드:{" "}
{Object.keys(statusCodes).length > 0 ? (
Object.entries(statusCodes).map(([code, count]) => (
<span key={code}>
{code}({count}){" "}
</span>
{/* summary.byStatus 객체를 사용합니다. */}
{Object.keys(byStatus).length > 0 ? (
Object.entries(byStatus).map(([code, count]) => (
<span key={code}>{code}({count}) </span>
))
) : (
<span>N/A</span>
Expand All @@ -85,4 +76,4 @@ export default function SpecCard({ method, url, specData }) {
</div>
</div>
);
}
}
62 changes: 39 additions & 23 deletions src/components/PerformaceTest/TrafficCard.jsx
Original file line number Diff line number Diff line change
@@ -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 <div>트래픽 데이터를 표시할 수 없습니다.</div>;
}

// 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 (
<div className="performance-card">
<div className={`performance-card ${isSuccess ? "success" : "failure"}`}>
<div className="card-header">
<div className="badges-left">
<span className={`badge status ${isSuccess ? "success" : "failure"}`}>
{isSuccess ? <CheckCircle size={14} /> : <AlertCircle size={14} />}
<span className="status-text">{isSuccess ? "완료" : "실패"}</span>
</span>
{/* 3. 이제 method 값을 정상적으로 사용할 수 있습니다. */}
<MethodBadge type="method" value={method} />
</div>
<div className="badges-right">
<span className="badge type-badge-traffic">
{/* 예: "SPEC" or "TRAFFIC" */}Traffic
</span>
<span className="badge type-badge-traffic">TRAFFIC</span>
</div>
</div>

<span className="request-header">
{/* 4. url 값도 정상적으로 사용할 수 있습니다. */}
API URL : <span className="request-url">{url}</span>
</span>

<div className="response-meta">
<div className="response-meta-row">
총 요청 수: <strong>{requests}</strong>
<div>총 요청 수: <strong>{requests}</strong></div>
</div>
<div className="response-meta-row">
<div>
평균 수신 바이트 : <strong>{bytes?.in?.mean ?? 0}B</strong>
</div>
<div>
총 수신 바이트 : <strong>{bytes?.in?.total ?? 0}B</strong>
</div>
<div>총 수신량: <strong>{formatBytes(bytes.in.total)}</strong></div>
<div>평균 수신량: <strong>{formatBytes(bytes.in.mean)}</strong></div>
</div>

<div className="response-meta-row">
<div>
평균 송신 바이트 : <strong>{bytes?.out?.mean ?? 0}B</strong>
</div>
<div>
총 송신 바이트 : <strong>{bytes?.out?.total ?? 0}B</strong>
</div>
<div>총 송신량: <strong>{formatBytes(bytes.out.total)}</strong></div>
<div>평균 송신량: <strong>{formatBytes(bytes.out.mean)}</strong></div>
</div>
</div>
</div>
);
}
}
Loading