crmbackend/crm/views/debug_views.py
vickytechkey 4130bf88d5
All checks were successful
Deploy Beta (NATIVE) / deploy (push) Successful in 30s
feat: implement service management and diagnostic views for backend monitoring
2026-08-16 10:12:59 +05:30

291 lines
10 KiB
Python

import os
import signal
import socket
import subprocess
import shutil
import time
from django.conf import settings
from django.db import connections
from django.http import JsonResponse
from rest_framework.decorators import api_view, permission_classes
from crm.permissions import IsCRMAdmin
SERVICES = {
'crm': {
'name': 'CRM Backend',
'dir': '/home/vignesh/github/crmbackend',
'venv_python': '/home/vignesh/github/crmbackend/.venv/bin/python',
'venv_gunicorn': '/home/vignesh/github/crmbackend/.venv/bin/gunicorn',
'wsgi': 'digihoxbackend.wsgi:application',
'port': 8081,
'logs_dir': '/home/vignesh/github/crmbackend/logs',
},
'seller': {
'name': 'Seller Central Backend',
'dir': '/home/vignesh/github/seller_central_backend',
'venv_python': '/home/vignesh/github/seller_central_backend/venv/bin/python',
'venv_gunicorn': '/home/vignesh/github/seller_central_backend/venv/bin/gunicorn',
'wsgi': 'config.wsgi:application',
'port': 8080,
'logs_dir': '/home/vignesh/github/seller_central_backend/logs',
},
'customer': {
'name': 'Customer Website Backend',
'dir': '/home/vignesh/github/customerwebsitebackend',
'venv_python': '/home/vignesh/github/customerwebsitebackend/venv/bin/python',
'venv_gunicorn': '/home/vignesh/github/customerwebsitebackend/venv/bin/gunicorn',
'wsgi': 'customerwebsitebackend.wsgi:application',
'port': 8082,
'logs_dir': '/home/vignesh/github/customerwebsitebackend/logs',
}
}
def is_port_open(port):
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.settimeout(0.5)
return s.connect_ex(('127.0.0.1', port)) == 0
def find_pids_for_service(service_key):
cfg = SERVICES.get(service_key)
if not cfg:
return []
pids = []
# Search via ps command for any gunicorn or python processes running in that directory
try:
cmd = f"ps -eo pid,cmd | grep '{cfg['dir']}' | grep -v grep"
out = subprocess.check_output(cmd, shell=True, text=True)
for line in out.strip().split('\n'):
parts = line.strip().split(None, 1)
if len(parts) >= 1:
pids.append(int(parts[0]))
except subprocess.CalledProcessError:
pass
return pids
@api_view(['GET'])
@permission_classes([IsCRMAdmin])
def service_status(request):
results = {}
for key, cfg in SERVICES.items():
pids = find_pids_for_service(key)
port_active = is_port_open(cfg['port'])
results[key] = {
'name': cfg['name'],
'directory': cfg['dir'],
'port': cfg['port'],
'port_active': port_active,
'running': len(pids) > 0 or port_active,
'pids': pids
}
return JsonResponse(results)
@api_view(['POST'])
@permission_classes([IsCRMAdmin])
def service_control(request):
service_key = request.data.get('service')
action = request.data.get('action') # start, stop, restart
if service_key not in SERVICES:
return JsonResponse({'error': 'Invalid service. Choose from crm, seller, customer'}, status=400)
if action not in ['start', 'stop', 'restart']:
return JsonResponse({'error': 'Invalid action. Choose from start, stop, restart'}, status=400)
cfg = SERVICES[service_key]
# 1. Stop if running or restart
if action in ['stop', 'restart']:
pids = find_pids_for_service(service_key)
for pid in pids:
try:
os.kill(pid, signal.SIGTERM)
except ProcessLookupError:
pass
time.sleep(1)
# Verify if still alive, then SIGKILL
pids = find_pids_for_service(service_key)
for pid in pids:
try:
os.kill(pid, signal.SIGKILL)
except ProcessLookupError:
pass
if action == 'stop':
return JsonResponse({'message': f'Stopped {cfg["name"]}'})
# 2. Start (or restart continuation)
# Ensure logs folder exists
os.makedirs(cfg['logs_dir'], exist_ok=True)
# We will launch using gunicorn if present, else fallback to manage.py runserver
if os.path.exists(cfg['venv_gunicorn']):
cmd = [
cfg['venv_gunicorn'],
cfg['wsgi'],
'--bind', f'0.0.0.0:{cfg["port"]}',
'--workers', '2',
'--access-logfile', os.path.join(cfg['logs_dir'], 'gunicorn-access.log'),
'--error-logfile', os.path.join(cfg['logs_dir'], 'gunicorn-error.log'),
]
else:
cmd = [
cfg['venv_python'],
os.path.join(cfg['dir'], 'manage.py'),
'runserver',
f'0.0.0.0:{cfg["port"]}'
]
# Run in background session
try:
proc = subprocess.Popen(
cmd,
cwd=cfg['dir'],
stdout=open(os.path.join(cfg['logs_dir'], 'stdout.log'), 'a'),
stderr=open(os.path.join(cfg['logs_dir'], 'stderr.log'), 'a'),
start_new_session=True
)
return JsonResponse({
'message': f'Started {cfg["name"]}',
'pid': proc.pid,
'port': cfg['port']
})
except Exception as e:
return JsonResponse({'error': f'Failed to start: {str(e)}'}, status=500)
@api_view(['GET'])
@permission_classes([IsCRMAdmin])
def list_and_search_logs(request):
service_key = request.query_params.get('service')
log_file_name = request.query_params.get('log_file')
lines_count = int(request.query_params.get('lines', 100))
search_query = request.query_params.get('search', '').lower()
if service_key not in SERVICES:
return JsonResponse({'error': 'Invalid service'}, status=400)
cfg = SERVICES[service_key]
logs_dir = cfg['logs_dir']
if not os.path.exists(logs_dir):
return JsonResponse({'message': 'No logs directory found', 'files': []})
available_files = [f for f in os.listdir(logs_dir) if os.path.isfile(os.path.join(logs_dir, f))]
if not log_file_name:
return JsonResponse({
'service': service_key,
'logs_dir': logs_dir,
'available_files': available_files
})
if log_file_name not in available_files:
return JsonResponse({'error': f'Log file not found. Available: {available_files}'}, status=404)
filepath = os.path.join(logs_dir, log_file_name)
try:
# Read file lines from the end
with open(filepath, 'r', errors='ignore') as f:
lines = f.readlines()
# Filter if search query is provided
if search_query:
lines = [l for l in lines if search_query in l.lower()]
# Slice the last lines
selected_lines = lines[-lines_count:]
return JsonResponse({
'file': log_file_name,
'total_lines_found': len(lines),
'returned_lines_count': len(selected_lines),
'content': ''.join(selected_lines)
})
except Exception as e:
return JsonResponse({'error': f'Failed to read log: {str(e)}'}, status=500)
@api_view(['GET'])
@permission_classes([IsCRMAdmin])
def system_diagnostics(request):
# 1. System stats
total, used, free = shutil.disk_usage("/")
sys_stats = {
'disk': {
'total_gb': total // (2**30),
'used_gb': used // (2**30),
'free_gb': free // (2**30),
'percent_used': round((used / total) * 100, 2)
}
}
# Try reading /proc/meminfo for memory stats
try:
with open('/proc/meminfo', 'r') as f:
lines = f.readlines()
mem_total = 0
mem_free = 0
mem_avail = 0
for line in lines:
if 'MemTotal' in line:
mem_total = int(line.split()[1])
elif 'MemFree' in line:
mem_free = int(line.split()[1])
elif 'MemAvailable' in line:
mem_avail = int(line.split()[1])
if mem_total > 0:
sys_stats['memory'] = {
'total_mb': mem_total // 1024,
'available_mb': mem_avail // 1024 if mem_avail else mem_free // 1024,
'percent_used': round(((mem_total - (mem_avail if mem_avail else mem_free)) / mem_total) * 100, 2)
}
except Exception:
pass
# 2. Database checks
db_status = {}
for db_alias in connections:
try:
connections[db_alias].ensure_connection()
db_status[db_alias] = 'connected'
except Exception as e:
db_status[db_alias] = f'failed: {str(e)}'
return JsonResponse({
'system_resources': sys_stats,
'database_connections': db_status
})
@api_view(['POST'])
@permission_classes([IsCRMAdmin])
def run_management_command(request):
service_key = request.data.get('service')
command = request.data.get('command') # e.g. check, showmigrations, migrate
if service_key not in SERVICES:
return JsonResponse({'error': 'Invalid service'}, status=400)
# Basic protection against arbitrary CLI injection
allowed_commands = ['check', 'showmigrations', 'migrate', 'test']
base_cmd = command.split()[0] if command else ''
if base_cmd not in allowed_commands:
return JsonResponse({'error': f'Command not allowed. Allowed list: {allowed_commands}'}, status=400)
cfg = SERVICES[service_key]
full_cmd = [cfg['venv_python'], 'manage.py'] + command.split()
try:
res = subprocess.run(
full_cmd,
cwd=cfg['dir'],
capture_output=True,
text=True,
timeout=30
)
return JsonResponse({
'command': f'python manage.py {command}',
'exit_code': res.returncode,
'stdout': res.stdout,
'stderr': res.stderr
})
except subprocess.TimeoutExpired:
return JsonResponse({'error': 'Command timed out after 30 seconds'}, status=504)
except Exception as e:
return JsonResponse({'error': f'Execution failed: {str(e)}'}, status=500)