Skip to content

Commit

Permalink
feat: 외부 API 어드민 페이지 구축 (#717)
Browse files Browse the repository at this point in the history
* feat: 어드민 페이지 html, js, css 작성

- thymeleaf 의존성 추가

* refactor: js 내에서 사용하지 않는 메서드 제거

* style: spring-boot-starter 정렬

* style: css 내 주석 제거

* refactor: ApiCallPageController 메서드 이름 유의미하게 변경

* refactor: 버튼 이름 의미 부여, enabled 조회해 버튼 갱신하도록 수정

* feat: 어드민 페이지 주소 환경 변수 설정

* refactor: axios 제거

* fix: ApiCallPageController profile 설정

* fix: 응답값 변환
  • Loading branch information
mzeong authored Oct 22, 2024
1 parent 4201779 commit 381f6ba
Show file tree
Hide file tree
Showing 6 changed files with 293 additions and 0 deletions.
1 change: 1 addition & 0 deletions backend/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-actuator'
implementation 'org.flywaydb:flyway-mysql'
implementation 'org.flywaydb:flyway-core'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'

runtimeOnly 'com.mysql:mysql-connector-j:8.4.0'

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.ody.route.controller;

import lombok.RequiredArgsConstructor;
import org.springframework.context.annotation.Profile;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;

@Profile("!test")
@Controller
@RequiredArgsConstructor
public class ApiCallPageController {

@GetMapping("${api-call.page}")
public String apiCallPage() {
return "api-call";
}
}
3 changes: 3 additions & 0 deletions backend/src/main/resources/common.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,5 +51,8 @@ management:
db:
enabled: false

api-call:
page: ENC(Tmip6pkMmuStDTjkRyspLm2PSd5/ov+T9Gzu2XZJKTjmLsrx4dplPIt/8CarbG2B)

allowed-origins:
api-call: ENC(GwAP/KfLoZ6IqqnMPFpqqmg11CXjh1Al6oMlwB+YTekyHGFuRPn1789uAwyJKx2mPX5BRS4I/CoBn6HgnTJwjPL2hm/eHn7VHACtur1NdVc=)
82 changes: 82 additions & 0 deletions backend/src/main/resources/static/css/api-call.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 20px;
background-color: #f0f0f0;
}

.container {
max-width: 1000px;
margin: 0 auto;
background-color: #fff;
padding: 40px;
border-radius: 5px;
box-shadow: 0 0 10px rgba(0,0,0,0.1);
}

h1, h2 {
color: #333;
}

h1 {
text-align: center;
}

.data-table {
width: 100%;
border-collapse: collapse;
margin-top: 20px;
}

.data-table th, .data-table td {
border: 1px solid #ddd;
padding: 8px;
text-align: center;
}

.data-table th {
background-color: #f2f2f2;
}

.button {
padding: 10px 20px;
border: none;
border-radius: 5px;
font-size: 14px;
font-weight: bold;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
}

.blue-button {
background-color: #a8d8ff;
color: #2c3e50;
}

.red-button {
background-color: #ffb3ba;
color: #2c3e50;
}

.unknown-button {
background-color: gray;
color: white;
}

.blue-button:hover {
background-color: #7fc6ff;
}

.red-button:hover {
background-color: #ff9aa2;
}

.unknown-button:hover {
background-color: #555555;
}

.button:active {
transform: translateY(2px);
box-shadow: 0 1px 3px rgba(0,0,0,0.1);
}
110 changes: 110 additions & 0 deletions backend/src/main/resources/static/js/api-call.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
const DEV_SERVER = 'https://dev.oody.site';
const PROD_SERVER = 'https://prod.oody.site';
const ODSAY = '/odsay';
const GOOGLE = '/google';

const COUNT_URL = '/admin/api-call/count';
const TOGGLE_URL = '/admin/api-call/toggle';
const ENABLED_URL = '/admin/api-call/enabled';

async function initializeApiCallCounts() {
try {
const odsayDevCount = await fetchApiCallCount(DEV_SERVER, COUNT_URL + ODSAY);
const googleDevCount = await fetchApiCallCount(DEV_SERVER, COUNT_URL + GOOGLE);
const odsayProdCount = await fetchApiCallCount(PROD_SERVER, COUNT_URL + ODSAY);
const googleProdCount = await fetchApiCallCount(PROD_SERVER, COUNT_URL + GOOGLE);
const odsayTotalCount = (typeof odsayDevCount !== 'number' || typeof odsayProdCount !== 'number' ? odsayDevCount : odsayDevCount + odsayProdCount);
const googleTotalCount = (typeof googleDevCount !== 'number' || typeof googleProdCount !== 'number' ? googleDevCount : googleDevCount + googleProdCount);

document.querySelector('#countTable').innerHTML = `
<tr>
<td>Dev</td>
<td>${odsayDevCount}</td>
<td>${googleDevCount}</td>
</tr>
<tr>
<td>Prod</td>
<td>${odsayProdCount}</td>
<td>${googleProdCount}</td>
</tr>
<tr>
<td>Total</td>
<td>${odsayTotalCount}</td>
<td>${googleTotalCount}</td>
</tr>
`;
} catch (error) {
console.error('Error fetching api call count:', error);
}
}

async function fetchApiCallCount(url, endpoint) {
try {
const response = await fetch(url + endpoint, {
method: 'GET',
headers: {
'Content-Type': 'application/json',
}
});
const data = await response.json();
return data.count;
} catch (error) {
console.warn(`Error fetching ${endpoint} from ${url}:`, error);
return '-';
}
}

async function initializeApiCallEnabledButtons() {
const configs = [
{id: 'odsayDevToggleButton', server: DEV_SERVER, clientType: ODSAY},
{id: 'googleDevToggleButton', server: DEV_SERVER, clientType: GOOGLE},
{id: 'odsayProdToggleButton', server: PROD_SERVER, clientType: ODSAY},
{id: 'googleProdToggleButton', server: PROD_SERVER, clientType: GOOGLE}
];

for (const config of configs) {
const button = document.getElementById(config.id);
await fetchApiCallEnabled(button, config.server, config.clientType);
button.addEventListener('click', (event) => handleButtonClick(event.target, config.server, config.clientType));
}
}

async function handleButtonClick(button, server, clientType) {
try {
const toggleResponse = await fetch(server + TOGGLE_URL + clientType, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
}
});
if (!toggleResponse.ok) {
throw new Error('Network response was not ok');
}

await fetchApiCallEnabled(button, server, clientType);
} catch (error) {
console.error('Error toggling enabled button:', error);
}
}

async function fetchApiCallEnabled(button, server, clientType) {
try {
const response = await fetch(server + ENABLED_URL + clientType);
const data = await response.json();
updateButton(button, data.enabled);
} catch (error) {
console.warn('Error fetching api call enabled:', error);
button.textContent = 'unknown';
button.className = 'unknown-button';
}
}

function updateButton(button, enabled) {
button.textContent = enabled ? 'enabled' : 'disabled';
button.className = enabled ? 'blue-button' : 'red-button';
}

document.addEventListener('DOMContentLoaded', function () {
initializeApiCallCounts();
initializeApiCallEnabledButtons();
});
80 changes: 80 additions & 0 deletions backend/src/main/resources/templates/api-call.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Ody Admin Page</title>
<link rel="stylesheet" type="text/css" href="css/api-call.css">
</head>
<body>
<div class="container">
<h1>오디 어드민 페이지</h1>

<br>
<h2>외부 API 호출 횟수 조회하기</h2>
<table class="data-table">
<thead>
<tr>
<th></th>
<th>Odsay API (일별)</th>
<th>Google API (월별)</th>
</tr>
</thead>
<tbody id="countTable">
<tr>
<td>Dev</td>
<td id="odsayDevCount">0</td>
<td id="googleDevCount">0</td>
</tr>
<tr>
<td>Prod</td>
<td id="odsayProdCount">0</td>
<td id="googleProdCount">0</td>
</tr>
<tr>
<td>Total</td>
<td id="odsayTotalCount">0</td>
<td id="googleTotalCount">0</td>
</tr>
</tbody>
</table>

<br>
<h2>외부 API 호출 제어하기</h2>
<table class="data-table">
<thead>
<tr>
<th></th>
<th>Odsay API (일별)</th>
<th>Google API (월별)</th>
</tr>
</thead>
<tbody id="toggleTable">
<tr>
<td>Dev</td>
<td>
<button id="odsayDevToggleButton" th:text="unknown" th:class="unknown-button">
</button>
</td>
<td>
<button id="googleDevToggleButton" th:text="unknown" th:class="unknown-button">
</button>
</td>
</tr>
<tr>
<td>Prod</td>
<td>
<button id="odsayProdToggleButton" th:text="unknown" th:class="unknown-button">
</button>
</td>
<td>
<button id="googleProdToggleButton" th:text="unknown" th:class="unknown-button">
</button>
</td>
</tr>
</tbody>
</table>
</div>
<script src="/js/api-call.js"></script>
</body>
</html>

0 comments on commit 381f6ba

Please sign in to comment.