231 lines
7.5 KiB
PHP
231 lines
7.5 KiB
PHP
<!DOCTYPE html>
|
|
<html lang="ko">
|
|
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title><?= $title ?? '대시보드' ?></title>
|
|
<!-- Bootstrap 5 CSS CDN 로드 -->
|
|
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
<!-- Chart.js CDN 로드 -->
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.1/dist/chart.umd.min.js"></script>
|
|
<style>
|
|
:root {
|
|
--bs-primary: #0d6efd;
|
|
--bs-success: #198754;
|
|
--bs-danger: #dc3545;
|
|
}
|
|
|
|
.chart-container {
|
|
position: relative;
|
|
height: 400px;
|
|
}
|
|
</style>
|
|
</head>
|
|
|
|
<body>
|
|
<div id="app" class="container-fluid">
|
|
<div class="card shadow-lg dashboard-card">
|
|
<div class="card-body p-4 p-md-5">
|
|
<h2 id="chartTitle" class="h5 card-title text-dark mb-1"><?= $viewDatas['entity']->getCustomTitle() ?> 트래픽 속도 추이 (IN/OUT Kbit/s)</h2>
|
|
<div class="row g-3 mb-3">
|
|
<div class="col-5"><input type="date" id="startDate" class="form-control" value="<?= date('Y-m-d') ?>"></div>
|
|
<div class="col-5"><input type="date" id="endDate" class="form-control" value="<?= date('Y-m-d') ?>"></div>
|
|
<div class="col-2"><button onclick="fetchDataAndDrawChart()" id="viewChartBtn" class="btn btn-primary">차트 보기</button></div>
|
|
</div>
|
|
|
|
<div id="loadingIndicator" class="text-center p-5" style="display: none;">
|
|
<div class="spinner-border text-primary" role="status">
|
|
<span class="visually-hidden">Loading...</span>
|
|
</div>
|
|
<p class="mt-3 text-primary fw-bold">데이터를 불러오는 중입니다... 잠시만 기다려 주세요.</p>
|
|
</div>
|
|
|
|
<div id="errorMessage" class="alert alert-danger text-center" role="alert" style="display: none;"></div>
|
|
<div id="noDataMessage" class="alert alert-warning text-center" role="alert" style="display: none;">선택하신 기간에는 조회된 트래픽 데이터가 없습니다. 기간을 다시 설정해주세요.</div>
|
|
|
|
<div class="chart-container">
|
|
<canvas id="trafficChart"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Bootstrap JS Bundle -->
|
|
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
|
|
|
|
<script>
|
|
let trafficChart = null;
|
|
|
|
document.addEventListener('DOMContentLoaded', () => {
|
|
fetchDataAndDrawChart();
|
|
});
|
|
|
|
async function fetchAggregatedData(startDate, endDate) {
|
|
const baseEndpoint = '<?= base_url("/admin/traffic/data/{$viewDatas['entity']->getPK()}") ?>';
|
|
const params = new URLSearchParams({
|
|
startDate,
|
|
endDate
|
|
});
|
|
const endpoint = `${baseEndpoint}?${params.toString()}`;
|
|
|
|
const maxRetries = 3;
|
|
let delay = 1000;
|
|
|
|
for (let i = 0; i < maxRetries; i++) {
|
|
try {
|
|
const response = await fetch(endpoint, {
|
|
method: 'GET'
|
|
});
|
|
if (!response.ok) throw new Error(`HTTP error! Status: ${response.status}`);
|
|
const result = await response.json();
|
|
|
|
if (result.status === 'error') throw new Error(result.message || "서버 오류");
|
|
|
|
return result;
|
|
|
|
} catch (error) {
|
|
console.error(`Attempt ${i + 1} failed:`, error);
|
|
if (i === maxRetries - 1) throw new Error("서버에서 데이터를 가져오는데 실패했습니다.");
|
|
await new Promise(resolve => setTimeout(resolve, delay));
|
|
delay *= 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
async function fetchDataAndDrawChart() {
|
|
const startDate = document.getElementById('startDate').value;
|
|
const endDate = document.getElementById('endDate').value;
|
|
const chartTitleElement = document.getElementById('chartTitle');
|
|
const loadingIndicator = document.getElementById('loadingIndicator');
|
|
const errorMessage = document.getElementById('errorMessage');
|
|
const noDataMessage = document.getElementById('noDataMessage');
|
|
const canvas = document.getElementById('trafficChart');
|
|
const viewChartBtn = document.getElementById('viewChartBtn');
|
|
|
|
loadingIndicator.style.display = 'block';
|
|
errorMessage.style.display = 'none';
|
|
noDataMessage.style.display = 'none';
|
|
canvas.style.opacity = '0';
|
|
viewChartBtn.disabled = true;
|
|
|
|
try {
|
|
const result = await fetchAggregatedData(startDate, endDate);
|
|
|
|
// calculates 안전 처리
|
|
const calculates = result.calculates || {};
|
|
const in95 = calculates.in_kbits ?? '-';
|
|
const out95 = calculates.out_kbits ?? '-';
|
|
|
|
// 차트 제목 업데이트
|
|
chartTitleElement.textContent = `트래픽 속도 추이 , 상위5%를 제외한 최대값=>(IN: ${in95} Kbit/s, OUT: ${out95} Kbit/s)`;
|
|
|
|
const data = result.data || [];
|
|
if (data.length === 0) {
|
|
noDataMessage.style.display = 'block';
|
|
if (trafficChart) {
|
|
trafficChart.destroy();
|
|
trafficChart = null;
|
|
}
|
|
return;
|
|
}
|
|
|
|
const labels = data.map(d => d.created_at);
|
|
const inData = data.map(d => Number(d.in_kbits));
|
|
const outData = data.map(d => Number(d.out_kbits));
|
|
|
|
drawChart(labels, inData, outData);
|
|
|
|
} catch (error) {
|
|
console.error("Chart data processing error:", error);
|
|
errorMessage.textContent = `데이터 로딩 실패: ${error.message}`;
|
|
errorMessage.style.display = 'block';
|
|
} finally {
|
|
loadingIndicator.style.display = 'none';
|
|
canvas.style.opacity = '1';
|
|
viewChartBtn.disabled = false;
|
|
}
|
|
}
|
|
|
|
function drawChart(labels, inData, outData) {
|
|
const ctx = document.getElementById('trafficChart').getContext('2d');
|
|
if (trafficChart) trafficChart.destroy();
|
|
|
|
trafficChart = new Chart(ctx, {
|
|
type: 'line',
|
|
data: {
|
|
labels,
|
|
datasets: [{
|
|
label: 'IN Kbit/s',
|
|
data: inData,
|
|
borderColor: '#198754',
|
|
backgroundColor: 'rgba(25, 135, 84, 0.1)',
|
|
fill: true,
|
|
tension: 0.3,
|
|
pointRadius: 3,
|
|
pointHoverRadius: 6
|
|
},
|
|
{
|
|
label: 'OUT Kbit/s',
|
|
data: outData,
|
|
borderColor: '#dc3545',
|
|
backgroundColor: 'rgba(220, 53, 69, 0.1)',
|
|
fill: true,
|
|
tension: 0.3,
|
|
pointRadius: 3,
|
|
pointHoverRadius: 6
|
|
}
|
|
]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true,
|
|
title: {
|
|
display: true,
|
|
text: 'Kbit/s',
|
|
font: {
|
|
weight: 'bold'
|
|
}
|
|
}
|
|
},
|
|
x: {
|
|
title: {
|
|
display: true,
|
|
text: '기간',
|
|
font: {
|
|
weight: 'bold'
|
|
}
|
|
},
|
|
ticks: {
|
|
autoSkip: true,
|
|
maxTicksLimit: 20
|
|
}
|
|
}
|
|
},
|
|
plugins: {
|
|
legend: {
|
|
position: 'top',
|
|
labels: {
|
|
usePointStyle: true,
|
|
padding: 20
|
|
}
|
|
},
|
|
tooltip: {
|
|
mode: 'index',
|
|
intersect: false
|
|
}
|
|
},
|
|
hover: {
|
|
mode: 'nearest',
|
|
intersect: true
|
|
}
|
|
}
|
|
});
|
|
}
|
|
</script>
|
|
</body>
|
|
|
|
</html>
|