This commit is contained in:
Alex Yang
2025-11-29 23:40:01 +08:00
parent 26889f5b38
commit 70cf1a7306
30 changed files with 1644023 additions and 817 deletions

488
static/api/css/style.css Normal file
View File

@@ -0,0 +1,488 @@
/* 基础样式 */
body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
background-color: #ffffff;
color: #333333;
}
/* 默认浅色主题样式 */
.swagger-ui .topbar {
background-color: #2c3e50;
padding: 15px 0;
}
.swagger-ui .topbar .topbar-wrapper .link {
color: #ecf0f1;
font-size: 1.2rem;
}
.swagger-ui .info {
margin: 20px 0;
}
.swagger-ui .info .title {
font-size: 2rem;
margin-bottom: 10px;
color: #333;
}
.swagger-ui .info .description {
font-size: 1rem;
color: #555;
margin-bottom: 15px;
}
/* 修复服务器URL输入框样式 */
.swagger-ui .servers li input[type="text"] {
padding: 8px 12px;
width: 100%;
box-sizing: border-box;
}
/* 修复服务器选择区域的背景颜色和布局 */
.swagger-ui .servers {
padding: 16px;
width: 100%;
box-sizing: border-box;
margin: 0;
}
/* 确保服务器列表容器有正确的背景色和布局 */
.swagger-ui .servers-wrapper {
width: 100%;
box-sizing: border-box;
margin: 0;
}
/* 确保整个顶部区域颜色一致和布局正确 */
.swagger-ui .info {
margin: 0;
padding: 20px 16px;
width: 100%;
box-sizing: border-box;
}
/* 确保顶部主容器颜色一致和布局正确 */
.swagger-ui {
width: 100%;
box-sizing: border-box;
margin: 0;
padding: 0;
}
/* 确保API信息区域颜色一致和布局正确 */
.swagger-ui .info-container {
width: 100%;
box-sizing: border-box;
}
body.dark-mode .swagger-ui .servers li label {
color: #ffffff !important;
font-weight: 500 !important;
}
/* 修复服务器URL输入框深色模式样式 */
body.dark-mode .swagger-ui .servers li input[type="text"] {
background-color: #1a202c !important;
color: #ffffff !important;
border-color: #4a5568 !important;
padding: 8px 12px !important;
width: 100% !important;
}
/* 修复服务器选择区域的深色模式背景颜色和布局 */
body.dark-mode .swagger-ui .servers {
background-color: #1a202c !important;
border: none !important;
padding: 16px !important;
width: 100% !important;
box-sizing: border-box !important;
margin: 0 !important;
}
/* 确保服务器列表容器在深色模式下也有正确的背景色和布局 */
body.dark-mode .swagger-ui .servers-wrapper {
background-color: #1a202c !important;
width: 100% !important;
box-sizing: border-box !important;
margin: 0 !important;
}
/* 确保整个顶部区域在深色模式下颜色一致和布局正确 */
body.dark-mode .swagger-ui .info {
background-color: #1a202c !important;
margin: 0 !important;
padding: 20px 16px !important;
border-bottom: 1px solid #4a5568 !important;
width: 100% !important;
box-sizing: border-box !important;
}
/* 确保顶部主容器在深色模式下颜色一致和布局正确 */
body.dark-mode .swagger-ui {
background-color: #1a202c !important;
width: 100% !important;
box-sizing: border-box !important;
margin: 0 !important;
padding: 0 !important;
}
/* 确保API信息区域在深色模式下颜色一致和布局正确 */
body.dark-mode .swagger-ui .info-container {
background-color: #1a202c !important;
width: 100% !important;
box-sizing: border-box !important;
margin: 0 !important;
padding: 0 !important;
}
/* 修复深色模式下内容区域的布局问题 */
body.dark-mode .swagger-ui .wrapper {
width: 100% !important;
box-sizing: border-box !important;
margin: 0 !important;
padding: 0 !important;
}
/* 修复深色模式下API操作块的布局 */
body.dark-mode .swagger-ui .opblock {
margin: 0 !important;
padding: 0 !important;
width: 100% !important;
box-sizing: border-box !important;
}
/* 修复深色模式下过滤器的布局 */
body.dark-mode .swagger-ui .filter {
width: 100% !important;
box-sizing: border-box !important;
padding: 16px !important;
margin: 0 !important;
}
/* 修复深色模式下顶部栏布局 */
body.dark-mode .swagger-ui .topbar {
width: 100% !important;
box-sizing: border-box !important;
margin: 0 !important;
padding: 15px 0 !important;
}
/* 修复深色模式下顶部栏包装器布局 */
body.dark-mode .swagger-ui .topbar .topbar-wrapper {
width: 100% !important;
box-sizing: border-box !important;
padding: 0 16px !important;
}
/* 修复深色模式下响应容器布局 */
body.dark-mode .swagger-ui .responses-inner {
width: 100% !important;
box-sizing: border-box !important;
}
/* 修复深色模式下操作块摘要布局 */
body.dark-mode .swagger-ui .opblock-summary {
width: 100% !important;
box-sizing: border-box !important;
}
/* 确保深色模式下所有容器元素都使用box-sizing */
body.dark-mode * {
box-sizing: border-box !important;
}
/* 增强标签标题深色模式样式 */
body.dark-mode .swagger-ui .opblock-tag {
color: #ffffff !important;
background-color: #2d3748 !important;
padding: 12px 16px !important;
border-radius: 6px !important;
margin-bottom: 12px !important;
font-weight: 700 !important;
font-size: 1.1rem !important;
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.3) !important;
}
/* 增强标签标题h3深色模式样式 */
body.dark-mode .swagger-ui .opblock-tag.h3 {
color: #ffffff !important;
background-color: #2d3748 !important;
}
/* 增强标签部分深色模式样式 */
body.dark-mode .swagger-ui .opblock-tag-section {
background-color: #2d3748 !important;
padding: 16px !important;
border-radius: 8px !important;
margin-bottom: 20px !important;
}
/* 增强API描述深色模式样式 */
body.dark-mode .swagger-ui .opblock-description-wrapper {
color: #ffffff !important;
background-color: #2d3748 !important;
padding: 12px 16px !important;
border-radius: 6px !important;
margin-bottom: 12px !important;
font-weight: 500 !important;
}
body.dark-mode .swagger-ui .opblock-description-wrapper p {
color: #ffffff !important;
line-height: 1.5 !important;
}
/* 增强stats标签描述深色模式样式 */
body.dark-mode .swagger-ui .opblock-summary-description {
color: #ffffff !important;
font-weight: 500 !important;
}
/* 增强操作块标题深色模式样式 */
body.dark-mode .swagger-ui .opblock-title_normal h4 {
color: #ffffff !important;
font-weight: 600 !important;
}
/* 增强参数部分深色模式样式 */
body.dark-mode .swagger-ui .opblock-body {
background-color: #2d3748 !important;
}
body.dark-mode .swagger-ui .opblock-body .parameter__name {
color: #ffffff !important;
font-weight: 600 !important;
}
body.dark-mode .swagger-ui .opblock-body .parameter__type {
color: #ffffff !important;
font-weight: 500 !important;
}
body.dark-mode .swagger-ui .opblock-body .parameter__description {
color: #ffffff !important;
}
body.dark-mode .swagger-ui .parameters-col_description,
body.dark-mode .swagger-ui .parameters-col_name,
body.dark-mode .swagger-ui .parameters-col_type {
color: #ffffff !important;
}
body.dark-mode .swagger-ui .parameters-col_description p,
body.dark-mode .swagger-ui .parameters-col_name p,
body.dark-mode .swagger-ui .parameters-col_type p {
color: #ffffff !important;
}
/* 新增适配API文档展开界面的所有文字元素 */
body.dark-mode .swagger-ui .opblock-body {
color: #ffffff;
}
body.dark-mode .swagger-ui .opblock-body .parameter__name {
color: #ffffff;
}
body.dark-mode .swagger-ui .opblock-body .parameter__type {
color: #ffffff;
}
body.dark-mode .swagger-ui .opblock-body .parameter__description {
color: #ffffff;
}
body.dark-mode .swagger-ui .opblock-body .body-param-options {
color: #ffffff;
}
body.dark-mode .swagger-ui .opblock-body .body-param-options .body-param-type {
color: #ffffff;
}
body.dark-mode .swagger-ui .responses-inner {
color: #ffffff;
}
body.dark-mode .swagger-ui .responses-inner h4 {
color: #ffffff;
}
body.dark-mode .swagger-ui .response-container {
color: #ffffff;
}
body.dark-mode .swagger-ui .response-container .response-wrapper {
color: #ffffff;
}
body.dark-mode .swagger-ui .response-container .response-code {
color: #ffffff;
}
body.dark-mode .swagger-ui .response-container .response-description {
color: #ffffff;
}
body.dark-mode .swagger-ui .model {
color: #ffffff;
}
body.dark-mode .swagger-ui .model .property {
color: #ffffff;
}
body.dark-mode .swagger-ui .model .property .property-name {
color: #ffffff;
}
body.dark-mode .swagger-ui .model .property .property-description {
color: #ffffff;
}
body.dark-mode .swagger-ui .model .property .property-type {
color: #ffffff;
}
body.dark-mode .swagger-ui .model .property .required {
color: #ffffff;
}
body.dark-mode .swagger-ui .scroll-to-top {
color: #ffffff;
}
body.dark-mode .swagger-ui .opblock-tag-section {
color: #ffffff;
}
body.dark-mode .swagger-ui .servers-title {
color: #ffffff;
}
body.dark-mode .swagger-ui .servers {
color: #ffffff;
}
body.dark-mode .swagger-ui .servers li {
color: #ffffff;
}
body.dark-mode .swagger-ui .servers li label {
color: #ffffff;
}
body.dark-mode .swagger-ui .servers li select {
color: #ffffff;
background-color: #1a202c;
border-color: #4a5568;
}
body.dark-mode .swagger-ui .auth-wrapper {
color: #ffffff;
}
body.dark-mode .swagger-ui .auth-wrapper .auth-title {
color: #ffffff;
}
body.dark-mode .swagger-ui .auth-wrapper .auth-list {
color: #ffffff;
}
body.dark-mode .swagger-ui .auth-wrapper .auth-item {
color: #ffffff;
}
body.dark-mode .swagger-ui .auth-wrapper .auth-item label {
color: #ffffff;
}
/* 确保代码块内的文字也清晰可见 */
body.dark-mode .swagger-ui pre {
color: #ffffff;
}
body.dark-mode .swagger-ui code {
color: #ffffff;
}
/* 确保所有表单元素的文字颜色正确 */
body.dark-mode .swagger-ui form {
color: #ffffff;
}
body.dark-mode .swagger-ui form label {
color: #ffffff;
}
body.dark-mode .swagger-ui select {
color: #ffffff;
background-color: #1a202c;
border-color: #4a5568;
}
/* 适配可能的嵌套内容 */
body.dark-mode .swagger-ui .opblock-body .schema {
color: #ffffff;
}
body.dark-mode .swagger-ui .opblock-body .schema .title {
color: #ffffff;
}
body.dark-mode .swagger-ui .opblock-body .schema .required {
color: #ffffff;
}
/* 适配可能的按钮组 */
body.dark-mode .swagger-ui .btn-group {
color: #ffffff;
}
/* 适配可能的标签 */
body.dark-mode .swagger-ui .tag {
color: #ffffff;
}
/* 适配可能的警告和提示信息 */
body.dark-mode .swagger-ui .warning {
color: #ffffff;
}
body.dark-mode .swagger-ui .hint {
color: #ffffff;
}
/* 适配可能的表格内容 */
body.dark-mode .swagger-ui table {
color: #ffffff;
}
body.dark-mode .swagger-ui table th {
color: #ffffff;
}
body.dark-mode .swagger-ui table td {
color: #ffffff;
}
/* 响应式设计 */
@media (max-width: 768px) {
.topbar-controls {
flex-direction: column;
align-items: flex-end;
gap: 10px;
}
.theme-toggle-btn {
padding: 6px 10px;
font-size: 12px;
}
.theme-toggle-btn span {
display: none;
}
}

View File

@@ -3,462 +3,14 @@
<head>
<meta charset="UTF-8">
<title>DNS Server API 文档</title>
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/3.52.3/swagger-ui.css">
<style>
body {
margin: 0;
padding: 0;
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
}
.swagger-ui .topbar {
background-color: #2c3e50;
padding: 10px 0;
}
.swagger-ui .topbar .topbar-wrapper .link {
color: #ecf0f1;
}
</style>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" type="text/css" href="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/4.18.3/swagger-ui.css">
<link rel="stylesheet" type="text/css" href="css/style.css">
</head>
<body>
<div id="swagger-ui"></div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/3.52.3/swagger-ui-bundle.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/3.52.3/swagger-ui-standalone-preset.js"></script>
<script>
// 定义API文档的JSON
const swaggerDocument = {
"openapi": "3.0.0",
"info": {
"title": "DNS Server API",
"description": "DNS服务器API文档",
"version": "1.0.0",
"contact": {
"name": "API Support",
"email": "support@example.com"
},
"license": {
"name": "Apache 2.0",
"url": "http://www.apache.org/licenses/LICENSE-2.0.html"
}
},
"servers": [
{
"url": "http://localhost:8080/api",
"description": "本地开发服务器"
}
],
"paths": {
"/stats": {
"get": {
"summary": "获取系统统计信息",
"description": "获取DNS服务器和Shield的统计信息",
"tags": ["stats"],
"responses": {
"200": {
"description": "成功获取统计信息",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"dns": {
"type": "object",
"properties": {
"Queries": {"type": "integer"},
"Blocked": {"type": "integer"},
"Allowed": {"type": "integer"},
"Errors": {"type": "integer"},
"LastQuery": {"type": "string"},
"AvgResponseTime": {"type": "number"},
"TotalResponseTime": {"type": "number"},
"QueryTypes": {"type": "object"},
"SourceIPs": {"type": "object"},
"CpuUsage": {"type": "number"}
}
},
"shield": {"type": "object"},
"topQueryType": {"type": "string"},
"activeIPs": {"type": "integer"},
"avgResponseTime": {"type": "number"},
"cpuUsage": {"type": "number"},
"time": {"type": "string"}
}
}
}
}
},
"500": {
"description": "服务器内部错误",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {"type": "string"}
}
}
}
}
}
}
}
},
"/shield": {
"get": {
"summary": "获取Shield配置",
"description": "获取Shield的配置信息",
"tags": ["shield"],
"responses": {
"200": {
"description": "成功获取配置信息",
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
},
"500": {
"description": "服务器内部错误",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {"type": "string"}
}
}
}
}
}
}
},
"post": {
"summary": "更新Shield配置",
"description": "更新Shield的配置信息",
"tags": ["shield"],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object"
}
}
}
},
"responses": {
"200": {
"description": "成功更新配置",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"status": {"type": "string"}
}
}
}
}
},
"400": {
"description": "请求参数错误",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {"type": "string"}
}
}
}
}
},
"500": {
"description": "服务器内部错误",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {"type": "string"}
}
}
}
}
}
}
}
},
"/shield/blacklists": {
"get": {
"summary": "获取黑名单列表",
"description": "获取所有远程黑名单的列表",
"tags": ["shield"],
"responses": {
"200": {
"description": "成功获取黑名单列表",
"content": {
"application/json": {
"schema": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": {"type": "string"},
"url": {"type": "string"},
"enabled": {"type": "boolean"},
"lastUpdate": {"type": "string"},
"status": {"type": "string"}
}
}
}
}
}
},
"500": {
"description": "服务器内部错误",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {"type": "string"}
}
}
}
}
}
}
},
"post": {
"summary": "添加黑名单",
"description": "添加新的远程黑名单",
"tags": ["shield"],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["name", "url"],
"properties": {
"name": {"type": "string"},
"url": {"type": "string"},
"enabled": {"type": "boolean"}
}
}
}
}
},
"responses": {
"200": {
"description": "成功添加黑名单",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"status": {"type": "string"}
}
}
}
}
},
"400": {
"description": "请求参数错误",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {"type": "string"}
}
}
}
}
},
"500": {
"description": "服务器内部错误",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {"type": "string"}
}
}
}
}
}
}
},
"put": {
"summary": "更新黑名单",
"description": "更新黑名单的配置信息",
"tags": ["shield"],
"requestBody": {
"required": true,
"content": {
"application/json": {
"schema": {
"type": "object",
"required": ["name"],
"properties": {
"name": {"type": "string"},
"url": {"type": "string"},
"enabled": {"type": "boolean"}
}
}
}
}
},
"responses": {
"200": {
"description": "成功更新黑名单",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"status": {"type": "string"}
}
}
}
}
},
"400": {
"description": "请求参数错误",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {"type": "string"}
}
}
}
}
},
"404": {
"description": "黑名单不存在",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {"type": "string"}
}
}
}
}
},
"500": {
"description": "服务器内部错误",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {"type": "string"}
}
}
}
}
}
}
}
},
"/shield/blacklists/{name}": {
"delete": {
"summary": "删除黑名单",
"description": "根据名称删除指定的远程黑名单",
"tags": ["shield"],
"parameters": [
{
"name": "name",
"in": "path",
"required": true,
"schema": {
"type": "string"
},
"description": "黑名单名称"
}
],
"responses": {
"200": {
"description": "成功删除黑名单",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"status": {"type": "string"}
}
}
}
}
},
"404": {
"description": "黑名单不存在",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {"type": "string"}
}
}
}
}
},
"500": {
"description": "服务器内部错误",
"content": {
"application/json": {
"schema": {
"type": "object",
"properties": {
"error": {"type": "string"}
}
}
}
}
}
}
}
}
},
"tags": [
{
"name": "stats",
"description": "统计信息相关API"
},
{
"name": "shield",
"description": "Shield功能相关API"
}
]
};
// 初始化Swagger UI
window.onload = function() {
const ui = SwaggerUIBundle({
spec: swaggerDocument,
dom_id: '#swagger-ui',
deepLinking: true,
presets: [
SwaggerUIBundle.presets.apis,
SwaggerUIStandalonePreset
],
plugins: [
SwaggerUIBundle.plugins.DownloadUrl
],
layout: "StandaloneLayout"
});
window.ui = ui;
};
</script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/4.18.3/swagger-ui-bundle.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/swagger-ui/4.18.3/swagger-ui-standalone-preset.js"></script>
<script src="js/index.js"></script>
</body>
</html>

1001
static/api/js/index.js Normal file

File diff suppressed because it is too large Load Diff

62
static/css/animation.css Normal file
View File

@@ -0,0 +1,62 @@
@layer utilities {
.content-auto {
content-visibility: auto;
}
.card-shadow {
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.sidebar-item-active {
background-color: rgba(22, 93, 255, 0.1);
color: #165DFF;
border-right: 4px solid #165DFF;
}
}
/* 服务器状态组件光晕效果 */
.glow-effect {
animation: pulse 2s ease-in-out;
}
@keyframes pulse {
0% {
box-shadow: 0 0 0 0 rgba(41, 128, 185, 0.4);
}
70% {
box-shadow: 0 0 0 10px rgba(41, 128, 185, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(41, 128, 185, 0);
}
}
/* 服务器状态组件样式优化 */
.server-status-widget {
min-width: 170px;
transition: all 0.3s ease;
}
.server-status-widget:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
/* 加载状态样式 */
.status-loading {
animation: status-pulse 1.5s ease-in-out infinite;
}
/* 状态脉冲动画 */
@keyframes status-pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.7;
}
}
/* 保存按钮状态样式 */
#save-blacklist-status {
transition: all 0.3s ease-in-out;
}

View File

@@ -1043,18 +1043,6 @@ tr:hover {
font-size: 0.9rem;
}
}
/* 加载动画 */
.loading {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid #f3f3f3;
border-top: 3px solid #3498db;
border-radius: 50%;
animation: spin 1s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }

View File

@@ -8,224 +8,12 @@
<script src="https://cdn.tailwindcss.com"></script>
<!-- Font Awesome -->
<link href="https://cdn.staticfile.org/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
<!-- Chart.js -->
<!-- Chart.js 本地备用 -->
<script src="js/vendor/chart.umd.min.js" onerror="this.onerror=null;this.src='js/chart.umd.min.js';"></script>
<!-- Tailwind 配置 -->
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#165DFF',
secondary: '#36CFFB',
success: '#00B42A',
warning: '#FF7D00',
danger: '#F53F3F',
info: '#86909C',
dark: '#1D2129',
light: '#F2F3F5',
},
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
},
},
}
}
</script>
<script src="js/vendor/tailwind.js"></script>
<!-- 自定义工具类 -->
<style type="text/tailwindcss">
@layer utilities {
.content-auto {
content-visibility: auto;
}
.card-shadow {
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
}
.sidebar-item-active {
background-color: rgba(22, 93, 255, 0.1);
color: #165DFF;
border-right: 4px solid #165DFF;
}
}
</style>
<!-- 数字光晕效果样式 -->
<style>
/* 数字光晕效果基础样式 */
.number-glow {
animation: glow-pulse 2s ease-in-out;
}
/* 服务器状态组件光晕效果 */
.glow-effect {
animation: pulse 2s ease-in-out;
}
@keyframes pulse {
0% {
box-shadow: 0 0 0 0 rgba(41, 128, 185, 0.4);
}
70% {
box-shadow: 0 0 0 10px rgba(41, 128, 185, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(41, 128, 185, 0);
}
}
/* 服务器状态组件样式优化 */
.server-status-widget {
min-width: 170px;
transition: all 0.3s ease;
}
.server-status-widget:hover {
transform: translateY(-2px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
/* 蓝色光晕效果 */
.number-glow-blue {
animation: glow-blue 2s ease-in-out;
}
/* 红色光晕效果 */
.number-glow-red {
animation: glow-red 2s ease-in-out;
}
/* 绿色光晕效果 */
.number-glow-green {
animation: glow-green 2s ease-in-out;
}
/* 黄色光晕效果 */
.number-glow-yellow {
animation: glow-yellow 2s ease-in-out;
}
/* 光晕动画定义 */
@keyframes glow-pulse {
0% {
text-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
}
50% {
text-shadow: 0 0 20px rgba(0, 0, 0, 0.7);
}
100% {
text-shadow: 0 0 5px rgba(0, 0, 0, 0.3);
}
}
@keyframes glow-blue {
0% {
text-shadow: 0 0 5px rgba(59, 130, 246, 0.3);
}
50% {
text-shadow: 0 0 20px rgba(59, 130, 246, 0.7);
}
100% {
text-shadow: 0 0 5px rgba(59, 130, 246, 0.3);
}
}
@keyframes glow-red {
0% {
text-shadow: 0 0 5px rgba(239, 68, 68, 0.3);
}
50% {
text-shadow: 0 0 20px rgba(239, 68, 68, 0.7);
}
100% {
text-shadow: 0 0 5px rgba(239, 68, 68, 0.3);
}
}
@keyframes glow-green {
0% {
text-shadow: 0 0 5px rgba(16, 185, 129, 0.3);
}
50% {
text-shadow: 0 0 20px rgba(16, 185, 129, 0.7);
}
100% {
text-shadow: 0 0 5px rgba(16, 185, 129, 0.3);
}
}
@keyframes glow-yellow {
0% {
text-shadow: 0 0 5px rgba(250, 204, 21, 0.3);
}
50% {
text-shadow: 0 0 20px rgba(250, 204, 21, 0.7);
}
100% {
text-shadow: 0 0 5px rgba(250, 204, 21, 0.3);
}
}
/* 状态过渡效果 */
.status-transition {
transition: all 0.3s ease-in-out;
}
/* 状态淡入动画 */
@keyframes status-fade-in {
0% {
opacity: 0;
transform: translateY(-5px);
}
100% {
opacity: 1;
transform: translateY(0);
}
}
/* 状态淡出动画 */
@keyframes status-fade-out {
0% {
opacity: 1;
transform: translateY(0);
}
100% {
opacity: 0;
transform: translateY(-5px);
}
}
/* 淡入类 */
.status-fade-in {
animation: status-fade-in 0.3s ease-in-out forwards;
}
/* 淡出类 */
.status-fade-out {
animation: status-fade-out 0.3s ease-in-out forwards;
}
/* 加载状态样式 */
.status-loading {
animation: status-pulse 1.5s ease-in-out infinite;
}
/* 状态脉冲动画 */
@keyframes status-pulse {
0%, 100% {
opacity: 1;
}
50% {
opacity: 0.7;
}
}
/* 保存按钮状态样式 */
#save-blacklist-status {
transition: all 0.3s ease-in-out;
}
</style>
<style type="text/tailwindcss" src="css/index.css"></style>
</head>
<body class="bg-gray-50 text-dark font-sans">
<div class="flex h-screen overflow-hidden">
@@ -855,7 +643,7 @@
<th class="text-left py-3 px-4 text-sm font-medium text-gray-500">名称</th>
<th class="text-left py-3 px-4 text-sm font-medium text-gray-500">URL</th>
<th class="text-center py-3 px-4 text-sm font-medium text-gray-500">状态</th>
<th class="text-center py-3 px-4 text-sm font-medium text-gray-500">更新状态</th>
<th class="text-center py-3 px-4 text-sm font-medium text-gray-500"></th>
<th class="text-right py-3 px-4 text-sm font-medium text-gray-500">操作</th>
</tr>
</thead>

View File

@@ -758,14 +758,29 @@ function updateStatsCards(stats) {
queryTypePercentage = stats[0].queryTypePercentage || 0;
activeIPs = stats[0].activeIPs || 0;
activeIPsPercentage = stats[0].activeIPsPercentage || 0;
}
// 存储正在进行的动画状态,避免动画重叠
const animationInProgress = {};
// 为数字元素添加翻页滚动特效
function animateValue(elementId, newValue) {
const element = document.getElementById(elementId);
if (!element) return;
// 如果该元素正在进行动画,取消当前动画并立即更新值
if (animationInProgress[elementId]) {
// 清除之前可能设置的定时器
clearTimeout(animationInProgress[elementId].timeout1);
clearTimeout(animationInProgress[elementId].timeout2);
clearTimeout(animationInProgress[elementId].timeout3);
// 立即设置新值,避免显示错乱
const formattedNewValue = formatNumber(newValue);
element.innerHTML = formattedNewValue;
return;
}
const oldValue = parseInt(element.textContent.replace(/,/g, '')) || 0;
const formattedNewValue = formatNumber(newValue);
@@ -776,124 +791,152 @@ function updateStatsCards(stats) {
// 先移除可能存在的光晕效果类
element.classList.remove('number-glow', 'number-glow-blue', 'number-glow-red', 'number-glow-green', 'number-glow-yellow');
element.classList.remove('number-glow-dark-blue', 'number-glow-dark-red', 'number-glow-dark-green', 'number-glow-dark-yellow');
// 保存原始样式和内容
// 保存原始样式
const originalStyle = element.getAttribute('style') || '';
const originalContent = element.innerHTML;
// 配置翻页容器样式
const containerStyle = `
position: relative;
display: inline-block;
overflow: hidden;
height: ${element.offsetHeight}px;
width: ${element.offsetWidth}px;
`;
// 创建翻页容器
const flipContainer = document.createElement('div');
flipContainer.style.cssText = containerStyle;
flipContainer.className = 'number-flip-container';
// 创建旧值元素
const oldValueElement = document.createElement('div');
oldValueElement.textContent = originalContent;
oldValueElement.style.cssText = `
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
transition: transform 400ms ease-in-out;
transform-origin: center;
`;
// 创建新值元素
const newValueElement = document.createElement('div');
newValueElement.textContent = formattedNewValue;
newValueElement.style.cssText = `
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
transition: transform 400ms ease-in-out;
transform-origin: center;
transform: translateY(100%);
`;
// 复制原始元素的样式到新元素
const computedStyle = getComputedStyle(element);
[oldValueElement, newValueElement].forEach(el => {
el.style.fontSize = computedStyle.fontSize;
el.style.fontWeight = computedStyle.fontWeight;
el.style.color = computedStyle.color;
el.style.fontFamily = computedStyle.fontFamily;
el.style.textAlign = computedStyle.textAlign;
el.style.lineHeight = computedStyle.lineHeight;
});
// 替换原始元素的内容
element.textContent = '';
flipContainer.appendChild(oldValueElement);
flipContainer.appendChild(newValueElement);
element.appendChild(flipContainer);
// 启动翻页动画
setTimeout(() => {
oldValueElement.style.transform = 'translateY(-100%)';
newValueElement.style.transform = 'translateY(0)';
}, 50);
// 动画结束后,恢复原始元素
setTimeout(() => {
// 清理并设置最终值
try {
// 配置翻页容器样式,确保与原始元素大小完全一致
const containerStyle =
'position: relative; '
+ 'display: ' + computedStyle.display + '; '
+ 'overflow: hidden; '
+ 'height: ' + element.offsetHeight + 'px; '
+ 'width: ' + element.offsetWidth + 'px; '
+ 'margin: ' + computedStyle.margin + '; '
+ 'padding: ' + computedStyle.padding + '; '
+ 'box-sizing: ' + computedStyle.boxSizing + '; '
+ 'line-height: ' + computedStyle.lineHeight + ';';
// 创建翻页容器
const flipContainer = document.createElement('div');
flipContainer.style.cssText = containerStyle;
flipContainer.className = 'number-flip-container';
// 创建旧值元素
const oldValueElement = document.createElement('div');
oldValueElement.textContent = element.textContent;
oldValueElement.style.cssText =
'position: absolute; ' +
'top: 0; ' +
'left: 0; ' +
'width: 100%; ' +
'height: 100%; ' +
'display: flex; ' +
'align-items: center; ' +
'justify-content: center; ' +
'transition: transform 400ms ease-in-out; ' +
'transform-origin: center;';
// 创建新值元素
const newValueElement = document.createElement('div');
newValueElement.textContent = formattedNewValue;
newValueElement.style.cssText =
'position: absolute; ' +
'top: 0; ' +
'left: 0; ' +
'width: 100%; ' +
'height: 100%; ' +
'display: flex; ' +
'align-items: center; ' +
'justify-content: center; ' +
'transition: transform 400ms ease-in-out; ' +
'transform-origin: center; ' +
'transform: translateY(100%);';
// 复制原始元素的样式到新元素,确保大小完全一致
const computedStyle = getComputedStyle(element);
[oldValueElement, newValueElement].forEach(el => {
el.style.fontSize = computedStyle.fontSize;
el.style.fontWeight = computedStyle.fontWeight;
el.style.color = computedStyle.color;
el.style.fontFamily = computedStyle.fontFamily;
el.style.textAlign = computedStyle.textAlign;
el.style.lineHeight = computedStyle.lineHeight;
el.style.width = '100%';
el.style.height = '100%';
el.style.margin = '0';
el.style.padding = '0';
el.style.boxSizing = 'border-box';
el.style.whiteSpace = computedStyle.whiteSpace;
el.style.overflow = 'hidden';
el.style.textOverflow = 'ellipsis';
// 确保垂直对齐正确
el.style.verticalAlign = 'middle';
});
// 替换原始元素的内容
element.textContent = '';
flipContainer.appendChild(oldValueElement);
flipContainer.appendChild(newValueElement);
element.appendChild(flipContainer);
// 标记该元素正在进行动画
animationInProgress[elementId] = {};
// 启动翻页动画
animationInProgress[elementId].timeout1 = setTimeout(() => {
if (oldValueElement && newValueElement) {
oldValueElement.style.transform = 'translateY(-100%)';
newValueElement.style.transform = 'translateY(0)';
}
}, 50);
// 动画结束后,恢复原始元素
animationInProgress[elementId].timeout2 = setTimeout(() => {
try {
// 清理并设置最终值
element.innerHTML = formattedNewValue;
if (originalStyle) {
element.setAttribute('style', originalStyle);
} else {
element.removeAttribute('style');
}
// 添加当前卡片颜色的深色光晕效果
const card = element.closest('.stat-card, .bg-blue-50, .bg-red-50, .bg-green-50, .bg-yellow-50');
let glowColorClass = '';
if (card) {
if (card.classList.contains('bg-blue-50') || card.id.includes('total') || card.id.includes('response')) {
glowColorClass = 'number-glow-dark-blue';
} else if (card.classList.contains('bg-red-50') || card.id.includes('blocked')) {
glowColorClass = 'number-glow-dark-red';
} else if (card.classList.contains('bg-green-50') || card.id.includes('allowed') || card.id.includes('active')) {
glowColorClass = 'number-glow-dark-green';
} else if (card.classList.contains('bg-yellow-50') || card.id.includes('error') || card.id.includes('cpu')) {
glowColorClass = 'number-glow-dark-yellow';
}
}
if (glowColorClass) {
element.classList.add(glowColorClass);
// 2秒后移除光晕效果
animationInProgress[elementId].timeout3 = setTimeout(() => {
element.classList.remove('number-glow-dark-blue', 'number-glow-dark-red', 'number-glow-dark-green', 'number-glow-dark-yellow');
}, 2000);
}
} catch (e) {
console.error('更新元素失败:', e);
} finally {
// 清除动画状态标记
delete animationInProgress[elementId];
}
}, 450);
} catch (e) {
console.error('创建动画失败:', e);
// 出错时直接设置值
element.innerHTML = formattedNewValue;
if (originalStyle) {
element.setAttribute('style', originalStyle);
} else {
element.removeAttribute('style');
}
// 添加当前卡片颜色的深色光晕效果
// 根据父级卡片类型确定光晕颜色
const card = element.closest('.stat-card, .bg-blue-50, .bg-red-50, .bg-green-50, .bg-yellow-50');
let glowColorClass = '';
// 使用更精准的卡片颜色检测
if (card) {
// 根据卡片类名确定深色光晕颜色
if (card.classList.contains('bg-blue-50') || card.id.includes('total') || card.id.includes('response')) {
// 蓝色卡片 - 深蓝色光晕
glowColorClass = 'number-glow-dark-blue';
} else if (card.classList.contains('bg-red-50') || card.id.includes('blocked')) {
// 红色卡片 - 深红色光晕
glowColorClass = 'number-glow-dark-red';
} else if (card.classList.contains('bg-green-50') || card.id.includes('allowed') || card.id.includes('active')) {
// 绿色卡片 - 深绿色光晕
glowColorClass = 'number-glow-dark-green';
} else if (card.classList.contains('bg-yellow-50') || card.id.includes('error') || card.id.includes('cpu')) {
// 黄色卡片 - 深黄色光晕
glowColorClass = 'number-glow-dark-yellow';
}
}
// 如果确定了光晕颜色类,则添加它
if (glowColorClass) {
element.classList.add(glowColorClass);
// 2秒后移除光晕效果
setTimeout(() => {
element.classList.remove('number-glow-dark-blue', 'number-glow-dark-red', 'number-glow-dark-green', 'number-glow-dark-yellow');
}, 2000);
}
}, 450);
// 清除动画状态标记
delete animationInProgress[elementId];
}
}
// 更新百分比元素的函数
@@ -901,13 +944,37 @@ function updateStatsCards(stats) {
const element = document.getElementById(elementId);
if (!element) return;
element.style.opacity = '0';
element.style.transition = 'opacity 200ms ease-out';
// 检查是否有正在进行的动画
if (animationInProgress[elementId + '_percent']) {
clearTimeout(animationInProgress[elementId + '_percent']);
}
setTimeout(() => {
element.textContent = value;
element.style.opacity = '1';
}, 200);
try {
element.style.opacity = '0';
element.style.transition = 'opacity 200ms ease-out';
// 保存定时器ID便于后续可能的取消
animationInProgress[elementId + '_percent'] = setTimeout(() => {
try {
element.textContent = value;
element.style.opacity = '1';
} catch (e) {
console.error('更新百分比元素失败:', e);
} finally {
// 清除动画状态标记
delete animationInProgress[elementId + '_percent'];
}
}, 200);
} catch (e) {
console.error('设置百分比动画失败:', e);
// 出错时直接设置值
try {
element.textContent = value;
element.style.opacity = '1';
} catch (e2) {
console.error('直接更新百分比元素也失败:', e2);
}
}
}
// 平滑更新数量显示
@@ -917,10 +984,14 @@ function updateStatsCards(stats) {
animateValue('error-queries', errorQueries);
animateValue('active-ips', activeIPs);
// 平滑更新文本和百分比
updatePercentage('top-query-type', topQueryType);
updatePercentage('query-type-percentage', `${Math.round(queryTypePercentage)}%`);
updatePercentage('active-ips-percent', `${Math.round(activeIPsPercentage)}%`);
// 直接更新文本和百分比,移除动画效果
const topQueryTypeElement = document.getElementById('top-query-type');
const queryTypePercentageElement = document.getElementById('query-type-percentage');
const activeIpsPercentElement = document.getElementById('active-ips-percent');
if (topQueryTypeElement) topQueryTypeElement.textContent = topQueryType;
if (queryTypePercentageElement) queryTypePercentageElement.textContent = `${Math.round(queryTypePercentage)}%`;
if (activeIpsPercentElement) activeIpsPercentElement.textContent = `${Math.round(activeIPsPercentage)}%`;
// 计算并平滑更新百分比
if (totalQueries > 0) {
@@ -962,11 +1033,9 @@ function updateTopBlockedTable(domains) {
// 如果没有有效数据,提供示例数据
if (tableData.length === 0) {
tableData = [
{ name: 'example1.com', count: 150 },
{ name: 'example2.com', count: 130 },
{ name: 'example3.com', count: 120 },
{ name: 'example4.com', count: 110 },
{ name: 'example5.com', count: 100 }
{ name: '---.---.---', count: '---' },
{ name: '---.---.---', count: '---' },
{ name: '---.---.---', count: '---' }
];
console.log('使用示例数据填充Top屏蔽域名表格');
}
@@ -1016,11 +1085,11 @@ function updateRecentBlockedTable(domains) {
if (tableData.length === 0) {
const now = Date.now();
tableData = [
{ name: 'recent1.com', timestamp: now - 5 * 60 * 1000, type: '广告' },
{ name: 'recent2.com', timestamp: now - 15 * 60 * 1000, type: '恶意' },
{ name: 'recent3.com', timestamp: now - 30 * 60 * 1000, type: '广告' },
{ name: 'recent4.com', timestamp: now - 45 * 60 * 1000, type: '追踪' },
{ name: 'recent5.com', timestamp: now - 60 * 60 * 1000, type: '恶意' }
{ name: '---.---.---', timestamp: now - 5 * 60 * 1000, type: '广告' },
{ name: '---.---.---', timestamp: now - 15 * 60 * 1000, type: '恶意' },
{ name: '---.---.---', timestamp: now - 30 * 60 * 1000, type: '广告' },
{ name: '---.---.---', timestamp: now - 45 * 60 * 1000, type: '追踪' },
{ name: '---.---.---', timestamp: now - 60 * 60 * 1000, type: '恶意' }
];
console.log('使用示例数据填充最近屏蔽域名表格');
}
@@ -1073,11 +1142,11 @@ function updateTopClientsTable(clients) {
// 如果没有有效数据,提供示例数据
if (tableData.length === 0) {
tableData = [
{ ip: '192.168.1.100', count: 120 },
{ ip: '192.168.1.101', count: 95 },
{ ip: '192.168.1.102', count: 80 },
{ ip: '192.168.1.103', count: 65 },
{ ip: '192.168.1.104', count: 50 }
{ ip: '---.---.---', count: '---' },
{ ip: '---.---.---', count: '---' },
{ ip: '---.---.---', count: '---' },
{ ip: '---.---.---', count: '---' },
{ ip: '---.---.---', count: '---' }
];
console.log('使用示例数据填充TOP客户端表格');
}

19
static/js/vendor/tailwind.js vendored Normal file
View File

@@ -0,0 +1,19 @@
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#165DFF',
secondary: '#36CFFB',
success: '#00B42A',
warning: '#FF7D00',
danger: '#F53F3F',
info: '#86909C',
dark: '#1D2129',
light: '#F2F3F5',
},
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
},
},
}
}