更新
This commit is contained in:
488
static/api/css/style.css
Normal file
488
static/api/css/style.css
Normal 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;
|
||||
}
|
||||
}
|
||||
@@ -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
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
62
static/css/animation.css
Normal 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;
|
||||
}
|
||||
@@ -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); }
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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
19
static/js/vendor/tailwind.js
vendored
Normal 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'],
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user