更新
This commit is contained in:
@@ -44,7 +44,7 @@
|
||||
{
|
||||
"name": "CNList",
|
||||
"url": "https://gitea.amazehome.xyz/AMAZEHOME/hosts-and-filters/raw/branch/main/list/china.list",
|
||||
"enabled": true
|
||||
"enabled": false
|
||||
}
|
||||
],
|
||||
"updateInterval": 3600,
|
||||
|
||||
5
data/hosts.txt
Normal file
5
data/hosts.txt
Normal file
@@ -0,0 +1,5 @@
|
||||
# DNS Server Hosts File
|
||||
# Generated by DNS Server
|
||||
|
||||
127.0.0.1 localhost
|
||||
::1 localhost
|
||||
@@ -0,0 +1 @@
|
||||
<!doctype html><html lang="en"><head><title>Example Domain</title><meta name="viewport" content="width=device-width, initial-scale=1"><style>body{background:#eee;width:60vw;margin:15vh auto;font-family:system-ui,sans-serif}h1{font-size:1.5em}div{opacity:0.8}a:link,a:visited{color:#348}</style><body><div><h1>Example Domain</h1><p>This domain is for use in documentation examples without needing permission. Avoid use in operations.<p><a href="https://iana.org/domains/example">Learn more</a></div></body></html>
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
1
data/rules.txt
Normal file
1
data/rules.txt
Normal file
@@ -0,0 +1 @@
|
||||
||example.org^
|
||||
5
data/shield_stats.json
Normal file
5
data/shield_stats.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"blockedDomainsCount": {},
|
||||
"resolvedDomainsCount": {},
|
||||
"lastSaved": "2025-11-29T23:38:28.511055298+08:00"
|
||||
}
|
||||
5883
data/stats.json
Normal file
5883
data/stats.json
Normal file
File diff suppressed because it is too large
Load Diff
BIN
dns-server
Executable file
BIN
dns-server
Executable file
Binary file not shown.
105234
logs/dns-server.log
Normal file
105234
logs/dns-server.log
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
<!doctype html><html lang="en"><head><title>Example Domain</title><meta name="viewport" content="width=device-width, initial-scale=1"><style>body{background:#eee;width:60vw;margin:15vh auto;font-family:system-ui,sans-serif}h1{font-size:1.5em}div{opacity:0.8}a:link,a:visited{color:#348}</style><body><div><h1>Example Domain</h1><p>This domain is for use in documentation examples without needing permission. Avoid use in operations.<p><a href="https://iana.org/domains/example">Learn more</a></div></body></html>
|
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
5
shield_stats.json
Normal file
5
shield_stats.json
Normal file
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"blockedDomainsCount": {},
|
||||
"resolvedDomainsCount": {},
|
||||
"lastSaved": "2025-11-29T02:08:50.6341349+08:00"
|
||||
}
|
||||
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