📋 기획 및 설계: - PharmQ SaaS 서비스 기획서 작성 - 구독 서비스 라인업 정의 (클라우드PC, AI CCTV, CRM) - DB 스키마 설계 및 API 아키텍처 설계 🗄️ 데이터베이스 구조: - service_products: 서비스 상품 마스터 테이블 - pharmacy_subscriptions: 약국별 구독 현황 테이블 - subscription_usage_logs: 서비스 이용 로그 테이블 - billing_history: 결제 이력 테이블 - 샘플 데이터 자동 생성 (21개 구독, 월 118만원 매출) 🔧 백엔드 API 구현: - 구독 현황 통계 API (/api/subscriptions/stats) - 약국별 구독 조회 API (/api/pharmacies/subscriptions) - 구독 상세 정보 API (/api/pharmacy/{id}/subscriptions) - 구독 생성/해지 API (/api/subscriptions) 🖥️ 프론트엔드 UI 구현: - 대시보드 구독 현황 카드 (월 매출, 구독 수, 구독률 등) - 약국 목록에 구독 상태 아이콘 및 월 구독료 표시 - 약국 상세 페이지 구독 서비스 섹션 추가 - 실시간 구독 생성/해지 기능 구현 ✨ 주요 특징: - 서비스별 색상 코딩 및 이모지 아이콘 시스템 - 실시간 업데이트 (구독 생성/해지 즉시 반영) - 반응형 디자인 (모바일/태블릿 최적화) - 툴팁 기반 상세 정보 표시 📊 현재 구독 현황: - 총 월 매출: ₩1,180,000 - 구독 약국: 10/14개 (71.4%) - AI CCTV: 6개 약국, CRM: 10개 약국, 클라우드PC: 5개 약국 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
508 lines
22 KiB
Markdown
508 lines
22 KiB
Markdown
# 🌐 Flask + Jinja2 Headplane 고도화 관리자 페이지 개발 계획
|
|
|
|
## 📋 프로젝트 개요
|
|
|
|
### 개발 목표
|
|
기존 Headplane UI를 포크하지 않고, **Flask + Jinja2**로 별도 관리자 페이지를 구축하여 Headscale 데이터베이스와 직접 연동하는 고도화된 관리 시스템 개발
|
|
|
|
### 핵심 컨셉
|
|
- **기존 Headplane**: 기본 기능 유지 (3000번 포트)
|
|
- **Flask Admin**: 고도화된 관리 기능 (5000번 포트)
|
|
- **데이터 통합**: 동일한 SQLite DB 공유로 실시간 동기화
|
|
- **팜큐 특화**: 약국 관리에 최적화된 UI/UX
|
|
|
|
## 🏗️ 아키텍처 설계
|
|
|
|
### 시스템 구조
|
|
```
|
|
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
|
|
│ Headplane UI │ │ Flask Admin │ │ Headscale API │
|
|
│ (포트: 3000) │ │ (포트: 5000) │ │ (포트: 8070) │
|
|
│ 기본 기능 │ │ 고도화 기능 │ │ 백엔드 API │
|
|
└─────────────────┘ └──────────────────┘ └─────────────────┘
|
|
│ │ │
|
|
└───────────────────────┼───────────────────────┘
|
|
│
|
|
┌──────────────────┐
|
|
│ SQLite Database │
|
|
│ (공유 데이터) │
|
|
└──────────────────┘
|
|
```
|
|
|
|
### 포트 구성
|
|
- **Headscale API**: 8070 (기존 유지)
|
|
- **Headplane UI**: 3000 (기존 유지)
|
|
- **Flask Admin**: 5000 (신규 추가)
|
|
|
|
## 📂 Flask 프로젝트 구조
|
|
|
|
```
|
|
farmq-admin/
|
|
├── app.py # Flask 애플리케이션 메인
|
|
├── config.py # 설정 파일
|
|
├── requirements.txt # Python 의존성
|
|
├── models/
|
|
│ ├── __init__.py
|
|
│ ├── headscale_models.py # SQLAlchemy 모델 (재사용)
|
|
│ └── pharmacy_models.py # 팜큐 확장 모델
|
|
├── routes/
|
|
│ ├── __init__.py
|
|
│ ├── dashboard.py # 메인 대시보드
|
|
│ ├── pharmacy.py # 약국 관리
|
|
│ ├── machines.py # 머신 관리 (고도화)
|
|
│ ├── users.py # 사용자 관리 (고도화)
|
|
│ ├── monitoring.py # 실시간 모니터링
|
|
│ └── api.py # REST API 엔드포인트
|
|
├── templates/
|
|
│ ├── base.html # 기본 레이아웃
|
|
│ ├── dashboard/
|
|
│ │ ├── index.html # 메인 대시보드
|
|
│ │ └── stats.html # 통계 대시보드
|
|
│ ├── pharmacy/
|
|
│ │ ├── list.html # 약국 목록
|
|
│ │ ├── detail.html # 약국 상세
|
|
│ │ ├── create.html # 약국 등록
|
|
│ │ └── edit.html # 약국 수정
|
|
│ ├── machines/
|
|
│ │ ├── list.html # 머신 목록 (고도화)
|
|
│ │ ├── detail.html # 머신 상세 (하드웨어 정보)
|
|
│ │ └── monitoring.html # 실시간 모니터링
|
|
│ └── users/
|
|
│ ├── list.html # 사용자 목록 (약국 정보 포함)
|
|
│ └── detail.html # 사용자 상세
|
|
├── static/
|
|
│ ├── css/
|
|
│ │ ├── bootstrap.min.css # Bootstrap 5
|
|
│ │ ├── custom.css # 커스텀 스타일
|
|
│ │ └── dashboard.css # 대시보드 전용 스타일
|
|
│ ├── js/
|
|
│ │ ├── bootstrap.min.js # Bootstrap JS
|
|
│ │ ├── chart.min.js # Chart.js 라이브러리
|
|
│ │ ├── dashboard.js # 대시보드 JS
|
|
│ │ └── monitoring.js # 실시간 모니터링 JS
|
|
│ └── img/
|
|
│ ├── logo.png # 팜큐 로고
|
|
│ └── icons/ # 아이콘들
|
|
├── utils/
|
|
│ ├── __init__.py
|
|
│ ├── database.py # DB 연결 유틸리티
|
|
│ ├── auth.py # 인증 관련
|
|
│ ├── monitoring.py # 모니터링 데이터 수집
|
|
│ └── proxmox.py # Proxmox API 연동
|
|
└── docker/
|
|
├── Dockerfile # Flask 앱용 도커파일
|
|
└── docker-compose.yml # 통합 컨테이너 구성
|
|
```
|
|
|
|
## 🎨 UI/UX 설계
|
|
|
|
### 디자인 컨셉
|
|
- **Modern Dashboard**: Bootstrap 5 기반 반응형 디자인
|
|
- **팜큐 브랜딩**: 약국 관리에 특화된 색상/아이콘 사용
|
|
- **Korean-First**: 한국어 우선 인터페이스
|
|
- **Mobile Responsive**: 모바일/태블릿 완벽 지원
|
|
|
|
### 메인 대시보드 레이아웃
|
|
```
|
|
┌────────────────────────────────────────────────────────────┐
|
|
│ 🏥 팜큐 약국 관리 시스템 [관리자: admin] [로그아웃] │
|
|
├────────────────────────────────────────────────────────────┤
|
|
│ [대시보드] [약국관리] [머신관리] [사용자관리] [모니터링] [설정] │
|
|
├────────────────────────────────────────────────────────────┤
|
|
│ 📊 전체 현황 │
|
|
│ ┌──────────┬──────────┬──────────┬──────────────────────┐ │
|
|
│ │총 약국 수 │온라인 │오프라인 │평균 CPU 온도 │ │
|
|
│ │ 100 │ 95 │ 5 │ 62°C │ │
|
|
│ └──────────┴──────────┴──────────┴──────────────────────┘ │
|
|
│ │
|
|
│ 🚨 실시간 알림 📈 성능 차트 │
|
|
│ ┌─────────────────────────┐ ┌────────────────────┐ │
|
|
│ │• 부산해운약국: CPU 85°C │ │ [CPU 사용률 차트] │ │
|
|
│ │• 대구중앙약국: 디스크95% │ │ [메모리 사용률] │ │
|
|
│ │• 서울약국: 연결 끊김 │ │ [네트워크 트래픽] │ │
|
|
│ └─────────────────────────┘ └────────────────────┘ │
|
|
│ │
|
|
│ 📋 약국별 상태 (실시간) │
|
|
│ ┌─────────────┬────────┬────────┬────────┬──────────────┐ │
|
|
│ │약국명 │상태 │CPU온도 │메모리 │마지막 접속 │ │
|
|
│ ├─────────────┼────────┼────────┼────────┼──────────────┤ │
|
|
│ │서울중앙약국 │🟢 온라인│ 65°C │ 80% │ 2분 전 │ │
|
|
│ │부산해운약국 │🟡 경고 │ 85°C │ 60% │ 5분 전 │ │
|
|
│ │대구중앙약국 │🔴 위험 │ 70°C │ 95% │ 10분 전 │ │
|
|
│ └─────────────┴────────┴────────┴────────┴──────────────┘ │
|
|
└────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
## 🎯 핵심 기능 명세
|
|
|
|
### 1. 통합 대시보드
|
|
```python
|
|
# routes/dashboard.py
|
|
@app.route('/')
|
|
def dashboard():
|
|
stats = {
|
|
'total_pharmacies': get_pharmacy_count(),
|
|
'online_machines': get_online_machines_count(),
|
|
'offline_machines': get_offline_machines_count(),
|
|
'avg_cpu_temp': get_average_cpu_temperature(),
|
|
'alerts': get_active_alerts(),
|
|
'recent_activities': get_recent_activities()
|
|
}
|
|
return render_template('dashboard/index.html', stats=stats)
|
|
```
|
|
|
|
### 2. 약국 관리 시스템
|
|
#### 2-1. 약국 목록 페이지
|
|
```html
|
|
<!-- templates/pharmacy/list.html -->
|
|
<div class="container-fluid">
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-header d-flex justify-content-between">
|
|
<h5>🏥 약국 관리</h5>
|
|
<button class="btn btn-primary" onclick="location.href='/pharmacy/create'">
|
|
<i class="fas fa-plus"></i> 새 약국 등록
|
|
</button>
|
|
</div>
|
|
<div class="card-body">
|
|
<table class="table table-hover">
|
|
<thead>
|
|
<tr>
|
|
<th>약국명</th>
|
|
<th>사업자번호</th>
|
|
<th>담당자</th>
|
|
<th>연결된 머신</th>
|
|
<th>상태</th>
|
|
<th>마지막 접속</th>
|
|
<th>액션</th>
|
|
</tr>
|
|
</thead>
|
|
<tbody>
|
|
{% for pharmacy in pharmacies %}
|
|
<tr>
|
|
<td>
|
|
<strong>{{ pharmacy.pharmacy_name }}</strong><br>
|
|
<small class="text-muted">{{ pharmacy.address }}</small>
|
|
</td>
|
|
<td>{{ pharmacy.business_number }}</td>
|
|
<td>
|
|
{{ pharmacy.manager_name }}<br>
|
|
<small class="text-muted">{{ pharmacy.phone }}</small>
|
|
</td>
|
|
<td>
|
|
<span class="badge bg-info">{{ pharmacy.machine_count }}대</span>
|
|
</td>
|
|
<td>
|
|
{% if pharmacy.is_online %}
|
|
<span class="badge bg-success">🟢 온라인</span>
|
|
{% else %}
|
|
<span class="badge bg-danger">🔴 오프라인</span>
|
|
{% endif %}
|
|
</td>
|
|
<td>{{ pharmacy.last_seen_humanized }}</td>
|
|
<td>
|
|
<div class="btn-group btn-group-sm">
|
|
<a href="/pharmacy/{{ pharmacy.id }}" class="btn btn-outline-primary">상세</a>
|
|
<a href="/pharmacy/{{ pharmacy.id }}/edit" class="btn btn-outline-warning">수정</a>
|
|
<a href="/pharmacy/{{ pharmacy.id }}/monitoring" class="btn btn-outline-info">모니터링</a>
|
|
</div>
|
|
</td>
|
|
</tr>
|
|
{% endfor %}
|
|
</tbody>
|
|
</table>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
### 3. 고도화된 머신 관리
|
|
#### 3-1. 머신 상세 페이지 (하드웨어 정보 포함)
|
|
```html
|
|
<!-- templates/machines/detail.html -->
|
|
<div class="container-fluid">
|
|
<div class="row">
|
|
<!-- 기본 정보 -->
|
|
<div class="col-md-6">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5>🖥️ 머신 기본 정보</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
<dl class="row">
|
|
<dt class="col-sm-4">머신명:</dt>
|
|
<dd class="col-sm-8">{{ machine.given_name }}</dd>
|
|
|
|
<dt class="col-sm-4">호스트명:</dt>
|
|
<dd class="col-sm-8">{{ machine.hostname }}</dd>
|
|
|
|
<dt class="col-sm-4">IP 주소:</dt>
|
|
<dd class="col-sm-8">
|
|
<code>{{ machine.ipv4 }}</code>
|
|
</dd>
|
|
|
|
<dt class="col-sm-4">소속 약국:</dt>
|
|
<dd class="col-sm-8">
|
|
<a href="/pharmacy/{{ machine.pharmacy.id }}">
|
|
{{ machine.pharmacy.pharmacy_name }}
|
|
</a>
|
|
</dd>
|
|
|
|
<dt class="col-sm-4">마지막 접속:</dt>
|
|
<dd class="col-sm-8">
|
|
{% if machine.is_online() %}
|
|
<span class="badge bg-success">🟢 온라인</span>
|
|
{% else %}
|
|
<span class="badge bg-danger">🔴 {{ machine.last_seen_humanized }}</span>
|
|
{% endif %}
|
|
</dd>
|
|
</dl>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 하드웨어 사양 -->
|
|
<div class="col-md-6">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5>⚙️ 하드웨어 사양</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
{% if machine.specs %}
|
|
<dl class="row">
|
|
<dt class="col-sm-4">CPU:</dt>
|
|
<dd class="col-sm-8">{{ machine.specs.cpu_model }} ({{ machine.specs.cpu_cores }}코어)</dd>
|
|
|
|
<dt class="col-sm-4">RAM:</dt>
|
|
<dd class="col-sm-8">{{ machine.specs.ram_gb }}GB</dd>
|
|
|
|
<dt class="col-sm-4">Storage:</dt>
|
|
<dd class="col-sm-8">{{ machine.specs.storage_gb }}GB</dd>
|
|
|
|
<dt class="col-sm-4">GPU:</dt>
|
|
<dd class="col-sm-8">{{ machine.specs.gpu_model or '없음' }}</dd>
|
|
</dl>
|
|
{% else %}
|
|
<p class="text-muted">하드웨어 정보가 등록되지 않았습니다.</p>
|
|
<a href="/machines/{{ machine.id }}/specs" class="btn btn-outline-primary btn-sm">
|
|
하드웨어 정보 등록
|
|
</a>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- 실시간 모니터링 -->
|
|
<div class="row mt-4">
|
|
<div class="col-12">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h5>📊 실시간 모니터링</h5>
|
|
</div>
|
|
<div class="card-body">
|
|
{% if machine.latest_monitoring %}
|
|
<div class="row">
|
|
<div class="col-md-3">
|
|
<div class="text-center">
|
|
<canvas id="cpuChart" width="100" height="100"></canvas>
|
|
<h6 class="mt-2">CPU 사용률</h6>
|
|
<span class="h4 text-primary">{{ machine.latest_monitoring.cpu_usage }}%</span>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="text-center">
|
|
<canvas id="memoryChart" width="100" height="100"></canvas>
|
|
<h6 class="mt-2">메모리 사용률</h6>
|
|
<span class="h4 text-info">{{ machine.latest_monitoring.memory_usage }}%</span>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="text-center">
|
|
<div class="display-4 text-warning">🌡️</div>
|
|
<h6>CPU 온도</h6>
|
|
<span class="h4 text-warning">{{ machine.latest_monitoring.cpu_temperature }}°C</span>
|
|
</div>
|
|
</div>
|
|
<div class="col-md-3">
|
|
<div class="text-center">
|
|
<div class="display-4 text-success">💾</div>
|
|
<h6>디스크 사용률</h6>
|
|
<span class="h4 text-success">{{ machine.latest_monitoring.disk_usage }}%</span>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% else %}
|
|
<div class="alert alert-info">
|
|
<i class="fas fa-info-circle"></i>
|
|
아직 모니터링 데이터가 없습니다. 잠시 후 다시 확인해주세요.
|
|
</div>
|
|
{% endif %}
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<script>
|
|
// 실시간 업데이트를 위한 JavaScript
|
|
function updateMonitoring() {
|
|
fetch(`/api/machines/{{ machine.id }}/monitoring`)
|
|
.then(response => response.json())
|
|
.then(data => {
|
|
// 차트 업데이트 로직
|
|
updateCharts(data);
|
|
});
|
|
}
|
|
|
|
// 5초마다 업데이트
|
|
setInterval(updateMonitoring, 5000);
|
|
</script>
|
|
```
|
|
|
|
### 4. 실시간 모니터링 시스템
|
|
```python
|
|
# routes/monitoring.py
|
|
from flask import Blueprint, jsonify
|
|
from utils.monitoring import collect_monitoring_data
|
|
from utils.proxmox import ProxmoxAPI
|
|
|
|
monitoring_bp = Blueprint('monitoring', __name__)
|
|
|
|
@monitoring_bp.route('/api/monitoring/realtime')
|
|
def realtime_monitoring():
|
|
"""실시간 모니터링 데이터 API"""
|
|
data = {
|
|
'total_machines': get_total_machines(),
|
|
'online_count': get_online_machines_count(),
|
|
'alerts': get_active_alerts(),
|
|
'performance': get_performance_summary()
|
|
}
|
|
return jsonify(data)
|
|
|
|
@monitoring_bp.route('/api/machines/<int:machine_id>/monitoring')
|
|
def machine_monitoring(machine_id):
|
|
"""특정 머신 모니터링 데이터"""
|
|
monitoring_data = collect_monitoring_data(machine_id)
|
|
return jsonify(monitoring_data)
|
|
```
|
|
|
|
## 🔧 기술 스택
|
|
|
|
### Backend
|
|
- **Flask 3.0**: 웹 프레임워크
|
|
- **SQLAlchemy 2.0**: ORM (기존 모델 재사용)
|
|
- **Jinja2**: 템플릿 엔진
|
|
- **Flask-Login**: 사용자 인증
|
|
- **APScheduler**: 백그라운드 작업 (모니터링 데이터 수집)
|
|
|
|
### Frontend
|
|
- **Bootstrap 5**: CSS 프레임워크
|
|
- **Chart.js**: 차트 라이브러리
|
|
- **Font Awesome**: 아이콘
|
|
- **jQuery**: DOM 조작
|
|
- **Socket.io**: 실시간 통신
|
|
|
|
### 데이터베이스
|
|
- **SQLite**: 기존 Headscale DB 공유
|
|
- **확장 테이블**: PharmacyInfo, MachineSpecs, MonitoringData
|
|
|
|
### 배포
|
|
- **Docker**: 컨테이너화
|
|
- **Nginx**: 리버스 프록시 (옵션)
|
|
|
|
## 📅 개발 로드맵
|
|
|
|
### Phase 1: 기본 프레임워크 구축 (1-2일)
|
|
- [ ] Flask 애플리케이션 기본 구조 생성
|
|
- [ ] SQLAlchemy 모델 연동 (기존 모델 재사용)
|
|
- [ ] Bootstrap 기반 기본 템플릿 구성
|
|
- [ ] 라우팅 구조 설계
|
|
|
|
### Phase 2: 핵심 기능 구현 (3-4일)
|
|
- [ ] 메인 대시보드 구현
|
|
- [ ] 약국 관리 CRUD 기능
|
|
- [ ] 머신 관리 고도화 (하드웨어 정보 포함)
|
|
- [ ] 사용자 관리 확장 (약국 정보 연동)
|
|
|
|
### Phase 3: 실시간 기능 (2-3일)
|
|
- [ ] 모니터링 데이터 수집 시스템
|
|
- [ ] 실시간 차트 및 알림
|
|
- [ ] WebSocket 기반 라이브 업데이트
|
|
- [ ] Proxmox API 연동
|
|
|
|
### Phase 4: 통합 및 최적화 (2-3일)
|
|
- [ ] 기존 Headplane과 데이터 동기화 테스트
|
|
- [ ] Docker 컨테이너화
|
|
- [ ] 성능 최적화
|
|
- [ ] 사용자 테스트 및 피드백 반영
|
|
|
|
### Phase 5: 배포 및 운영 (1-2일)
|
|
- [ ] Docker Compose 통합 구성
|
|
- [ ] 프로덕션 배포
|
|
- [ ] 모니터링 및 로깅 설정
|
|
- [ ] 사용자 교육 자료 작성
|
|
|
|
## 💰 예상 리소스
|
|
|
|
### 개발 시간
|
|
- **총 개발 기간**: 8-12일
|
|
- **개발자**: 1명 (풀타임)
|
|
- **일일 작업량**: 6-8시간
|
|
|
|
### 기술적 요구사항
|
|
- **Python 3.8+**
|
|
- **메모리**: 최소 512MB (Flask 앱)
|
|
- **디스크**: 추가 100MB (정적 파일 포함)
|
|
|
|
## 🚀 시작하기
|
|
|
|
### 1단계: 개발 환경 준비
|
|
```bash
|
|
# Flask 프로젝트 디렉터리 생성
|
|
mkdir farmq-admin
|
|
cd farmq-admin
|
|
|
|
# Python 가상환경 생성
|
|
python3 -m venv flask-venv
|
|
source flask-venv/bin/activate
|
|
|
|
# 필수 패키지 설치
|
|
pip install flask sqlalchemy jinja2 flask-login apscheduler
|
|
```
|
|
|
|
### 2단계: 기본 구조 생성
|
|
```bash
|
|
# 프로젝트 구조 생성
|
|
mkdir -p {routes,templates,static/{css,js,img},utils,models}
|
|
touch app.py config.py requirements.txt
|
|
```
|
|
|
|
### 3단계: 첫 번째 구현
|
|
- 기본 Flask 앱 생성
|
|
- SQLAlchemy 연동
|
|
- 간단한 대시보드 페이지
|
|
|
|
## 🎯 성공 지표
|
|
|
|
### 기능적 목표
|
|
- [ ] 100개 약국 데이터 완벽 관리
|
|
- [ ] 실시간 모니터링 정확도 95% 이상
|
|
- [ ] 기존 Headplane과 데이터 100% 동기화
|
|
- [ ] 페이지 로딩 시간 2초 이내
|
|
|
|
### 사용성 목표
|
|
- [ ] 관리 업무 효율성 70% 향상
|
|
- [ ] 모바일 접근성 완벽 지원
|
|
- [ ] 한국어 UI 100% 완성
|
|
- [ ] 사용자 만족도 4.8/5.0 이상
|
|
|
|
이제 이 계획을 바탕으로 Flask 관리자 페이지 개발을 시작하시겠습니까?
|
|
|
|
---
|
|
**📅 작성일**: 2025-09-09
|
|
**👤 작성자**: Claude Code Assistant
|
|
**🎯 목표**: Headplane UI 고도화를 위한 Flask 기반 관리자 시스템 |