diff --git a/farmq-admin/app.py b/farmq-admin/app.py index c232a88..b4d9c91 100644 --- a/farmq-admin/app.py +++ b/farmq-admin/app.py @@ -6,6 +6,7 @@ Headscale + Headplane 고도화 관리자 페이지 from flask import Flask, render_template, jsonify, request, redirect, url_for import os +import json from datetime import datetime import uuid from config import config @@ -15,6 +16,7 @@ from utils.database_new import ( get_machine_detail, get_pharmacy_detail, get_active_alerts, sync_machines_from_headscale, sync_users_from_headscale ) +from models.farmq_models import PharmacyInfo import subprocess from utils.proxmox_client import ProxmoxClient @@ -437,6 +439,157 @@ def create_app(config_name=None): 'error': f'서버 오류: {str(e)}' }), 500 + # 사용자 관리 라우트 + @app.route('/users') + def users_list(): + """사용자 관리 페이지""" + return render_template('users/list.html') + + # 사용자 관리 API + @app.route('/api/users', methods=['GET']) + def api_get_users(): + """Headscale 사용자 목록 조회""" + try: + # Headscale CLI를 통해 사용자 목록 조회 + result = subprocess.run( + ['docker', 'exec', 'headscale', 'headscale', 'users', 'list', '-o', 'json'], + capture_output=True, + text=True, + check=True + ) + + users_data = json.loads(result.stdout) + + # FARMQ 약국 정보와 매칭 + farmq_session = get_farmq_session() + try: + pharmacies = farmq_session.query(PharmacyInfo).all() + pharmacy_map = {p.headscale_user_name: p for p in pharmacies if p.headscale_user_name} + + # 사용자별 노드 수 조회 + for user in users_data: + user_name = user.get('name', '') + + # 약국 정보 매칭 + pharmacy = pharmacy_map.get(user_name) + user['pharmacy'] = { + 'id': pharmacy.id, + 'name': pharmacy.pharmacy_name, + 'manager': pharmacy.manager_name + } if pharmacy else None + + # 해당 사용자의 노드 수 조회 + node_result = subprocess.run( + ['docker', 'exec', 'headscale', 'headscale', 'nodes', 'list', '-o', 'json'], + capture_output=True, + text=True, + check=True + ) + nodes_data = json.loads(node_result.stdout) + user['node_count'] = len([n for n in nodes_data if n.get('user', {}).get('name') == user_name]) + + return jsonify({'success': True, 'users': users_data}) + + finally: + farmq_session.close() + + except subprocess.CalledProcessError as e: + error_msg = e.stderr if e.stderr else e.stdout + return jsonify({ + 'success': False, + 'error': f'사용자 목록 조회 실패: {error_msg}' + }), 400 + + except Exception as e: + print(f"❌ 사용자 목록 조회 오류: {e}") + return jsonify({ + 'success': False, + 'error': f'서버 오류: {str(e)}' + }), 500 + + @app.route('/api/users', methods=['POST']) + def api_create_user(): + """새 Headscale 사용자 생성""" + try: + data = request.get_json() + user_name = data.get('name', '').strip() + display_name = data.get('display_name', '').strip() + email = data.get('email', '').strip() + + if not user_name: + return jsonify({ + 'success': False, + 'error': '사용자 이름은 필수입니다.' + }), 400 + + # Headscale CLI를 통해 사용자 생성 + cmd = ['docker', 'exec', 'headscale', 'headscale', 'users', 'create', user_name] + + if display_name: + cmd.extend(['-d', display_name]) + if email: + cmd.extend(['-e', email]) + + result = subprocess.run( + cmd, + capture_output=True, + text=True, + check=True + ) + + return jsonify({ + 'success': True, + 'message': f'사용자 "{user_name}"가 성공적으로 생성되었습니다.', + 'output': result.stdout + }) + + except subprocess.CalledProcessError as e: + error_msg = e.stderr if e.stderr else e.stdout + return jsonify({ + 'success': False, + 'error': f'사용자 생성 실패: {error_msg}' + }), 400 + + except Exception as e: + print(f"❌ 사용자 생성 오류: {e}") + return jsonify({ + 'success': False, + 'error': f'서버 오류: {str(e)}' + }), 500 + + @app.route('/api/users//delete', methods=['DELETE']) + def api_delete_user(user_name): + """Headscale 사용자 삭제""" + try: + # Headscale CLI를 통해 사용자 삭제 + result = subprocess.run( + ['docker', 'exec', 'headscale', 'headscale', 'users', 'destroy', + '--name', user_name, '--force'], + capture_output=True, + text=True, + check=True + ) + + return jsonify({ + 'success': True, + 'message': f'사용자 "{user_name}"가 성공적으로 삭제되었습니다.', + 'output': result.stdout + }) + + except subprocess.CalledProcessError as e: + error_msg = e.stderr if e.stderr else e.stdout + return jsonify({ + 'success': False, + 'error': f'사용자 삭제 실패: {error_msg}' + }), 400 + + except Exception as e: + print(f"❌ 사용자 삭제 오류: {e}") + return jsonify({ + 'success': False, + 'error': f'서버 오류: {str(e)}' + }), 500 + # 에러 핸들러 @app.errorhandler(404) def not_found_error(error): diff --git a/farmq-admin/templates/base.html b/farmq-admin/templates/base.html index cbc4042..295e002 100644 --- a/farmq-admin/templates/base.html +++ b/farmq-admin/templates/base.html @@ -193,7 +193,7 @@ diff --git a/farmq-admin/templates/users/list.html b/farmq-admin/templates/users/list.html new file mode 100644 index 0000000..cc48677 --- /dev/null +++ b/farmq-admin/templates/users/list.html @@ -0,0 +1,443 @@ +{% extends "base.html" %} + +{% block title %}사용자 관리 - 팜큐 약국 관리 시스템{% endblock %} + +{% block breadcrumb %} + +{% endblock %} + +{% block content %} +
+
+
+
+

+ + 사용자 관리 +

+

Headscale 네트워크 사용자 및 약국 매칭 관리

+
+
+ + +
+
+
+
+ + +
+
+
+
+
0
+
+ 총 사용자 수 +
+
+
+
+ +
+
+
+
0
+
+ 약국 연결됨 +
+
+
+
+ +
+
+
+
0
+
+ 연결 필요 +
+
+
+
+ +
+
+
+
0
+
+ 총 연결 노드 +
+
+
+
+
+ + +
+
+
+
+
+ Headscale 사용자 목록 +
+
+
+
+
+ 로딩 중... +
+

사용자 목록을 로드하고 있습니다...

+
+
+
+
+
+ + + + + + +{% endblock %} + +{% block extra_js %} + +{% endblock %} \ No newline at end of file