第一个版本

This commit is contained in:
2025-07-31 21:21:45 +08:00
commit 7792734fb0
31 changed files with 662 additions and 0 deletions

BIN
db.sqlite3 Normal file

Binary file not shown.

22
manage.py Normal file
View File

@@ -0,0 +1,22 @@
#!/usr/bin/env python
"""Django's command-line utility for administrative tasks."""
import os
import sys
def main():
"""Run administrative tasks."""
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'statuspage.settings')
try:
from django.core.management import execute_from_command_line
except ImportError as exc:
raise ImportError(
"Couldn't import Django. Are you sure it's installed and "
"available on your PYTHONPATH environment variable? Did you "
"forget to activate a virtual environment?"
) from exc
execute_from_command_line(sys.argv)
if __name__ == '__main__':
main()

1
requirements.txt Normal file
View File

@@ -0,0 +1 @@
Django==4.2.7

0
status/__init__.py Normal file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

9
status/admin.py Normal file
View File

@@ -0,0 +1,9 @@
from django.contrib import admin
from .models import 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')
admin.site.register(Service, ServiceAdmin)

6
status/apps.py Normal file
View File

@@ -0,0 +1,6 @@
from django.apps import AppConfig
class StatusConfig(AppConfig):
default_auto_field = 'django.db.models.BigAutoField'
name = 'status'

View File

@@ -0,0 +1,32 @@
# Generated by Django 4.2.7 on 2025-06-16 12:51
from django.db import migrations, models
import django.utils.timezone
class Migration(migrations.Migration):
initial = True
dependencies = [
]
operations = [
migrations.CreateModel(
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='最后更新时间')),
],
options={
'verbose_name': '服务状态',
'verbose_name_plural': '服务状态',
},
),
]

View File

Binary file not shown.

24
status/models.py Normal file
View File

@@ -0,0 +1,24 @@
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='最后更新时间')
def __str__(self):
return self.name
class Meta:
verbose_name = '服务状态'
verbose_name_plural = '服务状态'

View File

@@ -0,0 +1,239 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>服务状态监控</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdn.jsdelivr.net/npm/font-awesome@4.7.0/css/font-awesome.min.css" rel="stylesheet">
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#165DFF',
operational: '#36D399',
degraded: '#FBBD23',
outage: '#F87272',
dark: '#1E293B',
light: '#F8FAFC'
},
fontFamily: {
inter: ['Inter', 'system-ui', 'sans-serif'],
},
}
}
}
</script>
<style type="text/tailwindcss">
@layer utilities {
.content-auto {
content-visibility: auto;
}
.card-shadow {
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);
}
.card-hover {
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.card-hover:hover {
transform: translateY(-5px);
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);
}
.gradient-bg {
background: linear-gradient(135deg, #165DFF 0%, #0A2463 100%);
}
}
</style>
</head>
<body class="bg-gray-50 font-inter min-h-screen">
<!-- 顶部导航 -->
<header class="gradient-bg text-white shadow-lg">
<div class="container mx-auto px-4 py-6">
<div class="flex flex-col md:flex-row justify-between items-center">
<div class="flex items-center mb-4 md:mb-0">
<i class="fa fa-server text-3xl mr-3"></i>
<h1 class="text-[clamp(1.5rem,3vw,2.5rem)] font-bold">服务状态监控</h1>
</div>
<div class="flex items-center space-x-2">
<i class="fa fa-refresh animate-spin mr-2"></i>
<span id="last-updated" class="text-sm md:text-base">最后更新: 加载中...</span>
</div>
</div>
<p class="mt-2 text-gray-200">实时监控系统服务运行状态与可靠性</p>
</div>
</header>
<!-- 主内容区 -->
<main class="container mx-auto px-4 py-8">
<!-- 状态概览 -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-10">
<div class="bg-white rounded-xl p-6 card-shadow card-hover border-l-4 border-operational">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-500 text-sm">正常运行</p>
<h3 id="operational-count" class="text-3xl font-bold mt-1">0</h3>
</div>
<div class="w-12 h-12 rounded-full bg-operational/10 flex items-center justify-center text-operational">
<i class="fa fa-check-circle text-xl"></i>
</div>
</div>
</div>
<div class="bg-white rounded-xl p-6 card-shadow card-hover border-l-4 border-degraded">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-500 text-sm">性能下降</p>
<h3 id="degraded-count" class="text-3xl font-bold mt-1">0</h3>
</div>
<div class="w-12 h-12 rounded-full bg-degraded/10 flex items-center justify-center text-degraded">
<i class="fa fa-exclamation-triangle text-xl"></i>
</div>
</div>
</div>
<div class="bg-white rounded-xl p-6 card-shadow card-hover border-l-4 border-outage">
<div class="flex items-center justify-between">
<div>
<p class="text-gray-500 text-sm">服务中断</p>
<h3 id="outage-count" class="text-3xl font-bold mt-1">0</h3>
</div>
<div class="w-12 h-12 rounded-full bg-outage/10 flex items-center justify-center text-outage">
<i class="fa fa-times-circle text-xl"></i>
</div>
</div>
</div>
</div>
<!-- 服务列表 -->
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6" id="services-container">
<!-- 服务卡片将通过JavaScript动态生成 -->
<div class="col-span-full text-center py-12 text-gray-500">
<i class="fa fa-circle-o-notch fa-spin text-3xl mb-4"></i>
<p>加载服务数据中...</p>
</div>
</div>
</main>
<!-- 页脚 -->
<footer class="bg-dark text-white py-6 mt-12">
<div class="container mx-auto px-4 text-center">
<p>© 2025 服务状态监控系统 | 数据每30秒自动更新</p>
</div>
</footer>
<script>
// 状态样式映射
const statusStyles = {
operational: {
class: 'bg-operational text-white',
icon: 'fa-check-circle',
text: '正常运行'
},
degraded: {
class: 'bg-degraded text-white',
icon: 'fa-exclamation-triangle',
text: '性能下降'
},
outage: {
class: 'bg-outage text-white',
icon: 'fa-times-circle',
text: '服务中断'
}
};
// 更新服务数据
function updateServices() {
fetch('/api/services/')
.then(response => response.json())
.then(data => {
// 更新最后更新时间
const now = new Date();
document.getElementById('last-updated').textContent = `最后更新: ${now.toLocaleString()}`;
// 统计各状态数量
const counts = {
operational: 0,
degraded: 0,
outage: 0
};
data.forEach(service => {
counts[service.status]++;
});
// 更新统计数字
document.getElementById('operational-count').textContent = counts.operational;
document.getElementById('degraded-count').textContent = counts.degraded;
document.getElementById('outage-count').textContent = counts.outage;
// 生成服务卡片
const container = document.getElementById('services-container');
container.innerHTML = '';
if (data.length === 0) {
container.innerHTML = `
<div class="col-span-full text-center py-12 text-gray-500">
<i class="fa fa-info-circle text-3xl mb-4"></i>
<p>暂无服务数据</p>
</div>
`;
return;
}
data.forEach(service => {
const style = statusStyles[service.status];
const card = document.createElement('div');
card.className = 'bg-white rounded-xl overflow-hidden card-shadow card-hover';
card.innerHTML = `
<div class="p-6">
<div class="flex justify-between items-start mb-4">
<h3 class="text-xl font-bold text-gray-800">${service.name}</h3>
<span class="px-3 py-1 rounded-full text-sm font-medium ${style.class}">
<i class="fa ${style.icon} mr-1"></i> ${style.text}
</span>
</div>
<p class="text-gray-600 mb-4">${service.description}</p>
<div class="grid grid-cols-2 gap-3 text-sm">
<div class="bg-gray-50 p-3 rounded-lg">
<p class="text-gray-500">IP地址</p>
<p class="font-medium">${service.ip_address}</p>
</div>
<div class="bg-gray-50 p-3 rounded-lg">
<p class="text-gray-500">端口</p>
<p class="font-medium">${service.port}</p>
</div>
<div class="bg-gray-50 p-3 rounded-lg col-span-2">
<p class="text-gray-500">可靠率</p>
<div class="w-full bg-gray-200 rounded-full h-2 mt-1">
<div class="${style.class} h-2 rounded-full" style="width: ${service.reliability}%"></div>
</div>
<p class="font-medium mt-1">${service.reliability}%</p>
</div>
</div>
<div class="mt-4 text-xs text-gray-500">
<p>最后更新: ${new Date(service.last_updated).toLocaleString()}</p>
</div>
</div>
`;
container.appendChild(card);
});
})
.catch(error => {
console.error('获取服务数据失败:', error);
const container = document.getElementById('services-container');
container.innerHTML = `
<div class="col-span-full text-center py-12 text-red-500">
<i class="fa fa-exclamation-circle text-3xl mb-4"></i>
<p>加载服务数据失败,请刷新页面重试</p>
</div>
`;
});
}
// 初始加载
updateServices();
// 定时更新 (30秒)
setInterval(updateServices, 30000);
</script>
</body>
</html>

3
status/tests.py Normal file
View File

@@ -0,0 +1,3 @@
from django.test import TestCase
# Create your tests here.

7
status/urls.py Normal file
View File

@@ -0,0 +1,7 @@
from django.urls import path
from . import views
urlpatterns = [
path('', views.home, name='home'),
path('api/services/', views.get_services, name='get_services'),
]

23
status/views.py Normal file
View File

@@ -0,0 +1,23 @@
from django.shortcuts import render
from django.http import JsonResponse
from .models import Service
# 主页视图
def home(request):
return render(request, 'status/index.html')
# API视图 - 获取所有服务状态
def get_services(request):
services = Service.objects.all()
data = []
for service in services:
data.append({
'name': service.name,
'status': service.status,
'description': service.description,
'ip_address': service.ip_address,
'port': service.port,
'reliability': float(service.reliability),
'last_updated': service.last_updated.isoformat()
})
return JsonResponse(data, safe=False)

0
statuspage/__init__.py Normal file
View File

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

16
statuspage/asgi.py Normal file
View File

@@ -0,0 +1,16 @@
"""
ASGI config for statuspage project.
It exposes the ASGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/4.2/howto/deployment/asgi/
"""
import os
from django.core.asgi import get_asgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'statuspage.settings')
application = get_asgi_application()

113
statuspage/settings.py Normal file
View File

@@ -0,0 +1,113 @@
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'django-insecure-$w+8+hw%p$2xi_fi+7avahc&03-y@x05e^r02-x3nt5johmk6l'
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
ALLOWED_HOSTS = []
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'status',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'statuspage.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'statuspage.wsgi.application'
# Database
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.sqlite3',
'NAME': BASE_DIR / 'db.sqlite3',
}
}
# Password validation
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/4.2/topics/i18n/
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.2/howto/static-files/
STATIC_URL = 'static/'
# Default primary key field type
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

23
statuspage/urls.py Normal file
View File

@@ -0,0 +1,23 @@
"""
URL configuration for statuspage project.
The `urlpatterns` list routes URLs to views. For more information please see:
https://docs.djangoproject.com/en/4.2/topics/http/urls/
Examples:
Function views
1. Add an import: from my_app import views
2. Add a URL to urlpatterns: path('', views.home, name='home')
Class-based views
1. Add an import: from other_app.views import Home
2. Add a URL to urlpatterns: path('', Home.as_view(), name='home')
Including another URLconf
1. Import the include() function: from django.urls import include, path
2. Add a URL to urlpatterns: path('blog/', include('blog.urls'))
"""
from django.contrib import admin
from django.urls import path, include
urlpatterns = [
path('admin/', admin.site.urls),
path('', include('status.urls')),
]

16
statuspage/wsgi.py Normal file
View File

@@ -0,0 +1,16 @@
"""
WSGI config for statuspage project.
It exposes the WSGI callable as a module-level variable named ``application``.
For more information on this file, see
https://docs.djangoproject.com/en/4.2/howto/deployment/wsgi/
"""
import os
from django.core.wsgi import get_wsgi_application
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'statuspage.settings')
application = get_wsgi_application()

128
部署文件夹/settings.py Normal file
View File

@@ -0,0 +1,128 @@
import os
from pathlib import Path
# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/4.2/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.environ.get('DJANGO_SECRET_KEY', '')
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False
allowed_hosts_str = os.environ.get('DJANGO_ALLOWED_HOSTS', '')
ALLOWED_HOSTS = [host.strip() for host in allowed_hosts_str.split(',') if host.strip()]
# Application definition
INSTALLED_APPS = [
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'status',
]
MIDDLEWARE = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
]
ROOT_URLCONF = 'statuspage.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': [],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
],
},
},
]
WSGI_APPLICATION = 'statuspage.wsgi.application'
# Database
# https://docs.djangoproject.com/en/4.2/ref/settings/#databases
DATABASES = {
'default': {
'ENGINE': os.environ.get('DB_ENGINE', 'django.db.backends.sqlite3'),
'NAME': os.environ.get('DB_NAME', ''),
'USER': os.environ.get('DB_USER', ''),
'PASSWORD': os.environ.get('DB_PASSWORD', ''),
'HOST': os.environ.get('DB_HOST', ''),
'PORT': os.environ.get('DB_PORT', ''),
}
}
# Password validation
# https://docs.djangoproject.com/en/4.2/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
},
]
# Internationalization
# https://docs.djangoproject.com/en/4.2/topics/i18n/
LANGUAGE_CODE = 'zh-hans'
TIME_ZONE = 'Asia/Shanghai'
USE_I18N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/4.2/howto/static-files/
STATIC_URL = 'static/'
STATIC_ROOT = '/home/djangouser/statuspage/static/'
MEDIA_ROOT = '/home/djangouser/statuspage/media/'
MEDIA_URL = 'media/'
# Security settings
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_SECURE = True
SECURE_SSL_REDIRECT = True
# Default primary key field type
# https://docs.djangoproject.com/en/4.2/ref/settings/#default-auto-field
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'