trafficmonitor init...1
This commit is contained in:
parent
3a79048491
commit
a1d7c644ec
@ -46,24 +46,20 @@
|
|||||||
<div id="app" class="container-fluid" style="max-width: 1200px;">
|
<div id="app" class="container-fluid" style="max-width: 1200px;">
|
||||||
<header class="text-center mb-5">
|
<header class="text-center mb-5">
|
||||||
<h1 class="display-5 fw-bold text-dark mb-2"><?= $title ?? '트래픽 데이터 시각화 대시보드' ?></h1>
|
<h1 class="display-5 fw-bold text-dark mb-2"><?= $title ?? '트래픽 데이터 시각화 대시보드' ?></h1>
|
||||||
<!-- 인트로 문구 제거됨 -->
|
|
||||||
</header>
|
</header>
|
||||||
<!-- Configuration Card -->
|
<!-- Configuration Card -->
|
||||||
<div class="card shadow-lg mb-5 dashboard-card">
|
<div class="card shadow-lg mb-5 dashboard-card">
|
||||||
<div class="card-body p-4 p-md-5">
|
<div class="card-body p-4 p-md-5">
|
||||||
<!-- PHP 변수 사용: <?= $viewDatas['entity']->getCustomTitle() ?> -->
|
|
||||||
<h2 class="h5 card-title text-dark mb-4"><?= $viewDatas['entity']->getCustomTitle() ?>데이터 조회 설정</h2>
|
<h2 class="h5 card-title text-dark mb-4"><?= $viewDatas['entity']->getCustomTitle() ?>데이터 조회 설정</h2>
|
||||||
<div class="row g-3 align-items-end">
|
<div class="row g-3 align-items-end">
|
||||||
<!-- 1. Start Date Picker -->
|
<!-- 1. Start Date Picker -->
|
||||||
<div class="col-12 col-md-3">
|
<div class="col-12 col-md-3">
|
||||||
<label for="startDate" class="form-label text-muted">시작 날짜</label>
|
<label for="startDate" class="form-label text-muted">시작 날짜</label>
|
||||||
<!-- 기본값을 오늘 날짜로 설정 -->
|
|
||||||
<input type="date" id="startDate" class="form-control" value="<?= date('Y-m-d') ?>">
|
<input type="date" id="startDate" class="form-control" value="<?= date('Y-m-d') ?>">
|
||||||
</div>
|
</div>
|
||||||
<!-- 2. End Date Picker -->
|
<!-- 2. End Date Picker -->
|
||||||
<div class="col-12 col-md-3">
|
<div class="col-12 col-md-3">
|
||||||
<label for="endDate" class="form-label text-muted">종료 날짜</label>
|
<label for="endDate" class="form-label text-muted">종료 날짜</label>
|
||||||
<!-- 기본값을 오늘 날짜로 설정 -->
|
|
||||||
<input type="date" id="endDate" class="form-control" value="<?= date('Y-m-d') ?>">
|
<input type="date" id="endDate" class="form-control" value="<?= date('Y-m-d') ?>">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
@ -81,6 +77,7 @@
|
|||||||
<!-- 동적으로 업데이트될 날짜 정보 영역 (시작일, 종료일) -->
|
<!-- 동적으로 업데이트될 날짜 정보 영역 (시작일, 종료일) -->
|
||||||
<p id="dateRangeInfo" class="text-secondary mb-4"></p>
|
<p id="dateRangeInfo" class="text-secondary mb-4"></p>
|
||||||
|
|
||||||
|
<!-- 로딩 인디케이터 -->
|
||||||
<div id="loadingIndicator" class="text-center p-5 hidden" style="display: none;">
|
<div id="loadingIndicator" class="text-center p-5 hidden" style="display: none;">
|
||||||
<div class="spinner-border text-primary" role="status">
|
<div class="spinner-border text-primary" role="status">
|
||||||
<span class="visually-hidden">Loading...</span>
|
<span class="visually-hidden">Loading...</span>
|
||||||
@ -88,10 +85,16 @@
|
|||||||
<p class="mt-3 text-primary fw-bold">데이터를 불러오는 중입니다... 잠시만 기다려 주세요.</p>
|
<p class="mt-3 text-primary fw-bold">데이터를 불러오는 중입니다... 잠시만 기다려 주세요.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 오류 메시지 영역 -->
|
||||||
<div id="errorMessage" class="alert alert-danger text-center hidden" role="alert" style="display: none;">
|
<div id="errorMessage" class="alert alert-danger text-center hidden" role="alert" style="display: none;">
|
||||||
데이터를 불러오는데 실패했습니다. (서버 오류)
|
데이터를 불러오는데 실패했습니다. (서버 오류)
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- 데이터 없음 메시지 영역 -->
|
||||||
|
<div id="noDataMessage" class="alert alert-warning text-center hidden" role="alert" style="display: none;">
|
||||||
|
선택하신 기간에는 조회된 트래픽 데이터가 없습니다. 기간을 다시 설정해주세요.
|
||||||
|
</div>
|
||||||
|
|
||||||
<div class="chart-container">
|
<div class="chart-container">
|
||||||
<canvas id="trafficChart"></canvas>
|
<canvas id="trafficChart"></canvas>
|
||||||
</div>
|
</div>
|
||||||
@ -110,20 +113,17 @@
|
|||||||
});
|
});
|
||||||
|
|
||||||
// -----------------------------------------------------------
|
// -----------------------------------------------------------
|
||||||
// 1. CI4 백엔드 엔드포인트를 호출하여 데이터 가져오기 (AJAX) - GET 방식으로 변경됨
|
// 1. CI4 백엔드 엔드포인트를 호출하여 데이터 가져오기 (AJAX)
|
||||||
// -----------------------------------------------------------
|
// -----------------------------------------------------------
|
||||||
|
|
||||||
async function fetchAggregatedData(startDate, endDate) {
|
async function fetchAggregatedData(startDate, endDate) {
|
||||||
// PHP 변수를 사용하여 엔드포인트 URL을 구성합니다.
|
|
||||||
const baseEndpoint = '<?= base_url("/admin/traffic/data/{$viewDatas['entity']->getPK()}") ?>';
|
const baseEndpoint = '<?= base_url("/admin/traffic/data/{$viewDatas['entity']->getPK()}") ?>';
|
||||||
|
|
||||||
// GET 요청을 위해 쿼리 파라미터를 생성합니다.
|
|
||||||
const params = new URLSearchParams({
|
const params = new URLSearchParams({
|
||||||
startDate: startDate,
|
startDate: startDate,
|
||||||
endDate: endDate
|
endDate: endDate
|
||||||
});
|
});
|
||||||
|
|
||||||
// 최종 엔드포인트 URL: /admin/traffic/data/{PK}?startDate=...&endDate=...
|
|
||||||
const endpoint = `${baseEndpoint}?${params.toString()}`;
|
const endpoint = `${baseEndpoint}?${params.toString()}`;
|
||||||
|
|
||||||
const maxRetries = 3;
|
const maxRetries = 3;
|
||||||
@ -132,7 +132,7 @@
|
|||||||
for (let i = 0; i < maxRetries; i++) {
|
for (let i = 0; i < maxRetries; i++) {
|
||||||
try {
|
try {
|
||||||
const response = await fetch(endpoint, {
|
const response = await fetch(endpoint, {
|
||||||
method: 'GET', // GET 방식으로 유지
|
method: 'GET',
|
||||||
});
|
});
|
||||||
|
|
||||||
const result = await response.json();
|
const result = await response.json();
|
||||||
@ -141,7 +141,14 @@
|
|||||||
throw new Error(result.message || `HTTP error! Status: ${response.status}`);
|
throw new Error(result.message || `HTTP error! Status: ${response.status}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return result.data; // Model에서 반환된 데이터 배열
|
// **********************************************
|
||||||
|
// ** 중요: 데이터가 비어있는 경우를 여기서 감지 **
|
||||||
|
// **********************************************
|
||||||
|
if (!result.data || result.data.length === 0) {
|
||||||
|
return []; // 빈 배열 반환하여 데이터 없음 처리
|
||||||
|
}
|
||||||
|
|
||||||
|
return result.data;
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`Attempt ${i + 1} failed:`, error);
|
console.error(`Attempt ${i + 1} failed:`, error);
|
||||||
@ -149,7 +156,7 @@
|
|||||||
throw new Error("서버에서 데이터를 가져오는데 실패했습니다. 콘솔을 확인하세요.");
|
throw new Error("서버에서 데이터를 가져오는데 실패했습니다. 콘솔을 확인하세요.");
|
||||||
}
|
}
|
||||||
await new Promise(resolve => setTimeout(resolve, delay));
|
await new Promise(resolve => setTimeout(resolve, delay));
|
||||||
delay *= 2; // 지수 백오프
|
delay *= 2;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -167,31 +174,39 @@
|
|||||||
const dateRangeInfoElement = document.getElementById('dateRangeInfo');
|
const dateRangeInfoElement = document.getElementById('dateRangeInfo');
|
||||||
const loadingIndicator = document.getElementById('loadingIndicator');
|
const loadingIndicator = document.getElementById('loadingIndicator');
|
||||||
const errorMessage = document.getElementById('errorMessage');
|
const errorMessage = document.getElementById('errorMessage');
|
||||||
|
const noDataMessage = document.getElementById('noDataMessage');
|
||||||
const canvas = document.getElementById('trafficChart');
|
const canvas = document.getElementById('trafficChart');
|
||||||
const viewChartBtn = document.getElementById('viewChartBtn');
|
const viewChartBtn = document.getElementById('viewChartBtn');
|
||||||
|
|
||||||
// UI 초기화 및 로딩 표시
|
// UI 초기화 및 로딩 표시
|
||||||
loadingIndicator.style.display = 'block';
|
loadingIndicator.style.display = 'block';
|
||||||
errorMessage.style.display = 'none';
|
errorMessage.style.display = 'none';
|
||||||
|
noDataMessage.style.display = 'none';
|
||||||
canvas.style.opacity = '0';
|
canvas.style.opacity = '0';
|
||||||
viewChartBtn.disabled = true;
|
viewChartBtn.disabled = true;
|
||||||
|
|
||||||
// 메인 제목: Kbit/s 정보만
|
// 제목 및 기간 정보 업데이트
|
||||||
chartTitleElement.textContent = `트래픽 속도 추이 (IN/OUT Kbit/s)`;
|
chartTitleElement.textContent = `트래픽 속도 추이 (IN/OUT Kbit/s)`;
|
||||||
|
|
||||||
// 시작일, 마지막일 정보 표시
|
|
||||||
dateRangeInfoElement.textContent = `기간: ${startDate} 부터 ${endDate} 까지`;
|
dateRangeInfoElement.textContent = `기간: ${startDate} 부터 ${endDate} 까지`;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// CI4 백엔드(Controller -> Model)에서 데이터 가져오기
|
|
||||||
const aggregatedData = await fetchAggregatedData(startDate, endDate);
|
const aggregatedData = await fetchAggregatedData(startDate, endDate);
|
||||||
|
|
||||||
// --- IN, OUT Kbit/s 데이터만 추출 ---
|
if (aggregatedData.length === 0) {
|
||||||
// Model은 'created_at', 'in_kbits', 'out_kbits'를 반환해야 합니다.
|
// 데이터가 비어 있을 경우
|
||||||
|
noDataMessage.style.display = 'block';
|
||||||
|
if (trafficChart) {
|
||||||
|
trafficChart.destroy(); // 기존 차트 파괴
|
||||||
|
trafficChart = null;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 데이터 추출
|
||||||
const labels = aggregatedData.map(d => d.created_at);
|
const labels = aggregatedData.map(d => d.created_at);
|
||||||
const inData = aggregatedData.map(d => d.in_kbits);
|
// 데이터 타입을 Number로 명시적 변환하여 Chart.js가 숫자로 인식하도록 보장
|
||||||
const outData = aggregatedData.map(d => d.out_kbits);
|
const inData = aggregatedData.map(d => Number(d.in_kbits));
|
||||||
// ----------------------------------
|
const outData = aggregatedData.map(d => Number(d.out_kbits));
|
||||||
|
|
||||||
drawChart(labels, inData, outData);
|
drawChart(labels, inData, outData);
|
||||||
|
|
||||||
@ -202,7 +217,8 @@
|
|||||||
|
|
||||||
} finally {
|
} finally {
|
||||||
loadingIndicator.style.display = 'none';
|
loadingIndicator.style.display = 'none';
|
||||||
canvas.style.opacity = '1';
|
// 차트가 성공적으로 그려졌거나 '데이터 없음' 메시지가 표시된 경우에만 캔버스 영역 표시
|
||||||
|
canvas.style.opacity = (trafficChart || noDataMessage.style.display === 'block') ? '1' : '0';
|
||||||
viewChartBtn.disabled = false;
|
viewChartBtn.disabled = false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -215,79 +231,85 @@
|
|||||||
trafficChart.destroy();
|
trafficChart.destroy();
|
||||||
}
|
}
|
||||||
|
|
||||||
trafficChart = new Chart(ctx, {
|
try {
|
||||||
type: 'line',
|
trafficChart = new Chart(ctx, {
|
||||||
data: {
|
type: 'line',
|
||||||
labels: labels,
|
data: {
|
||||||
datasets: [{
|
labels: labels,
|
||||||
label: 'IN Kbit/s',
|
datasets: [{
|
||||||
data: inData,
|
label: 'IN Kbit/s',
|
||||||
borderColor: '#198754', // Bootstrap Success Color (Green)
|
data: inData,
|
||||||
backgroundColor: 'rgba(25, 135, 84, 0.1)',
|
borderColor: '#198754', // Bootstrap Success Color (Green)
|
||||||
fill: true,
|
backgroundColor: 'rgba(25, 135, 84, 0.1)',
|
||||||
tension: 0.3,
|
fill: true,
|
||||||
pointRadius: 3,
|
tension: 0.3,
|
||||||
pointHoverRadius: 6,
|
pointRadius: 3,
|
||||||
},
|
pointHoverRadius: 6,
|
||||||
{
|
},
|
||||||
label: 'OUT Kbit/s',
|
{
|
||||||
data: outData,
|
label: 'OUT Kbit/s',
|
||||||
borderColor: '#dc3545', // Bootstrap Danger Color (Red)
|
data: outData,
|
||||||
backgroundColor: 'rgba(220, 53, 69, 0.1)',
|
borderColor: '#dc3545', // Bootstrap Danger Color (Red)
|
||||||
fill: true,
|
backgroundColor: 'rgba(220, 53, 69, 0.1)',
|
||||||
tension: 0.3,
|
fill: true,
|
||||||
pointRadius: 3,
|
tension: 0.3,
|
||||||
pointHoverRadius: 6,
|
pointRadius: 3,
|
||||||
}
|
pointHoverRadius: 6,
|
||||||
]
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
responsive: true,
|
|
||||||
maintainAspectRatio: false,
|
|
||||||
scales: {
|
|
||||||
y: {
|
|
||||||
beginAtZero: true,
|
|
||||||
title: {
|
|
||||||
display: true,
|
|
||||||
text: 'Kbit/s', // Y축 레이블에 Kbit/s 명시
|
|
||||||
font: {
|
|
||||||
weight: 'bold'
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
},
|
]
|
||||||
x: {
|
},
|
||||||
title: {
|
options: {
|
||||||
display: true,
|
responsive: true,
|
||||||
text: '기간',
|
maintainAspectRatio: false,
|
||||||
font: {
|
scales: {
|
||||||
weight: 'bold'
|
y: {
|
||||||
|
beginAtZero: true,
|
||||||
|
title: {
|
||||||
|
display: true,
|
||||||
|
text: 'Kbit/s', // Y축 레이블에 Kbit/s 명시
|
||||||
|
font: {
|
||||||
|
weight: 'bold'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
ticks: {
|
x: {
|
||||||
autoSkip: true,
|
title: {
|
||||||
maxTicksLimit: 20
|
display: true,
|
||||||
}
|
text: '기간',
|
||||||
}
|
font: {
|
||||||
},
|
weight: 'bold'
|
||||||
plugins: {
|
}
|
||||||
legend: {
|
},
|
||||||
position: 'top',
|
ticks: {
|
||||||
labels: {
|
autoSkip: true,
|
||||||
usePointStyle: true,
|
maxTicksLimit: 20
|
||||||
padding: 20
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
tooltip: {
|
plugins: {
|
||||||
mode: 'index',
|
legend: {
|
||||||
intersect: false,
|
position: 'top',
|
||||||
|
labels: {
|
||||||
|
usePointStyle: true,
|
||||||
|
padding: 20
|
||||||
|
}
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
mode: 'index',
|
||||||
|
intersect: false,
|
||||||
|
}
|
||||||
|
},
|
||||||
|
hover: {
|
||||||
|
mode: 'nearest',
|
||||||
|
intersect: true
|
||||||
}
|
}
|
||||||
},
|
|
||||||
hover: {
|
|
||||||
mode: 'nearest',
|
|
||||||
intersect: true
|
|
||||||
}
|
}
|
||||||
}
|
});
|
||||||
});
|
} catch (e) {
|
||||||
|
console.error("Chart.js Initialization Failed:", e);
|
||||||
|
document.getElementById('errorMessage').textContent = `차트 생성 실패: ${e.message}. 브라우저의 콘솔을 확인하세요.`;
|
||||||
|
document.getElementById('errorMessage').style.display = 'block';
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
</body>
|
</body>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user