1026 lines
36 KiB
Plaintext
1026 lines
36 KiB
Plaintext
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
|
|
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
|
|
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
|
|
<%@ taglib prefix="ui" uri="http://egovframework.gov/ctl/ui"%>
|
|
<html>
|
|
<head>
|
|
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
|
|
<script src="https://code.jquery.com/jquery-3.7.1.min.js"></script>
|
|
<!-- Bootstrap Datepicker -->
|
|
<link rel="stylesheet" href="${pageContext.request.contextPath}/js/bootstrap-datepicker-1.9.0-dist/css/bootstrap-datepicker.min.css">
|
|
<script type="text/javascript" src="${pageContext.request.contextPath}/js/bootstrap-datepicker-1.9.0-dist/js/bootstrap-datepicker.min.js"></script>
|
|
<script type="text/javascript" src="${pageContext.request.contextPath}/js/bootstrap-datepicker-1.9.0-dist/locales/bootstrap-datepicker.ko.min.js"></script>
|
|
|
|
<!-- MUI + Chart.js + jQuery -->
|
|
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
|
<style>
|
|
body { font-family: 'Pretendard', 'Roboto', sans-serif; background-color: #f5f6fa; color: #333; margin: 0; padding: 20px; }
|
|
|
|
.chart-container {
|
|
background: #fff; border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.05);
|
|
padding: 20px; margin-bottom: 20px;
|
|
}
|
|
.chart-container h3 { margin-bottom: 10px; }
|
|
/* 국토지반 API 호출 통계 */
|
|
.stats-container {
|
|
margin: 20px auto;
|
|
background-color: #ffffff;
|
|
border: 1px solid #dbe3e0;
|
|
border-radius: 8px;
|
|
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
|
|
/* overflow: hidden; */
|
|
}
|
|
|
|
/* 헤더 영역 (버튼 배치를 위해 수정) */
|
|
.stats-header {
|
|
padding: 20px 30px;
|
|
border-bottom: 1px solid #eee;
|
|
overflow: hidden; /* float을 포함하기 위함 */
|
|
}
|
|
.stats-header h1 {
|
|
margin: 0;
|
|
color: #2c3e50;
|
|
font-size: 24px;
|
|
display: inline-block; /* 버튼과 정렬 */
|
|
float: left;
|
|
}
|
|
|
|
/* CSV 내보내기 버튼 스타일 */
|
|
.export-btn {
|
|
float: right;
|
|
background-color: #007bff;
|
|
color: white;
|
|
border: none;
|
|
padding: 8px 14px;
|
|
border-radius: 5px;
|
|
font-weight: 600;
|
|
cursor: pointer;
|
|
font-size: 14px;
|
|
transition: background-color 0.2s;
|
|
}
|
|
.export-btn:hover {
|
|
background-color: #0056b3;
|
|
}
|
|
|
|
/* (이하 탭, 테이블 스타일 동일) */
|
|
.tab-nav {
|
|
display: flex;
|
|
background-color: #fdfdfd;
|
|
border-bottom: 1px solid #eee;
|
|
padding: 0 30px;
|
|
}
|
|
.tab-link {
|
|
font-size: 16px;
|
|
font-weight: 600;
|
|
color: #555;
|
|
background-color: transparent;
|
|
border: none;
|
|
padding: 15px 20px;
|
|
cursor: pointer;
|
|
border-bottom: 3px solid transparent;
|
|
transition: all 0.3s ease;
|
|
margin-right: 10px;
|
|
}
|
|
.tab-link:hover {
|
|
color: #007bff;
|
|
}
|
|
.tab-link.active {
|
|
color: #007bff;
|
|
border-bottom-color: #007bff;
|
|
}
|
|
.tab-content-wrapper {
|
|
padding: 30px;
|
|
}
|
|
.tab-content {
|
|
display: none;
|
|
}
|
|
.tab-content.active {
|
|
display: block;
|
|
}
|
|
.tab-content h2 {
|
|
font-size: 20px;
|
|
color: #34495e;
|
|
margin-bottom: 20px;
|
|
}
|
|
.stats-table {
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
font-size: 14px;
|
|
}
|
|
.stats-table th,
|
|
.stats-table td {
|
|
border: 1px solid #e0e0e0;
|
|
padding: 12px 15px;
|
|
text-align: left;
|
|
}
|
|
.stats-table thead {
|
|
background-color: #f9fafa;
|
|
}
|
|
.stats-table th {
|
|
font-weight: 600;
|
|
color: #444;
|
|
}
|
|
.stats-table tbody tr:nth-child(even) {
|
|
background-color: #fcfcfc;
|
|
}
|
|
.stats-table td:last-child {
|
|
text-align: right;
|
|
font-weight: 600;
|
|
color: #e74c3c;
|
|
}
|
|
.stats-table .period-cell {
|
|
vertical-align: top;
|
|
font-weight: 600;
|
|
color: #333;
|
|
background-color: #fdfdfd;
|
|
}
|
|
/* 국토지반 API 호출 통계 END ------------------------- */
|
|
.dashboard-grid {
|
|
display: grid; grid-template-columns: 1fr 1fr; gap: 20px;
|
|
}
|
|
|
|
.card {
|
|
background: #fff; border-radius: 12px; box-shadow: 0 2px 8px rgba(0,0,0,0.05); padding: 20px;
|
|
}
|
|
|
|
.card-header {
|
|
display: flex; justify-content: space-between; align-items: center; margin-bottom: 15px;
|
|
}
|
|
|
|
.dashboard-btn {
|
|
background: linear-gradient(135deg, #4285f4, #1a73e8);
|
|
color: #fff; border: none; border-radius: 20px; padding: 6px 16px;
|
|
cursor: pointer; font-weight: 600; transition: 0.3s;
|
|
}
|
|
.dashboard-btn:hover { opacity: 0.9; }
|
|
|
|
.status-item {
|
|
display: flex; align-items: center; justify-content: space-between;
|
|
background: #fafafa; border-radius: 10px; padding: 10px 15px; margin-bottom: 8px;
|
|
}
|
|
.status-left { display: flex; align-items: center; gap: 10px; }
|
|
.status-icon {
|
|
width: 26px; height: 26px; display: flex; justify-content: center; align-items: center; border-radius: 50%;
|
|
}
|
|
.success { background-color: #e7f9ed; color: #2ecc71; }
|
|
.fail { background-color: #fdecea; color: #e74c3c; }
|
|
.delay { background-color: #eaf3fd; color: #3498db; }
|
|
.error { background-color: #fff7e6; color: #f1c40f; }
|
|
|
|
/* 통제 카드 */
|
|
.switch-container {
|
|
max-height: 260px;
|
|
overflow-y: auto;
|
|
}
|
|
|
|
.api-switch {
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center; /* 수직 가운데 정렬 */
|
|
background: #f8f8f8;
|
|
border-radius: 10px;
|
|
padding: 12px 15px;
|
|
margin-bottom: 10px;
|
|
gap: 12px; /* 텍스트와 스위치 사이 여백 */
|
|
}
|
|
|
|
.api-switch > div {
|
|
flex: 1; /* 왼쪽 내용이 남는 공간 차지 */
|
|
min-width: 0; /* 긴 텍스트 줄바꿈 정상화 */
|
|
}
|
|
|
|
.switch-title {
|
|
font-weight: 600;
|
|
font-size: 14px;
|
|
color: #222;
|
|
margin-bottom: 4px;
|
|
}
|
|
|
|
.switch-desc {
|
|
font-size: 12px;
|
|
color: #777;
|
|
line-height: 1.4;
|
|
white-space: pre-line; /* \n이나 <br>을 줄바꿈으로 인식 */
|
|
}
|
|
|
|
/* 스위치 디자인 */
|
|
.mui-switch {
|
|
position: relative;
|
|
flex-shrink: 0; /* 스위치 크기 줄어들지 않게 */
|
|
display: inline-block;
|
|
width: 48px;
|
|
height: 26px;
|
|
}
|
|
|
|
.mui-switch input {
|
|
opacity: 0;
|
|
width: 0;
|
|
height: 0;
|
|
}
|
|
|
|
.mui-slider {
|
|
position: absolute;
|
|
cursor: pointer;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
bottom: 0;
|
|
background-color: #ccc;
|
|
transition: 0.4s;
|
|
border-radius: 34px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: ${''}; /* 가운데 맞춤 */
|
|
}
|
|
|
|
.mui-slider:before {
|
|
position: absolute;
|
|
content: "OFF";
|
|
color: #fff;
|
|
font-size: 10px;
|
|
height: 20px;
|
|
width: 20px;
|
|
left: 3px;
|
|
bottom: 3px;
|
|
background-color: #999;
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
border-radius: 50%;
|
|
transition: 0.4s;
|
|
}
|
|
|
|
input:checked + .mui-slider {
|
|
background-color: #4A90E2;
|
|
}
|
|
|
|
input:checked + .mui-slider:before {
|
|
transform: translateX(22px);
|
|
content: "ON";
|
|
background-color: #1A73E8;
|
|
}
|
|
|
|
/* 로그 테이블 */
|
|
.table-container {
|
|
margin-top: 25px; background: #fff; border-radius: 12px; padding: 20px;
|
|
box-shadow: 0 2px 8px rgba(0,0,0,0.05);
|
|
}
|
|
table { width: 100%; border-collapse: collapse; }
|
|
thead { background: #f0f3f8; }
|
|
th, td { padding: 10px; border-bottom: 1px solid #eee; text-align: left; }
|
|
th { color: #555; font-weight: 600; }
|
|
|
|
.pagination {
|
|
display: flex;
|
|
justify-content: center;
|
|
align-items: center;
|
|
gap: 6px;
|
|
margin-top: 15px;
|
|
}
|
|
.pagination button {
|
|
border: none;
|
|
background-color: #e2e8f0;
|
|
color: #334155;
|
|
padding: 6px 12px;
|
|
border-radius: 8px;
|
|
font-size: 13px;
|
|
cursor: pointer;
|
|
transition: all 0.2s ease;
|
|
}
|
|
.pagination button.active {
|
|
background-color: #3b82f6;
|
|
color: white;
|
|
}
|
|
.pagination button:hover:not(.active) {
|
|
background-color: #cbd5e1;
|
|
}
|
|
|
|
.datepicker-wrapper {
|
|
}
|
|
.datepicker-dropdown {
|
|
z-index: 99999 !important;
|
|
position: absolute;
|
|
left: 0 !important;
|
|
background: #fff;
|
|
border: 1px solid #e6e6e6;
|
|
}
|
|
|
|
</style>
|
|
</head>
|
|
<body>
|
|
|
|
<!-- 일일 접속량 -->
|
|
<div class="chart-container">
|
|
<h3>📈 일일 접속량</h3>
|
|
<!-- <div id="chartDateContainer" style="position: relative;"><input type="text" name="chartDate" id="chartDate " class="input" placeholder="클릭하여 날짜 선택" ></div> -->
|
|
<div class="datepicker-wrapper" style="position: relative; display: inline-block;">
|
|
<input type="text" name="chartDate" id="chartDate" class="input" placeholder="클릭하여 날짜 선택">
|
|
</div>
|
|
<canvas id="trafficChart" height="100"></canvas>
|
|
</div>
|
|
|
|
<!-- 국토지반 API 호출 건수 통계 -->
|
|
<div class="stats-container">
|
|
<header class="stats-header">
|
|
<h1>국토지반 API 호출 통계</h1>
|
|
<button id="export-csv-btn" class="export-btn">CSV 내보내기</button>
|
|
</header>
|
|
|
|
<nav class="tab-nav">
|
|
<button class="tab-link active" data-tab="daily">일별 통계</button>
|
|
<button class="tab-link" data-tab="monthly">월별 통계</button>
|
|
<button class="tab-link" data-tab="yearly">연도별 통계</button>
|
|
</nav>
|
|
|
|
<div class="tab-content-wrapper">
|
|
<div class="datepicker-wrapper" style="position: relative; display: inline-block;">
|
|
<input type="text" name="count_from_dt" id="count_from_dt" class="input" placeholder="클릭하여 날짜 선택" >~
|
|
<input type="text" name="count_to_dt" id="count_to_dt" class="input" placeholder="클릭하여 날짜 선택" >
|
|
</div>
|
|
<div id="daily" class="tab-content active">
|
|
<h2>일별 API 호출 현황</h2>
|
|
<table class="stats-table">
|
|
<thead>
|
|
<tr>
|
|
<th>날짜</th>
|
|
<th>API 명</th>
|
|
<th>호출 건수</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="daily-tbody">
|
|
<tr><td colspan="3">표시 할 통계데이타가 없습니다.</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div id="monthly" class="tab-content">
|
|
<h2>월별 API 호출 현황</h2>
|
|
<table class="stats-table">
|
|
<thead>
|
|
<tr>
|
|
<th>월</th>
|
|
<th>API 명</th>
|
|
<th>총 호출 건수</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="monthly-tbody">
|
|
<tr><td colspan="3">표시 할 통계데이타가 없습니다.</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
<div id="yearly" class="tab-content">
|
|
<h2>연도별 API 호출 현황</h2>
|
|
<table class="stats-table">
|
|
<thead>
|
|
<tr>
|
|
<th>연도</th>
|
|
<th>API 명</th>
|
|
<th>총 호출 건수</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="yearly-tbody">
|
|
<tr><td colspan="3">표시 할 통계데이타가 없습니다.</td></tr>
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="dashboard-grid">
|
|
<!-- API 호출 현황 -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3>API 호출 현황</h3>
|
|
</div>
|
|
|
|
<div class="status-item">
|
|
<div class="status-left">
|
|
<div class="status-icon success">✔</div>
|
|
<div>
|
|
<div><strong>호출 성공</strong></div>
|
|
<small>3초 이내 응답</small>
|
|
</div>
|
|
</div>
|
|
<div><strong id="successRate">0%</strong></div>
|
|
</div>
|
|
|
|
<div class="status-item">
|
|
<div class="status-left">
|
|
<div class="status-icon fail">❌</div>
|
|
<div>
|
|
<div><strong>호출 실패</strong></div>
|
|
<small>API 실패</small>
|
|
</div>
|
|
</div>
|
|
<div><strong id="failRate">0%</strong></div>
|
|
</div>
|
|
|
|
<div class="status-item">
|
|
<div class="status-left">
|
|
<div class="status-icon delay">🕓</div>
|
|
<div>
|
|
<div><strong>호출 지연</strong></div>
|
|
<small>3초 초과 지연</small>
|
|
</div>
|
|
</div>
|
|
<div><strong id="delayRate">0%</strong></div>
|
|
</div>
|
|
|
|
<div class="status-item">
|
|
<div class="status-left">
|
|
<div class="status-icon error">⚠️</div>
|
|
<div>
|
|
<div><strong>호출 오류</strong></div>
|
|
<small>오류 응답</small>
|
|
</div>
|
|
</div>
|
|
<div><strong id="errorRate">0%</strong></div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- API 호출 통제 -->
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h3>API 호출 통제</h3>
|
|
<label class="mui-switch">
|
|
<input type="checkbox" id="globalSwitch" onchange="toggleStatus(this)" checked>
|
|
<span class="mui-slider"></span>
|
|
</label>
|
|
</div>
|
|
|
|
<div class="switch-container" id="apiSwitchList">
|
|
<div class="api-switch">
|
|
<div>
|
|
<div class="switch-title">표시 할 API 목록이 존재하지 않습니다.</div>
|
|
<div class="switch-desc">API를 등록해주세요.</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 로그 테이블 -->
|
|
<div class="table-container">
|
|
<h3>API 호출 로그</h3>
|
|
<table>
|
|
<colgroup>
|
|
<col style="width: 3%">
|
|
<col style="width: 7%">
|
|
<col style="width: 5%">
|
|
<col style="width: 15%">
|
|
<col style="width: auto">
|
|
<col style="width: 15%">
|
|
<col style="width: 5%">
|
|
</colgroup>
|
|
<thead>
|
|
<tr>
|
|
<th>No.</th>
|
|
<th>API_KEY</th>
|
|
<th>ACCESS_ID</th>
|
|
<th>ACCESS_TYPE</th>
|
|
<th>ACCESS_TABLE</th>
|
|
<th>ACCESS_DT</th>
|
|
<th>IP_ADDRESS</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody id="apiLogBody">
|
|
</tbody>
|
|
</table>
|
|
|
|
<!-- 페이징 영역 -->
|
|
<div id="pagination" class="pagination"></div>
|
|
</div>
|
|
|
|
<script>
|
|
// 퍼센트 애니메이션
|
|
function animateValue(id, start, end, duration) {
|
|
const obj = document.getElementById(id);
|
|
let startTime = null;
|
|
|
|
function animation(currentTime) {
|
|
if (!startTime) startTime = currentTime;
|
|
const progress = Math.min((currentTime - startTime) / duration, 1);
|
|
obj.innerText = (progress * (end - start) + start).toFixed(1) + "%";
|
|
if (progress < 1) requestAnimationFrame(animation);
|
|
}
|
|
requestAnimationFrame(animation);
|
|
}
|
|
// --------- 일일 접속량 차트 관련 변수 --------------------
|
|
let trafficChart; // 전역 변수
|
|
// --------- 일일 접속량 차트 관련 변수 --------------------
|
|
|
|
// --------- API 호출 통계 기간 입력 관련 변수 --------------------
|
|
var today = new Date(); // (기준)오늘 날짜
|
|
var thisYear = today.getFullYear(); // (기준)올해연도
|
|
var thisMonth = today.getMonth+1; // (기준)이번달
|
|
var toDate = new Date(today.getFullYear(), today.getMonth(), 1); // (기준)이번달1일
|
|
|
|
var weekAgo = new Date(); // 7일 전 날짜
|
|
weekAgo.setDate(today.getDate() - 7);
|
|
|
|
var fvMonthAgo = new Date();
|
|
fvMonthAgo.setMonth(toDate.getMonth() -5);
|
|
// --------- API 호출 통계 기간 입력 관련 변수 --------------------
|
|
|
|
// --------- API 호출 통계 CSV 내보내기 관련 변수 --------------------
|
|
let processedStats = {
|
|
daily: new Map(),
|
|
monthly: new Map(),
|
|
yearly: new Map()
|
|
};
|
|
// --------- API 호출 통계 CSV 내보내기 관련 변수 --------------------
|
|
|
|
$(document).ready(function(){
|
|
// ----------------------------------------------------
|
|
// 1. 날짜 선택기 초기화
|
|
// ----------------------------------------------------
|
|
|
|
// datepicker
|
|
$("#chartDate, #count_from_dt, #count_to_dt").datepicker({
|
|
format: "yyyy-mm-dd",
|
|
language: "ko",
|
|
autoclose: true,
|
|
todayHighlight: true,
|
|
container: $("#chartDate").closest(".datepicker-wrapper")
|
|
}).datepicker("setDate", new Date());
|
|
|
|
$("#count_from_dt").datepicker("setDate", weekAgo); // 국토지반 API 호출 통계 시작일 셋팅(저번주)
|
|
|
|
// ----------------------------------------------------
|
|
// 2. 탭 전환 처리
|
|
// ----------------------------------------------------
|
|
$('.tab-link').on('click', function() {
|
|
const targetId = $(this).data('tab');
|
|
|
|
$('.tab-link').removeClass('active');
|
|
$(this).addClass('active');
|
|
|
|
$('.tab-content').removeClass('active');
|
|
$('#' + targetId).addClass('active');
|
|
|
|
$("#count_from_dt, #count_to_dt").datepicker('destroy'); // datepicker 재선언(format 변경)
|
|
// 일별 탭 클릭 시
|
|
if (targetId === 'daily') {
|
|
// 월 단위 datepicker 재초기화
|
|
$("#count_from_dt, #count_to_dt").datepicker({
|
|
format: "yyyy-mm-dd",
|
|
language: "ko",
|
|
autoclose: true,
|
|
minViewMode: 0, // 월 단위
|
|
todayHighlight: true,
|
|
container: $("#count_from_dt").closest(".datepicker-wrapper")
|
|
});
|
|
$("#count_to_dt").datepicker("setDate", today); // 국토지반 API 호출 통계 시작일 셋팅(저번주)
|
|
$("#count_from_dt").datepicker("setDate", weekAgo); // 국토지반 API 호출 통계 시작일 셋팅(저번주)
|
|
}
|
|
// 월별 탭 클릭 시
|
|
if (targetId === 'monthly') {
|
|
// 월 단위 datepicker 재초기화
|
|
$("#count_from_dt, #count_to_dt").datepicker({
|
|
format: "yyyy-mm",
|
|
language: "ko",
|
|
autoclose: true,
|
|
minViewMode: 1, // 월 단위
|
|
todayHighlight: true,
|
|
container: $("#count_from_dt").closest(".datepicker-wrapper")
|
|
});
|
|
|
|
$("#count_from_dt").datepicker("setDate", fvMonthAgo);
|
|
$("#count_to_dt").datepicker("setDate", thisMonth);
|
|
|
|
$("#count_from_dt").val($("#count_from_dt").val().substr(0,7));
|
|
$("#count_to_dt").val($("#count_to_dt").val().substr(0,7));
|
|
}
|
|
|
|
// 연도별 탭 클릭 시
|
|
if (targetId === 'yearly') {
|
|
// 월 단위 datepicker 재초기화
|
|
$("#count_from_dt, #count_to_dt").datepicker({
|
|
minViewMode: 'years',
|
|
format: "yyyy",
|
|
language: "ko",
|
|
autoclose: true,
|
|
todayHighlight: true,
|
|
container: $("#count_from_dt").closest(".datepicker-wrapper")
|
|
});
|
|
|
|
$("#count_to_dt").datepicker("setDate", new Date(thisYear, 0, 1));
|
|
$("#count_from_dt").datepicker("setDate", new Date((thisYear -5), 0, 1));
|
|
$("#count_to_dt").val($("#count_to_dt").val().substr(0,4))
|
|
$("#count_from_dt").val($("#count_from_dt").val().substr(0,4))
|
|
}
|
|
});
|
|
|
|
// ----------------------------------------------------
|
|
// 3. API 호출 현황 초기 로드
|
|
// ----------------------------------------------------
|
|
animateValue("successRate", 0, 100, 1500);
|
|
animateValue("failRate", 0, 0, 1500);
|
|
animateValue("delayRate", 0, 0, 1500);
|
|
animateValue("errorRate", 0, 0, 1500);
|
|
|
|
// API 호출 통계 > 일별, 월별, 연도별 통계
|
|
getApiStat();
|
|
|
|
// API 호출 통제 목록 조회
|
|
getMgmtApiList();
|
|
|
|
// API 호출로그 목록 조회
|
|
getMgmtApiLogList();
|
|
|
|
// 페이지 로드시 차트 먼저 로드
|
|
getMgmtApiChart();
|
|
|
|
// 날짜 변경 시 차트 다시 로드
|
|
$(document).on('change', '#chartDate', function() {
|
|
getMgmtApiChart();
|
|
});
|
|
|
|
// API 호출 현황 건수 - 날짜 변경 시 차트 다시 로드
|
|
$(document).on('change', '#count_from_dt, #count_to_dt', function() {
|
|
if(!validDate()){
|
|
return;
|
|
}
|
|
getApiStat();
|
|
});
|
|
|
|
// API 호출 통계 건수 - CSV 내보내기 클릭
|
|
$(document).on('click', '#export-csv-btn', function() {
|
|
exportToCSV();
|
|
})
|
|
});
|
|
|
|
// API 호출 통제 목록 조회
|
|
function getMgmtApiList(){
|
|
$.ajax({
|
|
type : "POST",
|
|
url : "/admins/mgmtApi/list.do" ,
|
|
data : {},
|
|
dataType :"json",
|
|
success : function(res){ // res.code, res.msg, res.data
|
|
if (res.code == "SUCCESS") {
|
|
let procList = res.data; //Array List
|
|
const $listContainer = $("#apiSwitchList");
|
|
$listContainer.empty(); // 기존 내용 제거
|
|
// 데이터 반복
|
|
$.each(res.data, function(i, item) {
|
|
// DOM에 추가
|
|
$listContainer.append(drawApiControlList(item));
|
|
});
|
|
} else {
|
|
alert("관리 API 목록 조회중 오류가 발생하였습니다. 다시 시도해주세요.");
|
|
}
|
|
},
|
|
error : function(response){
|
|
alert("관리 API 목록 조회중 내부 오류가 발생하였습니다. 다시 시도해주세요.");
|
|
}
|
|
});
|
|
}
|
|
|
|
// 개별 API 활성상태 변경
|
|
function toggleStatus(ele) { // ele => 스위치(형제요소 input 값으로 변경할 상태 Y,N 그리고 btnType을 판별한다)
|
|
let btnType = $(ele).attr('id'); // 일괄변경 버튼, 각 변경 버튼 여부(globalSwitch면 일괄변경임)
|
|
|
|
let idxArr = [];
|
|
let active = $(ele).is(":checked") ? "Y" : "N"
|
|
if (btnType == 'globalSwitch') {
|
|
$('#apiSwitchList .api-switch').each(function(i, el) {
|
|
const idx = $(el).data('idx');
|
|
idxArr.push(idx);
|
|
});
|
|
} else {
|
|
idxArr.push($(ele).parents('.api-switch').data("idx"));
|
|
}
|
|
|
|
$.ajax({
|
|
type : "POST",
|
|
url : "/admins/mgmtApi/toggleSts.do" ,
|
|
data : {idxArr:idxArr, activeYn: active},
|
|
traditional: true,
|
|
dataType :"json",
|
|
success : function(res){ // res.code, res.msg, res.data
|
|
if (res.code == "SUCCESS") {
|
|
let procList = res.data; //Array List
|
|
const $listContainer = $("#apiSwitchList");
|
|
$listContainer.empty(); // 기존 내용 제거
|
|
// 데이터 반복
|
|
$.each(res.data, function(i, item) {
|
|
// DOM에 추가
|
|
$listContainer.append(drawApiControlList(item));
|
|
});
|
|
} else {
|
|
alert("활성상태 변경 중 오류가 발생했습니다. 다시 시도해주세요");
|
|
}
|
|
},
|
|
error : function(response){
|
|
alert("활성상태 변경 중 내부 오류가 발생했습니다. 다시 시도해주세요");
|
|
}
|
|
});
|
|
}
|
|
|
|
function drawApiControlList(item) {
|
|
// activeYn 이 "Y"이면 체크 상태, 아니면 체크 해제
|
|
const isChecked = item.activeYn === "Y" ? "checked" : "";
|
|
|
|
// HTML 문자열 생성
|
|
const html = `
|
|
<div class="api-switch" data-idx="\${item.idx}">
|
|
<div>
|
|
<div class="switch-title">\${item.idx} \${item.name}</div>
|
|
<div class="switch-desc">\${item.desc.replace(/\\n/g, "<br>")}</div>
|
|
</div>
|
|
<label class="mui-switch">
|
|
<input type="checkbox" class="api-toggle" data-api="\${item.idx}" onchange="toggleStatus(this)" \${isChecked}>
|
|
<span class="mui-slider"></span>
|
|
</label>
|
|
</div>
|
|
`;
|
|
return html;
|
|
}
|
|
|
|
// API 호출 통계 > 일별, 월별, 연도별 통계
|
|
function getApiStat(){
|
|
var statType = $('.stats-container > .tab-nav').find('.active').data("tab");
|
|
var fromDt = $('#count_from_dt').val();
|
|
var toDt = $('#count_to_dt').val();
|
|
// 날짜데이터가 올바르게 셋팅되지 않았을때는 요청하지 않는다.
|
|
if (statType === "monthly") {
|
|
if (fromDt.length < 7 || toDt.length < 7 ) return;
|
|
} else if (statType === "yearly") {
|
|
if (fromDt.length < 4 || toDt.length < 4 ) return;
|
|
} else { // statType === "daily"
|
|
if (fromDt.length < 10 || toDt.length < 10 ) return;
|
|
}
|
|
$.ajax({
|
|
type : "POST",
|
|
url : "/admins/mgmtApi/i_counts.do" ,
|
|
data : {
|
|
statType : statType,
|
|
countFromDt: $('#count_from_dt').val(),
|
|
countToDt: $('#count_to_dt').val()
|
|
},
|
|
dataType :"json",
|
|
success : function(res){ // res.code, res.msg, res.data
|
|
if (res.code == "SUCCESS") {
|
|
let countList = res.data;
|
|
let $listContainer;
|
|
// 활성화 된 탭의 tbody를 컨테이너로 지정
|
|
if (statType === "monthly") {
|
|
$listContainer = $("#monthly-tbody");
|
|
} else if (statType === "yearly") {
|
|
$listContainer = $("#yearly-tbody");
|
|
} else {
|
|
$listContainer = $("#daily-tbody");
|
|
}
|
|
|
|
// 활성화 된 탭의 csv 양식으로 지정
|
|
if (statType === "monthly") {
|
|
$listContainer = $("#monthly-tbody");
|
|
processedStats.monthly = countList;
|
|
} else if (statType === "yearly") {
|
|
$listContainer = $("#yearly-tbody");
|
|
processedStats.yearly = countList;
|
|
} else {
|
|
$listContainer = $("#daily-tbody");
|
|
processedStats.daily = countList;
|
|
}
|
|
// DOM에 추가
|
|
drawApiCountList(res.data, $listContainer);
|
|
} else {
|
|
alert("API 호출 로그 목록 조회중 오류가 발생하였습니다. 다시 시도해주세요.");
|
|
}
|
|
},
|
|
error : function(response){
|
|
alert("API 호출 로그 목록 조회중 내부 오류가 발생하였습니다. 다시 시도해주세요.");
|
|
}
|
|
});
|
|
};
|
|
|
|
function drawApiCountList(countList, $target) {
|
|
$target.empty();
|
|
if (!countList || countList.length === 0) {
|
|
$target.append('<tr><td colspan="3">표시 할 통계데이타가 없습니다.</td></tr>');
|
|
return;
|
|
}
|
|
|
|
const grouped = {};
|
|
let totalCount = 0; // 합계 초기화
|
|
|
|
countList.forEach(item => {
|
|
if (!grouped[item.period]) grouped[item.period] = [];
|
|
grouped[item.period].push(item);
|
|
totalCount += parseInt(item.accessCount);
|
|
});
|
|
|
|
let html = '';
|
|
Object.keys(grouped).sort((a, b) => b.localeCompare(a)).forEach(period => {
|
|
const items = grouped[period];
|
|
items.forEach((item, idx) => {
|
|
html += `<tr>
|
|
\${idx === 0 ? `<td rowspan="\${items.length}">\${period}</td>` : ''}
|
|
<td>\${item.apiName}</td>
|
|
<td>\${item.accessCount} 건</td>
|
|
</tr>`;
|
|
});
|
|
});
|
|
|
|
// 합계 row 추가
|
|
html += `<tr style="background:#f9fafa;">
|
|
<td colspan="2" style="font-weight:bold;">합계</td>
|
|
<td style="font-weight:bold;">\${totalCount} 건</td>
|
|
</tr>`;
|
|
|
|
$target.append(html);
|
|
}
|
|
|
|
// API 호출 로그 목록 조회
|
|
function getMgmtApiLogList(){
|
|
$.ajax({
|
|
type : "POST",
|
|
url : "/admins/mgmtApiLog/list.do" ,
|
|
data : {},
|
|
dataType :"json",
|
|
success : function(res){ // res.code, res.msg, res.data
|
|
if (res.code == "SUCCESS") {
|
|
let procList = res.data; //Array List
|
|
const $listContainer = $("#apiLogBody");
|
|
$listContainer.empty(); // 기존 내용 제거
|
|
// 데이터 반복
|
|
$.each(res.data, function(i, item) {
|
|
// DOM에 추가
|
|
$listContainer.append(drawApiLogList(item));
|
|
});
|
|
} else {
|
|
alert("API 호출 로그 목록 조회중 오류가 발생하였습니다. 다시 시도해주세요.");
|
|
}
|
|
},
|
|
error : function(response){
|
|
alert("API 호출 로그 목록 조회중 내부 오류가 발생하였습니다. 다시 시도해주세요.");
|
|
}
|
|
});
|
|
}
|
|
|
|
function drawApiLogList(item) {
|
|
// HTML 문자열 생성
|
|
const html = `
|
|
<tr data-idx="\${item.logSeq}">
|
|
<td>\${item.rn}</td>
|
|
<td title="\${item.apiKey}">\${item.apiKeyConcat}</td>
|
|
<td>\${item.accessId}</td>
|
|
<td>\${item.accessType}</td>
|
|
<td>\${item.accessTable}</td>
|
|
<td>\${item.accessDt}</td>
|
|
<td>\${item.ipAddress}</td>
|
|
</tr>
|
|
`;
|
|
return html;
|
|
}
|
|
|
|
// API 호출 로그 목록 조회
|
|
function getMgmtApiChart(){
|
|
var chartDate = $('#chartDate').val();
|
|
$.ajax({
|
|
type : "POST",
|
|
url : "/admins/mgmtApi/charts.do" ,
|
|
data : {chartDate: $('#chartDate').val()},
|
|
dataType :"json",
|
|
success : function(res){ // res.code, res.msg, res.data
|
|
if (res.code == "SUCCESS") {
|
|
const d = res.data;
|
|
|
|
// 시간대별 카운트를 배열로 변환
|
|
const chartData = [
|
|
d.count00, d.count01, d.count02, d.count03,
|
|
d.count04, d.count05, d.count06, d.count07,
|
|
d.count08, d.count09, d.count10, d.count11,
|
|
d.count12, d.count13, d.count14, d.count15,
|
|
d.count16, d.count17, d.count18, d.count19,
|
|
d.count20, d.count21, d.count22, d.count23
|
|
];
|
|
|
|
drawTrafficChart(chartData);
|
|
} else {
|
|
alert("API 호출 로그 목록 조회중 오류가 발생하였습니다. 다시 시도해주세요.");
|
|
}
|
|
},
|
|
error : function(response){
|
|
alert("API 호출 로그 목록 조회중 내부 오류가 발생하였습니다. 다시 시도해주세요.");
|
|
}
|
|
});
|
|
}
|
|
// Chart.js: 일일 접속량
|
|
function drawTrafficChart(dataArr) {
|
|
const ctx = document.getElementById('trafficChart').getContext('2d');
|
|
const labels = [
|
|
'00시','01시','02시','03시','04시','05시','06시','07시','08시','09시','10시','11시',
|
|
'12시','13시','14시','15시','16시','17시','18시','19시','20시','21시','22시','23시'
|
|
];
|
|
|
|
// 이미 차트가 있으면 갱신
|
|
if (trafficChart) {
|
|
trafficChart.data.datasets[0].data = dataArr;
|
|
trafficChart.update();
|
|
return;
|
|
}
|
|
|
|
// 최초 생성
|
|
trafficChart = new Chart(ctx, {
|
|
type: 'line',
|
|
data: {
|
|
labels: labels,
|
|
datasets: [{
|
|
data: dataArr,
|
|
borderColor: '#4285f4',
|
|
tension: 0.4,
|
|
fill: true,
|
|
backgroundColor: 'rgba(66,133,244,0.1)',
|
|
pointRadius: 3
|
|
}]
|
|
},
|
|
options: {
|
|
plugins: { legend: { display: false } },
|
|
scales: {
|
|
y: {
|
|
beginAtZero: true,
|
|
ticks: { stepSize: 1 }
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function exportToCSV() {
|
|
// 1. 현재 활성화된 탭 찾기
|
|
const activeTab = document.querySelector('.tab-link.active');
|
|
if (!activeTab) return;
|
|
|
|
const tabId = activeTab.getAttribute('data-tab'); // 'daily', 'monthly', 'yearly'
|
|
const dataList = processedStats[tabId]; // 전역 변수에서 배열 데이터 가져오기
|
|
|
|
if (!Array.isArray(dataList) || dataList.length === 0) {
|
|
alert('내보낼 데이터가 없습니다.');
|
|
return;
|
|
}
|
|
|
|
let headers = [];
|
|
let filename = '';
|
|
|
|
// 2. 탭에 맞는 헤더와 파일명 설정
|
|
if (tabId === 'daily') {
|
|
headers = ['날짜', 'API 명', '호출 건수'];
|
|
filename = 'api_stats_daily.csv';
|
|
} else if (tabId === 'monthly') {
|
|
headers = ['월', 'API 명', '총 호출 건수'];
|
|
filename = 'api_stats_monthly.csv';
|
|
} else { // 'yearly'
|
|
headers = ['연도', 'API 명', '총 호출 건수'];
|
|
filename = 'api_stats_yearly.csv';
|
|
}
|
|
|
|
// 3. 날짜 기준으로 내림차순 정렬
|
|
const sortedData = [...dataList].sort((a, b) => b.period.localeCompare(a.period));
|
|
|
|
// 4. CSV 헤더 + 데이터 행 구성
|
|
const rows = [headers];
|
|
sortedData.forEach(item => {
|
|
const { period, apiName, accessCount } = item;
|
|
rows.push([period, apiName, accessCount]);
|
|
});
|
|
|
|
// 5. CSV 문자열로 변환
|
|
const csvContent = rows.map(row => row.join(',')).join('\r\n');
|
|
|
|
// 6. UTF-8 BOM 추가 및 파일 다운로드
|
|
const bom = '\uFEFF';
|
|
const blob = new Blob([bom + csvContent], { type: 'text/csv;charset=utf-8;' });
|
|
const url = URL.createObjectURL(blob);
|
|
|
|
const a = document.createElement('a');
|
|
a.style.display = 'none';
|
|
a.href = url;
|
|
a.download = filename;
|
|
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
|
|
// 6. 리소스 정리
|
|
document.body.removeChild(a);
|
|
URL.revokeObjectURL(url);
|
|
}
|
|
/**
|
|
* API 호출 통계 날짜 유효검사
|
|
*/
|
|
function validDate(){
|
|
var statType = $('.stats-container > .tab-nav').find('.active').data("tab");
|
|
var from_dt_val = $('#count_from_dt').val();
|
|
var to_dt_val = $('#count_to_dt').val();
|
|
if ("daily" === statType && (from_dt_val.length != 10 || to_dt_val.length != 10)) return false; // YYYY-MM-DD
|
|
if ("monthly" === statType && (from_dt_val.length != 7 || to_dt_val.length != 7)) return false; // YYYY-MM
|
|
if ("yearly" === statType && (from_dt_val.length != 4 || to_dt_val.length != 4)) return false; // YYYY
|
|
return true;
|
|
}
|
|
|
|
</script>
|
|
|
|
</body>
|
|
</html>
|