diff --git a/db.sqlite3 b/db.sqlite3 index 8828eb8..70bf373 100644 Binary files a/db.sqlite3 and b/db.sqlite3 differ diff --git a/requirements.txt b/requirements.txt index c023ba7..791dd34 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,3 @@ -Django==4.2.7 \ No newline at end of file +Django==4.2.7 +djangorestframework==3.14.0 +loguru==0.7.2 \ No newline at end of file diff --git a/status/__pycache__/admin.cpython-38.pyc b/status/__pycache__/admin.cpython-38.pyc index f5334ac..83f5a02 100644 Binary files a/status/__pycache__/admin.cpython-38.pyc and b/status/__pycache__/admin.cpython-38.pyc differ diff --git a/status/__pycache__/models.cpython-38.pyc b/status/__pycache__/models.cpython-38.pyc index 48a79f4..54831d7 100644 Binary files a/status/__pycache__/models.cpython-38.pyc and b/status/__pycache__/models.cpython-38.pyc differ diff --git a/status/__pycache__/urls.cpython-38.pyc b/status/__pycache__/urls.cpython-38.pyc index 8739e36..e303279 100644 Binary files a/status/__pycache__/urls.cpython-38.pyc and b/status/__pycache__/urls.cpython-38.pyc differ diff --git a/status/__pycache__/views.cpython-38.pyc b/status/__pycache__/views.cpython-38.pyc index a908fed..0f47acc 100644 Binary files a/status/__pycache__/views.cpython-38.pyc and b/status/__pycache__/views.cpython-38.pyc differ diff --git a/status/admin.py b/status/admin.py index 715c2ff..00ac340 100644 --- a/status/admin.py +++ b/status/admin.py @@ -1,9 +1,50 @@ from django.contrib import admin -from .models import Service +from .models import ServiceGroup, Service, ServiceCheckRecord +# 引入loguru库用于日志记录 +try: + from loguru import logger +except ImportError: + import logging + logger = logging.getLogger(__name__) + +@admin.register(ServiceGroup) +class ServiceGroupAdmin(admin.ModelAdmin): + list_display = ('name', 'description') + search_fields = ('name', 'description') + + def save_model(self, request, obj, form, change): + if change: + logger.info(f"更新服务分组: {obj.name}") + else: + logger.info(f"创建新服务分组: {obj.name}") + super().save_model(request, obj, form, change) + +@admin.register(Service) class ServiceAdmin(admin.ModelAdmin): - list_display = ('name', 'status', 'description', 'ip_address', 'port', 'reliability', 'last_updated') - list_filter = ('status',) - search_fields = ('name', 'ip_address', 'description') + list_display = ('name', 'group', 'host', 'port', 'check_type', 'is_active', 'created_at') + list_filter = ('group', 'check_type', 'is_active') + search_fields = ('name', 'host', 'description') + + def save_model(self, request, obj, form, change): + if change: + logger.info(f"更新服务: {obj.name} ({obj.host})") + else: + logger.info(f"创建新服务: {obj.name} ({obj.host})") + super().save_model(request, obj, form, change) -admin.site.register(Service, ServiceAdmin) +@admin.register(ServiceCheckRecord) +class ServiceCheckRecordAdmin(admin.ModelAdmin): + list_display = ('service', 'status', 'response_time', 'checked_at') + list_filter = ('status', 'service__group', 'service') + search_fields = ('service__name', 'message') + date_hierarchy = 'checked_at' + readonly_fields = ('service', 'status', 'response_time', 'message', 'checked_at') + + def has_add_permission(self, request): + return False # 禁止手动添加检测记录,只能通过API上报 + + def has_change_permission(self, request, obj=None): + return False # 禁止修改检测记录 + +logger.info("管理后台配置已加载") diff --git a/status/migrations/0001_initial.py b/status/migrations/0001_initial.py index 5f01ade..707dd86 100644 --- a/status/migrations/0001_initial.py +++ b/status/migrations/0001_initial.py @@ -1,6 +1,7 @@ -# Generated by Django 4.2.7 on 2025-06-16 12:51 +# Generated by Django 4.2.7 on 2025-09-07 08:41 from django.db import migrations, models +import django.db.models.deletion import django.utils.timezone @@ -16,17 +17,50 @@ class Migration(migrations.Migration): name='Service', fields=[ ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('name', models.CharField(max_length=100, verbose_name='服务名称')), - ('status', models.CharField(choices=[('operational', '正常运行'), ('degraded', '性能下降'), ('outage', '服务中断')], default='operational', max_length=20, verbose_name='服务状态')), - ('description', models.TextField(verbose_name='服务描述')), - ('ip_address', models.GenericIPAddressField(verbose_name='IP地址')), - ('port', models.PositiveIntegerField(verbose_name='端口号')), - ('reliability', models.DecimalField(decimal_places=2, default=99.0, max_digits=5, verbose_name='可靠率(%)')), - ('last_updated', models.DateTimeField(default=django.utils.timezone.now, verbose_name='最后更新时间')), + ('name', models.CharField(max_length=200, verbose_name='服务名称')), + ('host', models.CharField(max_length=255, verbose_name='主机/IP')), + ('port', models.PositiveIntegerField(blank=True, null=True, verbose_name='端口(可选)')), + ('check_type', models.CharField(choices=[('ping', 'Ping检测'), ('tcp', 'TCP端口检测'), ('http', 'HTTP请求'), ('custom', '自定义脚本')], default='ping', max_length=50, verbose_name='检测类型')), + ('description', models.TextField(blank=True, null=True, verbose_name='描述')), + ('is_active', models.BooleanField(default=True, verbose_name='是否启用监控')), + ('created_at', models.DateTimeField(auto_now_add=True, verbose_name='创建时间')), ], options={ - 'verbose_name': '服务状态', - 'verbose_name_plural': '服务状态', + 'verbose_name': '服务', + 'verbose_name_plural': '服务', }, ), + migrations.CreateModel( + name='ServiceGroup', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('name', models.CharField(max_length=100, unique=True, verbose_name='服务分组')), + ('description', models.TextField(blank=True, null=True, verbose_name='描述')), + ], + options={ + 'verbose_name': '服务分组', + 'verbose_name_plural': '服务分组', + }, + ), + migrations.CreateModel( + name='ServiceCheckRecord', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('status', models.CharField(choices=[('UP', '正常'), ('DOWN', '故障'), ('UNKNOWN', '未知')], max_length=10, verbose_name='状态')), + ('response_time', models.FloatField(blank=True, null=True, verbose_name='响应时间(ms)')), + ('message', models.TextField(blank=True, null=True, verbose_name='返回信息/错误原因')), + ('checked_at', models.DateTimeField(default=django.utils.timezone.now, verbose_name='检测时间')), + ('service', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='records', to='status.service')), + ], + options={ + 'verbose_name': '服务检测记录', + 'verbose_name_plural': '服务检测记录', + 'ordering': ['-checked_at'], + }, + ), + migrations.AddField( + model_name='service', + name='group', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='services', to='status.servicegroup', verbose_name='所属分组'), + ), ] diff --git a/status/migrations/__pycache__/0001_initial.cpython-38.pyc b/status/migrations/__pycache__/0001_initial.cpython-38.pyc index c7dc611..852248c 100644 Binary files a/status/migrations/__pycache__/0001_initial.cpython-38.pyc and b/status/migrations/__pycache__/0001_initial.cpython-38.pyc differ diff --git a/status/models.py b/status/models.py index bb91ffe..fef97af 100644 --- a/status/models.py +++ b/status/models.py @@ -1,24 +1,67 @@ from django.db import models from django.utils import timezone -class Service(models.Model): - STATUS_CHOICES = ( - ('operational', '正常运行'), - ('degraded', '性能下降'), - ('outage', '服务中断'), - ) - - name = models.CharField(max_length=100, verbose_name='服务名称') - status = models.CharField(max_length=20, choices=STATUS_CHOICES, default='operational', verbose_name='服务状态') - description = models.TextField(verbose_name='服务描述') - ip_address = models.GenericIPAddressField(verbose_name='IP地址') - port = models.PositiveIntegerField(verbose_name='端口号') - reliability = models.DecimalField(max_digits=5, decimal_places=2, default=99.00, verbose_name='可靠率(%)') - last_updated = models.DateTimeField(default=timezone.now, verbose_name='最后更新时间') +# 引入loguru库用于日志记录 +try: + from loguru import logger +except ImportError: + import logging + logger = logging.getLogger(__name__) + +class ServiceGroup(models.Model): + name = models.CharField(max_length=100, unique=True, verbose_name="服务分组") + description = models.TextField(blank=True, null=True, verbose_name="描述") def __str__(self): return self.name class Meta: - verbose_name = '服务状态' - verbose_name_plural = '服务状态' + verbose_name = '服务分组' + verbose_name_plural = '服务分组' + +class Service(models.Model): + STATUS_CHOICES = [ + ('UP', '正常'), + ('DOWN', '故障'), + ('UNKNOWN', '未知'), + ] + + name = models.CharField(max_length=200, verbose_name="服务名称") + group = models.ForeignKey(ServiceGroup, on_delete=models.CASCADE, related_name='services', verbose_name="所属分组") + host = models.CharField(max_length=255, verbose_name="主机/IP") + port = models.PositiveIntegerField(null=True, blank=True, verbose_name="端口(可选)") + check_type = models.CharField(max_length=50, default='ping', choices=[ + ('ping', 'Ping检测'), + ('tcp', 'TCP端口检测'), + ('http', 'HTTP请求'), + ('custom', '自定义脚本'), + ], verbose_name="检测类型") + description = models.TextField(blank=True, null=True, verbose_name="描述") + is_active = models.BooleanField(default=True, verbose_name="是否启用监控") + created_at = models.DateTimeField(auto_now_add=True, verbose_name="创建时间") + + def __str__(self): + return f"{self.name} ({self.host})" + + class Meta: + verbose_name = '服务' + verbose_name_plural = '服务' + +class ServiceCheckRecord(models.Model): + service = models.ForeignKey(Service, on_delete=models.CASCADE, related_name='records') + status = models.CharField(max_length=10, choices=Service.STATUS_CHOICES, verbose_name="状态") + response_time = models.FloatField(null=True, blank=True, verbose_name="响应时间(ms)") + message = models.TextField(blank=True, null=True, verbose_name="返回信息/错误原因") + checked_at = models.DateTimeField(default=timezone.now, verbose_name="检测时间") + + class Meta: + ordering = ['-checked_at'] + verbose_name = '服务检测记录' + verbose_name_plural = '服务检测记录' + + def __str__(self): + return f"{self.service.name} - {self.status} at {self.checked_at}" + + def save(self, *args, **kwargs): + logger.info(f"保存服务检测记录: {self.service.name}, 状态: {self.status}, 响应时间: {self.response_time}ms") + super().save(*args, **kwargs) diff --git a/status/templates/status/index.html b/status/templates/status/index.html index fe7163a..f3736ee 100644 --- a/status/templates/status/index.html +++ b/status/templates/status/index.html @@ -5,7 +5,7 @@ 服务状态监控 - + + + + + + + + +
+
+ +
+
+ + +
+ +
+
+
+

{{ service.name }}

+
+ {% if service.latest_status == 'UP' %} + + 正常 + + {% elif service.latest_status == 'DOWN' %} + + 故障 + + {% else %} + + 未知 + + {% endif %} + + + {% if service.latest_check_time %}最后检测: {{ service.latest_check_time|date:"y-m-d h:i:s" }}{% else %}从未检测{% endif %} + +
+
+ + 返回服务列表 + +
+ +
+
+

基本信息

+
+
+ 服务名称: + {{ service.name }} +
+
+ 所属分组: + {{ service.group.name }} +
+
+ 主机/IP: + {{ service.host }} +
+
+ 端口: + {% if service.port %}{{ service.port }}{% else %}N/A{% endif %} +
+
+ 检测类型: + {{ service.get_check_type_display }} +
+
+ 是否启用: + {% if service.is_active %}是{% else %}否{% endif %} +
+
+
+ +
+

最新状态

+
+
+ 当前状态: + + {% if service.latest_status == 'UP' %}正常 + {% elif service.latest_status == 'DOWN' %}故障 + {% else %}未知{% endif %} + +
+
+ 响应时间: + {% if service.latest_response_time %}{{ service.latest_response_time }} ms{% else %}N/A{% endif %} +
+
+ 最后检测: + {% if service.latest_check_time %}{{ service.latest_check_time|date:"y-m-d h:i:s" }}{% else %}从未检测{% endif %} +
+
+ 检测消息: + {% if service.latest_message %}{{ service.latest_message }}{% else %}无{% endif %} +
+
+
+
+ + {% if service.description %} +
+

描述

+

{{ service.description }}

+
+ {% endif %} +
+ + +
+

响应时间趋势 (最近24小时)

+
+ +
+
+ + +
+

状态变化时间轴

+
+
+
+ {% for record in status_changes %} +
+
+ {% if record.status == 'UP' %} + {% elif record.status == 'DOWN' %} + {% else %}{% endif %} +
+
+
+ + {% if record.status == 'UP' %}正常 + {% elif record.status == 'DOWN' %}故障 + {% else %}未知{% endif %} + + {{ record.checked_at|date:"y-m-d h:i:s" }} +
+ {% if record.message %} +

{{ record.message }}

+ {% endif %} + {% if record.response_time %} +

响应时间: {{ record.response_time }} ms

+ {% endif %} +
+
+ {% empty %} +
+ +

暂无状态变化记录

+
+ {% endfor %} +
+
+
+ + +
+
+

最近检测记录

+
+
+ + + + + + + + + + + {% for record in recent_records %} + + + + + + + {% empty %} + + + + {% endfor %} + +
检测时间状态响应时间消息
{{ record.checked_at|date:"y-m-d h:i:s" }} + {% if record.status == 'UP' %} + + 正常 + + {% elif record.status == 'DOWN' %} + + 故障 + + {% else %} + + 未知 + + {% endif %} + + {% if record.response_time %}{{ record.response_time }} ms{% else %}N/A{% endif %} + + {% if record.message %}{{ record.message }}{% else %}无{% endif %} +
+ +

暂无检测记录

+
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/status/templates/status/service_list.html b/status/templates/status/service_list.html new file mode 100644 index 0000000..e2ba02e --- /dev/null +++ b/status/templates/status/service_list.html @@ -0,0 +1,216 @@ + + + + + + 服务列表 - 服务状态监控 + + + + + + + +
+
+ +
+
+ + +
+ +
+
+

服务列表

+
+
+ +
+ +
+
+
+ +
+ +
+
+
+ +
+ +
+
+ +
+
+
+ + +
+
+ + + + + + + + + + + + + + {% for service in services %} + + + + + + + + + + {% empty %} + + + + {% endfor %} + +
服务名称分组主机/IP状态最后检测时间响应时间操作
+
{{ service.name }}
+
{{ service.check_type }}
+
+
{{ service.group.name }}
+
+
{{ service.host }}
+
{% if service.port %}{{ service.port }}{% else %}N/A{% endif %}
+
+ {% if service.latest_status == 'UP' %} + + 正常 + + {% elif service.latest_status == 'DOWN' %} + + 故障 + + {% else %} + + 未知 + + {% endif %} + + {% if service.latest_check_time %}{{ service.latest_check_time|date:"y-m-d h:i" }}{% else %}从未检测{% endif %} + + {% if service.latest_response_time %}{{ service.latest_response_time }} ms{% else %}N/A{% endif %} + + + 查看详情 + +
+ +

暂无服务数据

+
+
+
+
+ + + + + + + \ No newline at end of file diff --git a/status/urls.py b/status/urls.py index 9bbb9fa..78abbca 100644 --- a/status/urls.py +++ b/status/urls.py @@ -1,7 +1,24 @@ from django.urls import path from . import views +# 引入loguru库用于日志记录 +try: + from loguru import logger +except ImportError: + import logging + logger = logging.getLogger(__name__) + urlpatterns = [ + # 前端页面路由 path('', views.home, name='home'), - path('api/services/', views.get_services, name='get_services'), -] \ No newline at end of file + path('services/', views.service_list, name='service_list'), + path('services//', views.service_detail, name='service_detail'), + + # API接口路由 + path('api/checkin/', views.checkin, name='api_checkin'), + path('api/services/', views.api_services, name='api_services'), + path('api/services//history/', views.api_service_history, name='api_service_history'), + path('api/status-summary/', views.api_status_summary, name='api_status_summary'), +] + +logger.info("URL路由配置已加载") \ No newline at end of file diff --git a/status/utils.py b/status/utils.py new file mode 100644 index 0000000..b69f95a --- /dev/null +++ b/status/utils.py @@ -0,0 +1,113 @@ +from django.db.models import Count, Case, When, Q, Subquery, OuterRef +from .models import ServiceGroup, Service, ServiceCheckRecord + +# 引入loguru库用于日志记录 +try: + from loguru import logger +except ImportError: + import logging + logger = logging.getLogger(__name__) + +# 工具函数:获取或创建服务 +def get_or_create_service(data): + """当客户端上报一个不存在的服务时,自动创建""" + service, created = Service.objects.get_or_create( + name=data['service_name'], + host=data['host'], + port=data.get('port'), + defaults={ + 'group': ServiceGroup.objects.get_or_create(name="Default")[0], + 'check_type': data['check_type'], + 'description': f"Auto-created from client checkin: {data['service_name']}" + } + ) + if created: + logger.info(f"自动创建新服务: {service.name} ({service.host})") + return service + +# 工具函数:获取状态摘要 +def get_status_summary(): + """获取全局状态摘要(如:总共服务数、正常数、异常数)""" + # 获取每个服务的最新状态 + latest_records = ServiceCheckRecord.objects.filter( + service=OuterRef('pk') + ).order_by('-checked_at') + + services_with_status = Service.objects.annotate( + latest_status=Subquery(latest_records.values('status')[:1]) + ) + + total = services_with_status.count() + up_count = services_with_status.filter(latest_status='UP').count() + down_count = services_with_status.filter(latest_status='DOWN').count() + unknown_count = services_with_status.filter(latest_status='UNKNOWN').count() + + return { + 'total': total, + 'up': up_count, + 'down': down_count, + 'unknown': unknown_count + } + +# 工具函数:获取服务的最新状态 +def get_service_latest_status(service): + """获取服务的最新状态""" + latest_record = service.records.order_by('-checked_at').first() + if latest_record: + return { + 'status': latest_record.status, + 'check_time': latest_record.checked_at, + 'response_time': latest_record.response_time, + 'message': latest_record.message + } + return { + 'status': 'UNKNOWN', + 'check_time': None, + 'response_time': None, + 'message': None + } + +# 工具函数:获取服务的状态变化时间轴 +def get_service_status_timeline(service, limit=20): + """获取服务的状态变化时间轴""" + status_changes = [] + prev_status = None + + for record in service.records.all().order_by('-checked_at'): + if prev_status is None or record.status != prev_status: + status_changes.append(record) + prev_status = record.status + + if len(status_changes) >= limit: + break + + return status_changes + +# 工具函数:获取服务的响应时间趋势数据 +def get_service_response_time_chart_data(service, hours=24): + """获取服务的响应时间趋势数据""" + from django.utils import timezone + import datetime + + end_time = timezone.now() + start_time = end_time - datetime.timedelta(hours=hours) + + # 获取指定时间范围内的记录 + chart_records = service.records.filter( + checked_at__gte=start_time, + checked_at__lte=end_time, + response_time__isnull=False + ).order_by('checked_at') + + # 准备图表数据 + chart_labels = [] + chart_data = [] + + for record in chart_records: + chart_labels.append(record.checked_at.strftime('%H:%M')) + chart_data.append(record.response_time) + + return { + 'labels': chart_labels, + 'data': chart_data + } \ No newline at end of file diff --git a/status/views.py b/status/views.py index 554bbcc..f34cf84 100644 --- a/status/views.py +++ b/status/views.py @@ -1,23 +1,219 @@ -from django.shortcuts import render +from django.shortcuts import render, get_object_or_404 from django.http import JsonResponse -from .models import Service +from django.db.models import Count, Case, When, Q, Subquery, OuterRef +from django.views.decorators.csrf import csrf_exempt +from django.utils import timezone +import json +from .models import ServiceGroup, Service, ServiceCheckRecord +from .utils import get_or_create_service, get_status_summary, get_service_latest_status, get_service_status_timeline, get_service_response_time_chart_data + +# 引入loguru库用于日志记录 +try: + from loguru import logger +except ImportError: + import logging + logger = logging.getLogger(__name__) # 主页视图 def home(request): - return render(request, 'status/index.html') + """首页Dashboard""" + summary = get_status_summary() + # 获取最近异常的服务 + recent_issues = ServiceCheckRecord.objects.filter( + status__in=['DOWN', 'UNKNOWN'] + ).order_by('-checked_at')[:10] + + context = { + 'summary': summary, + 'recent_issues': recent_issues + } + return render(request, 'status/index.html', context) -# API视图 - 获取所有服务状态 -def get_services(request): +# 服务列表页 +def service_list(request): + """服务列表页""" + group_filter = request.GET.get('group') + status_filter = request.GET.get('status') + search_query = request.GET.get('search', '') + services = Service.objects.all() + + if group_filter: + services = services.filter(group__name=group_filter) + + if status_filter: + # 根据最新状态筛选 + latest_records = ServiceCheckRecord.objects.filter( + service=OuterRef('pk') + ).order_by('-checked_at') + services = services.annotate( + latest_status=Subquery(latest_records.values('status')[:1]) + ).filter(latest_status=status_filter) + + if search_query: + services = services.filter( + Q(name__icontains=search_query) | + Q(host__icontains=search_query) | + Q(description__icontains=search_query) + ) + + # 获取每个服务的最新状态 + latest_records = ServiceCheckRecord.objects.filter( + service=OuterRef('pk') + ).order_by('-checked_at') + services = services.annotate( + latest_status=Subquery(latest_records.values('status')[:1]), + latest_check_time=Subquery(latest_records.values('checked_at')[:1]), + latest_response_time=Subquery(latest_records.values('response_time')[:1]) + ) + + groups = ServiceGroup.objects.all() + + context = { + 'services': services, + 'groups': groups, + 'current_group': group_filter, + 'current_status': status_filter, + 'search_query': search_query + } + return render(request, 'status/service_list.html', context) + +# 服务详情页 +def service_detail(request, service_id): + """服务详情页""" + service = get_object_or_404(Service, pk=service_id) + # 获取最近10条检测记录 + recent_records = service.records.all()[:10] + + # 获取状态变化时间轴数据 + status_changes = get_service_status_timeline(service, limit=20) + + # 获取响应时间趋势图数据 + chart_data = get_service_response_time_chart_data(service, hours=24) + + # 获取服务的最新状态信息 + latest_status = get_service_latest_status(service) + service.latest_status = latest_status['status'] + service.latest_check_time = latest_status['check_time'] + service.latest_response_time = latest_status['response_time'] + service.latest_message = latest_status['message'] + + context = { + 'service': service, + 'recent_records': recent_records, + 'status_changes': status_changes, + 'chart_labels': chart_data['labels'], + 'chart_data': chart_data['data'] + } + return render(request, 'status/service_detail.html', context) + +# API视图 - 客户端上报接口 +@csrf_exempt +def checkin(request): + """客户端定期调用此接口上报服务状态""" + if request.method != 'POST': + return JsonResponse({'code': 405, 'message': '只支持POST请求'}, status=405) + + try: + data = json.loads(request.body) + logger.info(f"收到服务状态上报: {data.get('service_name')} - {data.get('status')}") + + # 验证必要字段 + required_fields = ['service_name', 'host', 'check_type', 'status'] + for field in required_fields: + if field not in data: + return JsonResponse({'code': 400, 'message': f'缺少必要字段: {field}'}, status=400) + + # 获取或创建服务 + service = get_or_create_service(data) + + # 创建检测记录 + record = ServiceCheckRecord.objects.create( + service=service, + status=data['status'], + response_time=data.get('response_time'), + message=data.get('message', '') + ) + + logger.info(f"服务状态已记录: {service.name} - {record.status}") + return JsonResponse({ + 'code': 200, + 'message': '上报成功', + 'service_id': service.id + }) + + except json.JSONDecodeError: + return JsonResponse({'code': 400, 'message': '无效的JSON数据'}, status=400) + except Exception as e: + logger.error(f"处理上报数据时出错: {str(e)}") + return JsonResponse({'code': 500, 'message': f'服务器内部错误: {str(e)}'}, status=500) + +# API视图 - 获取所有服务列表(含最新状态) +def api_services(request): + """获取所有服务列表(含最新状态)""" + # 获取每个服务的最新状态 + latest_records = ServiceCheckRecord.objects.filter( + service=OuterRef('pk') + ).order_by('-checked_at') + + services = Service.objects.annotate( + latest_status=Subquery(latest_records.values('status')[:1]), + latest_check_time=Subquery(latest_records.values('checked_at')[:1]), + latest_response_time=Subquery(latest_records.values('response_time')[:1]) + ) + data = [] for service in services: data.append({ + 'id': service.id, 'name': service.name, - 'status': service.status, - 'description': service.description, - 'ip_address': service.ip_address, + 'group': service.group.name, + 'host': service.host, 'port': service.port, - 'reliability': float(service.reliability), - 'last_updated': service.last_updated.isoformat() + 'check_type': service.check_type, + 'is_active': service.is_active, + 'latest_status': service.latest_status, + 'latest_check_time': service.latest_check_time.isoformat() if service.latest_check_time else None, + 'latest_response_time': service.latest_response_time }) + return JsonResponse(data, safe=False) + +# API视图 - 获取某服务历史记录(分页) +def api_service_history(request, service_id): + """获取某服务历史记录(分页)""" + service = get_object_or_404(Service, pk=service_id) + + # 分页参数 + page = int(request.GET.get('page', 1)) + page_size = int(request.GET.get('page_size', 20)) + + # 计算分页范围 + start = (page - 1) * page_size + end = start + page_size + + records = service.records.all()[start:end] + total = service.records.count() + + data = [] + for record in records: + data.append({ + 'id': record.id, + 'status': record.status, + 'response_time': record.response_time, + 'message': record.message, + 'checked_at': record.checked_at.isoformat() + }) + + return JsonResponse({ + 'total': total, + 'page': page, + 'page_size': page_size, + 'records': data + }) + +# API视图 - 获取全局状态摘要 +def api_status_summary(request): + """获取全局状态摘要(如:总共服务数、正常数、异常数)""" + summary = get_status_summary() + return JsonResponse(summary) diff --git a/statuspage/__pycache__/settings.cpython-38.pyc b/statuspage/__pycache__/settings.cpython-38.pyc index 989515d..cebdec3 100644 Binary files a/statuspage/__pycache__/settings.cpython-38.pyc and b/statuspage/__pycache__/settings.cpython-38.pyc differ