# π 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
| μ½κ΅λͺ | μ¬μ μλ²νΈ | λ΄λΉμ | μ°κ²°λ λ¨Έμ | μν | λ§μ§λ§ μ μ | μ‘μ |
|---|---|---|---|---|---|---|
|
{{ pharmacy.pharmacy_name }} {{ pharmacy.address }} |
{{ pharmacy.business_number }} |
{{ pharmacy.manager_name }} {{ pharmacy.phone }} |
{{ pharmacy.machine_count }}λ | {% if pharmacy.is_online %} π’ μ¨λΌμΈ {% else %} π΄ μ€νλΌμΈ {% endif %} | {{ pharmacy.last_seen_humanized }} |
{{ machine.ipv4 }}
νλμ¨μ΄ μ λ³΄κ° λ±λ‘λμ§ μμμ΅λλ€.
νλμ¨μ΄ μ 보 λ±λ‘ {% endif %}