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 %}
+
+ | {{ 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 %}
+ |
+
+ {% empty %}
+
+ |
+
+ 暂无检测记录
+ |
+
+ {% endfor %}
+
+
+
+
+
+
+
+
+
+
+
+