
오늘은 DB 에 저장된 데이터를 시각적으로 표현하는 HTML 문서를 작성하고, DB의 데이터들을 WAS 에 적용하여 배포하는 과정을 진행한다. 먼저 데스크탑을 실행하고 DB 에 데이터가 잘 Insert 되는지 확인하기 위해 부팅 후 15분을 기다린다.
1. 기존 작성 프로세스의 정상 작동 확인하기

부팅 후 15분 정도를 기다리고, NAS 에 ssh 접속 후 DB table 을 확인하니 오늘 날짜 ( 5월 22일) 로 데이터가 추가된 것을 확인할 수 있었다.
2. 백앤드 디렉터리 구조 작성하기
monitoring_web/
├── app.py ← Flask 메인 파일
├── templates/
│ └── dashboard.html ← HTML 템플릿
└── static/
└── etc.. ← 기타 필요한 문서, 이미지 파일 등
Java 와 Python 중 어떤 언어로 백엔드를 구축할 까 고민하다가, spring boot 는 경험해본 적이 있기 때문에 python 으로 실습해보기로 마음 먹었다.
그러나 백엔드 구축을 위해서 python 언어로는 Django 라는 프레임워크를 쓰는 것으로 알고 있었는데, 알아보니 Flask 라는 프레임 워크도 있고, 작은 프로젝트에서는 Flask 를 사용할 것을 추천하였다.
잘 모르는 프레임워크들 이기 때문에 검색하고 조사해서 차이점을 작성해봤다.
| 항목 | Flask | Django |
| 성격 | 초경량, 필요한 기능만 추가 | 대규모 풀스택 프레임워크 |
| 사용 목적 | 간단한 API, 대시보드, 모니터링 시스템 | 게시판, 쇼핑몰, 관리자 페이지 등 복잡한 웹 서비스 |
| 설정 | 매우 간단 (파이썬 파일 하나로 가능) | 프로젝트 구조 복잡, 설정 많음 |
| 학습 곡선 | 낮음 | 높음 |
3. html 파일 만들기 (뼈대)
내 시스템 정보들이 들어갈 뼈대를 만든다. 웹페이지를 만드는 것이고, 일단 예시를 보기위해 더미 데이터를 넣어 만들었다.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8" />
<title>시스템 모니터링 대시보드</title>
<!-- Chart.js CDN -->
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.0/dist/chart.umd.min.js"></script>
<style>
:root{
--bg:#121212;--panel:#1e1e1e;--panel2:#2c2c2c;--txt:#e0e0e0;--accent:#90caf9;
}
*{box-sizing:border-box}
body{margin:0;font-family:'Segoe UI',sans-serif;background:var(--bg);color:var(--txt);padding:2rem;line-height:1.4}
h2{color:var(--accent);margin:0 0 1rem 0;text-align:center}
/* 레이아웃 */
.grid{display:grid;grid-template-columns:1fr 320px;gap:1rem;margin-bottom:2rem}
.panel{background:var(--panel);border-radius:10px;padding:1rem;box-shadow:0 0 10px #0008}
/* 표 */
table{width:100%;border-collapse:collapse;border-radius:8px;overflow:hidden;background:var(--panel)}
th,td{padding:.7rem .9rem;text-align:center;border-bottom:1px solid #333}
th{background:var(--panel2);color:var(--accent)}
tr:hover{background:#2a2a2a}
tbody tr:last-child td{border:none}
/* GPU 카드 */
.gpu-card{display:grid;gap:1rem}
canvas{width:100%!important;height:100%!important}
</style>
</head>
<body>
<h2>시스템 모니터링 대시보드</h2>
<!-- 그래프 + GPU 카드 -->
<div class="grid">
<!-- 꺾은선 그래프 패널 -->
<div class="panel">
<canvas id="lineChart"></canvas>
</div>
<!-- GPU 시각화 패널 -->
<div class="panel gpu-card">
<canvas id="gpuDonut"></canvas>
<canvas id="gpuTempGauge"></canvas>
</div>
</div>
<!-- 데이터 테이블 -->
<div class="panel">
<table>
<thead>
<tr>
<th>시간</th><th>CPU (%)</th><th>Mem (Used/Total)</th><th>Disk (%)</th>
<th>In(Bytes)</th><th>Out(Bytes)</th><th>GPU (%)</th><th>GPU Mem(MB)</th><th>GPU Temp(°C)</th>
</tr>
</thead>
<tbody id="dataBody"></tbody>
</table>
</div>
<script>
/* ---------- 더미 데이터 ---------- */
const dummy = [
{t:"2025-05-19 00:15",cpu:21,memU:6400,memT:32794,disk:15,inB:51583321,outB:15918320,gpu:3,gMem:599,gTemp:48},
{t:"2025-05-19 00:00",cpu:24,memU:6543,memT:32794,disk:15,inB:51557106,outB:15836167,gpu:1,gMem:598,gTemp:49},
{t:"2025-05-18 23:45",cpu:31,memU:6237,memT:32794,disk:14,inB:51234800,outB:15400000,gpu:2,gMem:580,gTemp:47},
{t:"2025-05-18 23:30",cpu:17,memU:6002,memT:32794,disk:14,inB:50980000,outB:15210000,gpu:0,gMem:550,gTemp:46},
{t:"2025-05-18 23:15",cpu:28,memU:6100,memT:32794,disk:14,inB:50700000,outB:15000000,gpu:4,gMem:560,gTemp:48}
].reverse(); // 시간순 정렬
/* ---------- 테이블 삽입 ---------- */
const tbody=document.getElementById('dataBody');
dummy.slice().reverse().forEach(r=>{
const tr=document.createElement('tr');
tr.innerHTML=`<td>${r.t}</td><td>${r.cpu}</td><td>${r.memU} / ${r.memT}</td><td>${r.disk}</td>
<td>${r.inB}</td><td>${r.outB}</td><td>${r.gpu}</td><td>${r.gMem}</td><td>${r.gTemp}</td>`;
tbody.appendChild(tr);
});
/* ---------- 꺾은선 그래프 ---------- */
const ctxL=document.getElementById('lineChart');
new Chart(ctxL,{
type:'line',
data:{
labels:dummy.map(d=>d.t),
datasets:[
{label:'CPU %',data:dummy.map(d=>d.cpu),borderWidth:2,tension:.3},
{label:'Disk %',data:dummy.map(d=>d.disk),borderWidth:2,tension:.3},
{label:'Mem Used (GB)',data:dummy.map(d=> (d.memU/1024).toFixed(1) ),borderWidth:2,tension:.3}
]
},
options:{
responsive:true,
plugins:{legend:{labels:{color:'#fff'}},tooltip:{mode:'index'}},
scales:{x:{ticks:{color:'#ccc'}},y:{ticks:{color:'#ccc'},grid:{color:'#333'}}}
}
});
/* ---------- GPU 도넛 (Utilization) ---------- */
const ctxD=document.getElementById('gpuDonut');
new Chart(ctxD,{
type:'doughnut',
data:{
labels:['사용','여유'],
datasets:[{
data:[ dummy[0].gpu, 100-dummy[0].gpu ],
borderWidth:0
}]
},
options:{
cutout:'70%',
plugins:{legend:{display:false}},
}
});
/* ---------- GPU 게이지 (온도) ---------- */
const ctxG=document.getElementById('gpuTempGauge');
new Chart(ctxG,{
type:'doughnut',
data:{
labels:['Temp',''],
datasets:[{
data:[ dummy[0].gTemp, 100-dummy[0].gTemp ],
backgroundColor:['#ff6b6b','#444'],
borderWidth:0
}]
},
options:{
rotation:-90,
circumference:180,
cutout:'80%',
plugins:{legend:{display:false}},
}
});
</script>
</body>
</html>

4. NAS 의 배신
NAS 에서 Python 을 제공하기 때문에 Flask 프레임워크로 백엔드를 구현하려 했으나, 저장공간이 부족하다는 말도 안되는 이유로 패키지 설치가 되지 않는 문제가 발생했다. 하지만 이미 나는 라즈베리파이를 구매하였고, 아직은 OS 는 설치하지 않았기 때문에, 먼저 데스크탑에서 백앤드를 구현하고 제대로 동작하는지 확인한 후에, 라즈베리파이에 Linux 를 설치하고, 백앤드 기능을 옮기도록 하겠다.
일단은 Desktop 에 아래 디렉터리 구조대로 구성한다.
monitoring_web/
├── webApplication.py ← Flask 메인 파일
├── templates/
│ └── dashboard.html ← HTML 템플릿
└── static/
└── etc.. ← 기타 필요한 문서, 이미지 파일 등

5. webApplication.py 작성 및 실행
5-1. 데이터베이스와 html 파일을 연결해주어 동적인 애플리케이션을 만들어주는 파이썬 파일(webApplication.py)을 작성한다.
from flask import Flask, render_template, jsonify
import pymysql
app = Flask(__name__)
# DB 연결 설정
db_config = {
'host': '192.168.0.20',
'user': 'root',
'password': '실제비밀번호',
'database': 'monitoring',
'charset': 'utf8'
}
@app.route('/')
def dashboard():
return render_template('dashboard.html')
@app.route('/api/data')
def get_data():
conn = pymysql.connect(**db_config)
cur = conn.cursor()
cur.execute("""
SELECT timestamp, cpu_load, mem_used, mem_total, disk_used,
net_in, net_out, gpu_util, gpu_mem, gpu_temp
FROM sys_metrics
ORDER BY timestamp DESC
LIMIT 10
""")
rows = cur.fetchall()
conn.close()
data = []
for row in reversed(rows):
data.append({
't': row[0].strftime('%Y-%m-%d %H:%M'),
'cpu': row[1],
'memU': row[2],
'memT': row[3],
'disk': row[4],
'inB': row[5],
'outB': row[6],
'gpu': row[7],
'gMem': row[8],
'gTemp': row[9]
})
return jsonify(data)
if __name__ == '__main__':
app.run(debug=True, host='0.0.0.0')
이 설정파일에서 가장 중요한 것은 DB에 데이터를 잘 받와와야 하기 때문에, DB명, user name, password, table 이름이 정확히 일치해야 한다.
5-2. html 파일에서 더미 데이터 부분을 실제 데이터로 받을 수 있도록 수정한다.
<script>
/* ---------- 데이터 받아오기 및 처리 ---------- */
fetch('/api/data')
.then(res => res.json())
.then(dummy => {
dummy = dummy.reverse(); // 최신이 위로
/* ---------- 테이블 삽입 ---------- */
const tbody = document.getElementById('dataBody');
dummy.forEach(r => {
const tr = document.createElement('tr');
tr.innerHTML = `<td>${r.t}</td><td>${r.cpu}</td><td>${r.memU} / ${r.memT}</td><td>${r.disk}</td>
<td>${r.inB}</td><td>${r.outB}</td><td>${r.gpu}</td><td>${r.gMem}</td><td>${r.gTemp}</td>`;
tbody.appendChild(tr);
});
/* ---------- 꺾은선 그래프 ---------- */
const ctxL = document.getElementById('lineChart');
new Chart(ctxL, {
type: 'line',
data: {
labels: dummy.map(d => d.t),
datasets: [
{ label: 'CPU %', data: dummy.map(d => d.cpu), borderWidth: 2, tension: .3 },
{ label: 'Disk %', data: dummy.map(d => d.disk), borderWidth: 2, tension: .3 },
{ label: 'Mem Used (GB)', data: dummy.map(d => (d.memU / 1024).toFixed(1)), borderWidth: 2, tension: .3 }
]
},
options: {
responsive: true,
plugins: { legend: { labels: { color: '#fff' } }, tooltip: { mode: 'index' } },
scales: { x: { ticks: { color: '#ccc' } }, y: { ticks: { color: '#ccc' }, grid: { color: '#333' } } }
}
});
/* ---------- GPU 도넛 (Utilization) ---------- */
const ctxD = document.getElementById('gpuDonut');
new Chart(ctxD, {
type: 'doughnut',
data: {
labels: ['사용', '여유'],
datasets: [{
data: [dummy[0].gpu, 100 - dummy[0].gpu],
borderWidth: 0
}]
},
options: {
cutout: '70%',
plugins: { legend: { display: false } },
}
});
/* ---------- GPU 게이지 (온도) ---------- */
const ctxG = document.getElementById('gpuTempGauge');
new Chart(ctxG, {
type: 'doughnut',
data: {
labels: ['Temp', ''],
datasets: [{
data: [dummy[0].gTemp, 100 - dummy[0].gTemp],
backgroundColor: ['#ff6b6b', '#444'],
borderWidth: 0
}]
},
options: {
rotation: -90,
circumference: 180,
cutout: '80%',
plugins: { legend: { display: false } },
}
});
});
</script>
5-3 webApplication.py 실행
Flask 역시 가상환경 (venv) 에서 설치하고 실행해야 한다.
먼저 가상 모드를 활성화 한다.
source ~/venv-monitoring/bin/activate
필요한 라이브러리(모듈)을 설치한다.
pip install flask
이제 실행한다.
python webApplication.py

웹 페이지 기동이 시작되었다.
6. 웹 페이지 접속 및 마무리
localhost 인 127.0.0.1 의 5000 포트로 설정했기에, 웹 브라우저에서 127.0.0.1:5000 혹은 localhost:5000 을 입력한다.
터미널에서부터 바로 반응이 온다. 현재 11시 02분의 데이터를 받았다고 보여준다.

브라우저에 데이터들이 잘 보이고 있다.

<실제 DB 와 Web 의 데이터 비교>


DB의 데이터가 잘 반영되고 있지만,web 그래픽이 다소 아쉽고 가독성이 좋지 않다고 생각된다.
일단 DB의 데이터를 성공적으로 가져와서 Web 으로 보여주는 과정은 성공하였으므로, 가시성이 좋은 글씨와 디자인을 추가하여 모니터링 대시보드를 수정하고, 이 백앤드 구조를 추후 라즈베리파이에 옮기도록 하자!
'Project > 개인 Project' 카테고리의 다른 글
| [Project] ChatGPT + 개인 DB 기반 운동기록 API 서버 구축기 -2- (0) | 2025.07.15 |
|---|---|
| [Project] ChatGPT + 개인 DB 기반 운동기록 API 서버 구축기 (1) | 2025.07.14 |
| [개인 Project] NAS 를 이용한 개인 모니터링 시스템 구축하기 -2 편 (데이터 DB 저장) (4) | 2025.05.20 |
| [개인 Project] NAS 를 이용한 개인 모니터링 시스템 구축하기 -1 편 (데이터 추출) (0) | 2025.05.19 |
| [Project] API 응답문 데이터 통일화 및 알고리즘 적용 마무리 (0) | 2025.03.28 |