更新web
This commit is contained in:
488
staticbak/static/api/css/style.css
Normal file
488
staticbak/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;
|
||||
}
|
||||
}
|
||||
16
staticbak/static/api/index.html
Normal file
16
staticbak/static/api/index.html
Normal file
@@ -0,0 +1,16 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>DNS Server API 文档</title>
|
||||
<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/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>
|
||||
1791
staticbak/static/api/js/index.js
Normal file
1791
staticbak/static/api/js/index.js
Normal file
File diff suppressed because it is too large
Load Diff
62
staticbak/static/css/animation.css
Normal file
62
staticbak/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;
|
||||
}
|
||||
1157
staticbak/static/css/style.css
Normal file
1157
staticbak/static/css/style.css
Normal file
File diff suppressed because it is too large
Load Diff
9
staticbak/static/css/vendor/all.min.css
vendored
Normal file
9
staticbak/static/css/vendor/all.min.css
vendored
Normal file
File diff suppressed because one or more lines are too long
BIN
staticbak/static/css/webfonts/fa-solid-900.woff2
Normal file
BIN
staticbak/static/css/webfonts/fa-solid-900.woff2
Normal file
Binary file not shown.
535
staticbak/static/domain-info/domains/domain-info.json
Normal file
535
staticbak/static/domain-info/domains/domain-info.json
Normal file
@@ -0,0 +1,535 @@
|
||||
{
|
||||
"timeUpdated": "2025-03-17T10:05:02.622Z",
|
||||
"categories": {
|
||||
"0": "官方网站",
|
||||
"1": "搜索引擎",
|
||||
"2": "CDN",
|
||||
"3": "新闻资讯",
|
||||
"4": "购物网站",
|
||||
"5": "社交网站",
|
||||
"6": "工具网站",
|
||||
"7": "社交工具",
|
||||
"8": "视频网站",
|
||||
"9": "混合内容",
|
||||
"10": "博客/自媒体",
|
||||
"11": "论坛/社区",
|
||||
"12": "地图",
|
||||
"13": "涉政网站",
|
||||
"14": "政府网站",
|
||||
"15": "政务网站",
|
||||
"16": "组织网站",
|
||||
"17": "事业单位",
|
||||
"18": "政务服务",
|
||||
"19": "网络邮件",
|
||||
"20": "云服务",
|
||||
"21": "网络存储",
|
||||
"22": "网络服务",
|
||||
"23": "CDN网络分发平台",
|
||||
"24": "支付平台",
|
||||
"25": "其他"
|
||||
},
|
||||
"domains": {
|
||||
"网易": {
|
||||
"网易163": {
|
||||
"name": "网易163",
|
||||
"categoryId": 3,
|
||||
"url": "http://www.163.com/",
|
||||
"icon": "https://www.163.com/favicon.ico"
|
||||
},
|
||||
"网易163免费邮": {
|
||||
"name": "网易163免费邮",
|
||||
"categoryId": 18,
|
||||
"url": "https://mail.163.com/",
|
||||
"icon": "https://mail.163.com/favicon.ico"
|
||||
},
|
||||
"网易126免费邮": {
|
||||
"name": "网易126免费邮",
|
||||
"categoryId": 18,
|
||||
"url": "https://www.126.com/",
|
||||
"icon": "https://www.126.com/favicon.ico"
|
||||
},
|
||||
"网易有道词典": {
|
||||
"name": "网易有道词典",
|
||||
"categoryId": 0,
|
||||
"url": "https://dict.youdao.com/",
|
||||
"icon": "https://dict.youdao.com/favicon.ico"
|
||||
},
|
||||
"网易uu加速器":{
|
||||
"name": "网易uu加速器",
|
||||
"categoryId": 0,
|
||||
"url": {
|
||||
"1": "https://uu.163.com/",
|
||||
"2": "https://log.uu.163.com/"
|
||||
}
|
||||
},
|
||||
"company": "网易股份有限公司"
|
||||
},
|
||||
"百度": {
|
||||
"百度搜索": {
|
||||
"name": "百度搜索",
|
||||
"categoryId": 0,
|
||||
"url": "https://www.baidu.com/",
|
||||
"icon": "https://www.baidu.com/favicon.ico"
|
||||
},
|
||||
"百度百科": {
|
||||
"name": "百度百科",
|
||||
"categoryId": 19,
|
||||
"url": "https://baike.baidu.com/",
|
||||
"icon": "https://baike.baidu.com/favicon.ico"
|
||||
},
|
||||
"百度地图": {
|
||||
"name": "百度地图",
|
||||
"categoryId": 7,
|
||||
"url": "https://map.baidu.com/",
|
||||
"icon": "https://map.baidu.com/favicon.ico"
|
||||
},
|
||||
"百度知道": {
|
||||
"name": "百度知道",
|
||||
"categoryId": 12,
|
||||
"url": "https://zhidao.baidu.com/",
|
||||
"icon": "https://zhidao.baidu.com/favicon.ico"
|
||||
},
|
||||
"company": "北京百度网讯科技有限公司"
|
||||
},
|
||||
"阿里云": {
|
||||
"阿里云": {
|
||||
"name": "阿里云",
|
||||
"categoryId": 22,
|
||||
"url": "https://www.aliyun.com/",
|
||||
"icon": "https://img.alicdn.com/tfs/TB1_ZXuNcfpK1RjSZFOXXa6nFXa-32-32.ico"
|
||||
},
|
||||
"阿里云API服务": {
|
||||
"name": "阿里云API服务",
|
||||
"categoryId": 22,
|
||||
"url": "aliyuncs.com",
|
||||
"icon": "https://img.alicdn.com/tfs/TB1_ZXuNcfpK1RjSZFOXXa6nFXa-32-32.ico"
|
||||
},
|
||||
"阿里云DNS": {
|
||||
"name": "阿里云DNS",
|
||||
"categoryId": 22,
|
||||
"url": {
|
||||
"1": "alidns.com",
|
||||
"2": "alibabadns.com"
|
||||
},
|
||||
"icon": "https://img.alicdn.com/tfs/TB1_ZXuNcfpK1RjSZFOXXa6nFXa-32-32.ico"
|
||||
},
|
||||
"支付宝": {
|
||||
"name": "支付宝",
|
||||
"categoryId": 24,
|
||||
"url": "https://www.alipay.com/",
|
||||
"icon": "https://www.alipay.com/favicon.ico"
|
||||
},
|
||||
"淘宝": {
|
||||
"name": "淘宝",
|
||||
"categoryId": 4,
|
||||
"url": "https://www.taobao.com/",
|
||||
"icon": "https://www.taobao.com/favicon.ico"
|
||||
},
|
||||
"天猫": {
|
||||
"name": "天猫",
|
||||
"categoryId": 4,
|
||||
"url": "https://www.tmall.com/",
|
||||
"icon": "https://www.tmall.com/favicon.ico"
|
||||
},
|
||||
"阿里云镜像源":{
|
||||
"name": "阿里巴巴开源镜像站",
|
||||
"categoryId": 0,
|
||||
"url": {
|
||||
"1": "https://mirrors.aliyun.com/"
|
||||
},
|
||||
"icon": "https://www.aliyun.com/favicon.ico"
|
||||
},
|
||||
"company": "阿里云计算有限公司/阿里巴巴集团"
|
||||
},
|
||||
"UC":{
|
||||
"UC浏览器官网": {
|
||||
"name": "UC浏览器官网",
|
||||
"categoryId": 4,
|
||||
"url": {
|
||||
"1": "www.uc.cn",
|
||||
"2": "https://www.uc.com/"
|
||||
},
|
||||
"icon": "https://www.uc.cn/favicon.ico"
|
||||
},
|
||||
"company": "广州市动景计算机科技有限公司"
|
||||
},
|
||||
"腾讯": {
|
||||
"微信": {
|
||||
"name": "微信",
|
||||
"categoryId": 7,
|
||||
"url": {
|
||||
"1": "https://wx.qq.com/",
|
||||
"2": "https://weixin.qq.com/",
|
||||
"3": "https://res.wx.qq.com/",
|
||||
"4": "dns.weixin.qq.com",
|
||||
"5": "pc.weixin.qq.com"
|
||||
},
|
||||
"icon": "https://res.wx.qq.com/a/wx_fed/assets/res/NTI4MWU5.ico"
|
||||
},
|
||||
"WeChat": {
|
||||
"name": "WeChat",
|
||||
"categoryId": 7,
|
||||
"url": {
|
||||
"1":"wechat.com",
|
||||
"2": "support.wechat.com",
|
||||
"3": "www.wechat.com"
|
||||
},
|
||||
|
||||
"icon": "https://wechat.com/favicon.ico"
|
||||
},
|
||||
"微信开放平台": {
|
||||
"name": "微信开放平台",
|
||||
"categoryId": 24,
|
||||
"url": "https://open.weixin.qq.com/",
|
||||
"icon": "https://open.weixin.qq.com/favicon.ico"
|
||||
},
|
||||
"微信支付": {
|
||||
"name": "微信支付商户平台",
|
||||
"categoryId": 24,
|
||||
"url": {"1": "pay.weixin.qq.com",
|
||||
"2": "pay.wechatpay.cn",
|
||||
"3": "act.weixin.qq.com",
|
||||
"4": "api.wechatpay.cn",
|
||||
"5": "api.mch.weixin.qq.com",
|
||||
"6": "api2.mch.weixin.qq.com",
|
||||
"7": "api.wechatpay.cn",
|
||||
"8": "api2.wechatpay.cn",
|
||||
"9": "payapp.weixin.qq.com",
|
||||
"10": "payapp.wechatpay.cn",
|
||||
"11": "fraud.mch.weixin.qq.com",
|
||||
"12": "fraud.wechatpay.cn",
|
||||
"13": "action.weixin.qq.com",
|
||||
"14": "action.wechatpay.cn",
|
||||
"15": "wechatpay.cn"
|
||||
},
|
||||
|
||||
"icon": "https://gtimg.wechatpay.cn/core/favicon.ico"
|
||||
},
|
||||
"微信支付海外版": {
|
||||
"name": "微信支付海外版",
|
||||
"categoryId": 24,
|
||||
"url": {"1": "https://pay.wechatpay.global/",
|
||||
"2": "apihk.mch.weixin.qq.com",
|
||||
"3": "apius.mch.weixin.qq.com"
|
||||
},
|
||||
"icon": "https://gtimg.wechatpay.cn/core/favicon.ico"
|
||||
},
|
||||
"微信CDN": {
|
||||
"name": "微信CDN和日志等资源",
|
||||
"categoryId": 23,
|
||||
"url": {"1": "https://gtimg.wechatpay.cn/",
|
||||
"2": "log.wechatpay.cn",
|
||||
"3": "newres.wechat.com",
|
||||
"4": "res.wx.qq.com",
|
||||
"5": "sh.servicewechat.com",
|
||||
"6": "res.servicewechat.com"
|
||||
},
|
||||
"icon": "https://gtimg.wechatpay.cn/core/favicon.ico"
|
||||
},
|
||||
"腾讯下载加速网络CDN": {
|
||||
"name": "腾讯下载加速网络CDN",
|
||||
"categoryId": 23,
|
||||
"url": {"1": "dldir1v6.qq.com",
|
||||
"2": "dldir1.qq.com",
|
||||
"3": "dldir2.qq.com",
|
||||
"4": "dldir3.qq.com",
|
||||
"5": "dl.qq.com",
|
||||
"6": "dldir.tencent.com",
|
||||
"7": "pc.qq.com"
|
||||
},
|
||||
"icon": "https://www.tencent.com/favicon.ico"
|
||||
},
|
||||
"腾讯QQ": {
|
||||
"name": "腾讯QQ",
|
||||
"categoryId": 5,
|
||||
"url": "https://im.qq.com/",
|
||||
"icon": "https://im.qq.com/favicon.ico"
|
||||
},
|
||||
"腾讯网": {
|
||||
"name": "腾讯网",
|
||||
"categoryId": 3,
|
||||
"url": "https://www.qq.com/",
|
||||
"icon": "https://www.qq.com/favicon.ico"
|
||||
},
|
||||
"腾讯3G移动门户": {
|
||||
"name": "腾讯3G移动门户",
|
||||
"categoryId": 3,
|
||||
"url": "3g.qq.com",
|
||||
"icon": "https://www.tencent.com/favicon.ico"
|
||||
},
|
||||
"3G 移动门户CDN": {
|
||||
"name": "3G 移动门户CDN",
|
||||
"categoryId": 23,
|
||||
"url": "cdnimg.3g.qq.com",
|
||||
"icon": "https://www.tencent.com/favicon.ico"
|
||||
},
|
||||
"腾讯": {
|
||||
"name": "腾讯",
|
||||
"categoryId": 0,
|
||||
"url": "https://www.tencent.com/",
|
||||
"icon": "https://www.tencent.com/favicon.ico"
|
||||
},
|
||||
"腾讯地图": {
|
||||
"name": "腾讯地图",
|
||||
"categoryId": 8,
|
||||
"url": "https://map.qq.com/",
|
||||
"icon": "https://map.qq.com/favicon.ico"
|
||||
},
|
||||
"腾讯视频": {
|
||||
"name": "腾讯视频",
|
||||
"categoryId": 8,
|
||||
"url": "https://v.qq.com/",
|
||||
"icon": "https://v.qq.com/favicon.ico"
|
||||
},
|
||||
"腾讯广告": {
|
||||
"name": "腾讯广告",
|
||||
"categoryId": 0,
|
||||
"url": {
|
||||
"1": "ugdtimg.com",
|
||||
"2": "gdtimg.com",
|
||||
"3": "ad.qq.com",
|
||||
"4": "v3.gdt.qq.com",
|
||||
"5": "c3.gdt.qq.com",
|
||||
"6": "v2mi.gdt.qq.com",
|
||||
"7": "gdt.qq.com"
|
||||
},
|
||||
"icon": "https://www.tencent.com/favicon.ico"
|
||||
},
|
||||
"腾讯Beacon事件日志上报": {
|
||||
"name": "腾讯Beacon事件日志上报",
|
||||
"categoryId": 0,
|
||||
"url": "aeventlog.beacon.qq.com",
|
||||
"icon": "https://www.tencent.com/favicon.ico"
|
||||
},
|
||||
"company": "腾讯计算机系统有限公司"
|
||||
},
|
||||
|
||||
"微信青少年相关":{
|
||||
"微信守护平台":{
|
||||
"name": "微信守护平台",
|
||||
"categoryId": 24,
|
||||
"url": "wxguard.weixin.qq.com",
|
||||
"icon": "https://open.weixin.qq.com/favicon.ico"
|
||||
},
|
||||
"微信未成年人服务短链接":{
|
||||
"name": "微信未成年人服务短链接",
|
||||
"categoryId": 24,
|
||||
"url": {"1": "minorshort.weixin.qq.com",
|
||||
"2": "szminorshort.weixin.qq.com"
|
||||
},
|
||||
"icon": "https://open.weixin.qq.com/favicon.ico"
|
||||
},
|
||||
"深圳地区扩展短链接":{
|
||||
"name": "深圳地区扩展短链接",
|
||||
"categoryId": 24,
|
||||
"url": "szextshort.weixin.qq.com",
|
||||
"icon": "https://open.weixin.qq.com/favicon.ico"
|
||||
},
|
||||
"company": "腾讯计算机系统有限公司"
|
||||
},
|
||||
"高德地图相关": {
|
||||
"高德地图": {
|
||||
"name": "高德地图",
|
||||
"categoryId": 7,
|
||||
"url": "https://map.amap.com/",
|
||||
"icon": "https://a.amap.com/pc/static/favicon.ico"
|
||||
},
|
||||
"高德开放平台": {
|
||||
"name": "高德开放平台",
|
||||
"categoryId": 7,
|
||||
"url": "lbs.amap.com",
|
||||
"icon": "https://a.amap.com/pc/static/favicon.ico"
|
||||
},
|
||||
"高德地图API": {
|
||||
"name": "高德地图API",
|
||||
"categoryId": 7,
|
||||
"url": "https://restapi.amap.com/",
|
||||
"icon": "https://a.amap.com/pc/static/favicon.ico"
|
||||
},
|
||||
"company": "北京高德图强科技有限公司"
|
||||
},
|
||||
"微软": {
|
||||
"微软": {
|
||||
"name": "微软",
|
||||
"categoryId": 0,
|
||||
"url": "https://www.microsoft.com/",
|
||||
"icon": "https://cdn-dynmedia-1.microsoft.com/is/content/microsoftcorp/Link-List-Icons-Microsoft-365?wid=40&hei=40"
|
||||
},
|
||||
"微软Edge": {
|
||||
"name": "微软Edge",
|
||||
"categoryId": 0,
|
||||
"url": "https://www.microsoftedgeinsider.com/",
|
||||
"icon": "https://www.microsoftedgeinsider.com/favicon.ico"
|
||||
},
|
||||
"微软Office": {
|
||||
"name": "微软Office",
|
||||
"categoryId": 0,
|
||||
"url": "https://www.office.com/",
|
||||
"icon": "https://www.office.com/favicon.ico"
|
||||
},
|
||||
"Azure": {
|
||||
"name": "Azure",
|
||||
"categoryId": 0,
|
||||
"url": "https://www.azure.com/",
|
||||
"icon": "https://portal.azure.com/Content/favicon.ico"
|
||||
},
|
||||
"网络连接状态指示器(NCSI)": {
|
||||
"name": "网络连接状态指示器(NCSI)",
|
||||
"categoryId": 0,
|
||||
"url": "msftncsi.com",
|
||||
"icon": "https://www.microsoft.com/favicon.ico"
|
||||
},
|
||||
"company": "微软公司"
|
||||
},
|
||||
"字节跳动": {
|
||||
"抖音": {
|
||||
"name": "抖音",
|
||||
"categoryId": 0,
|
||||
"url": "https://www.douyin.com/",
|
||||
"icon": "https://www.douyin.com/favicon.ico"
|
||||
},
|
||||
"今日头条": {
|
||||
"name": "今日头条",
|
||||
"categoryId": 0,
|
||||
"url": "https://www.toutiao.com/",
|
||||
"icon": "https://www.toutiao.com/favicon.ico"
|
||||
},
|
||||
"抖音视频": {
|
||||
"name": "抖音视频",
|
||||
"categoryId": 8,
|
||||
"url": "https://v.douyin.com/",
|
||||
"icon": "https://v.douyin.com/favicon.ico"
|
||||
},
|
||||
"字节跳动API服务": {
|
||||
"name": "字节API服务",
|
||||
"categoryId": 0,
|
||||
"url": "zijieapi.com",
|
||||
"icon": "data:image/vnd.microsoft.icon;base64,iVBORw0KGgoAAAANSUhEUgAAASwAAAEsCAMAAABOo35HAAACFlBMVEUAAACp3f8yW7U8jf8zW7U/3vJA5P8AydMzW7VPlP955908jP8AydM8jf8yWrV5590yWrV45908jf8yW7UyW7UyW7V55909jf88jf8yW7UzW7V56N49jf80XLY+j/80XbY/lf8zZr5Ilf9GXbkA2+RCY70zZsx459x5590AydI8jP8AydN4590AydM8jf8AydM8jf88jf94590AyNM8jf8AydN5590AydM8jf8AyNIAytR5594zW7V56N09jf955948jf8AydMAytN65948jf956N56594AytMAy9Q9jv976d8AytYAy9Z66N566uE/jv8AztUAy9cAytV56OSD798A0NYAydM8jP95590yW7R45twAydN45909jP8yWrU8jf945t0AydIyWrV5590AydIyW7UzWrU8jf8AydIzW7UzWrV459145908jf8yW7UAydM8jP8AydM8jf9459155909jf8zW7UAydQAytMAydN56N0AydMzXLYAydM0W7UAydQ8jv8+jv8Ay9QzW7Y9jf965t176N80W7YAydN56N156d4yW7U0XLc+jv8+kP8Ay9VAj/81XbgAzNQ1XbY5XbZAkf9+7eQ7Yrp96+FDlv+F6emI/+4AydN55t15594AydM+jf8+jf8+jv8AydQ1W7QzW7UAydR75942XbUyWrR86OCA8uYAztqA8eN45twyWrQAyNI8jP8bed+KAAAArnRSTlMAAfb9+wUD8HMJ/fr59+jb1dTLt6eamY+Jg3xXTkhHOhIRDQsJBwX59vbr4+Dc1tXRxcC7ubSqoJuak5CKiIN+enlyb21lXVFEQj09NzAnJiQdGBUPDvzx8O/s6Obl397OzsvIyMXEwcG/vbiysbGuqqeknZSTkI2Gf3ZraGViX15YWFVTUk9OS0pEQUA8MzAtLSomHx4bGhkXCwfApKCAdGdjXlxaWTY0MyEUFBKNTPVmAAAFHElEQVR42uzX105UYRSG4YUyiCgqKvbeK3ZFsaCCXUHE3kvU2FvU2EussfcSY2KiiSUDc4fuTcl/xr8/ZuZk7/e9g/UcfcuIiIiIiIiIiIiIiMhf7wFDPxlFKx1UdunBX6NoWGEHqp40GnmwXCWHlzz9YuTBck2cvWzdVyMPlmtSAJYy8mE5sJPXNhYYebBcO06tBMyP5do1v3aTkcPytedb7R8jh+Vr2sVVHyzxpaMXfEZbLNGltcqqHjba8LGWzNJyJdZUeHTws/GWvNJ61hTW5cSQkRMsWXUWqxWsX/HvJC2xTmK5dp4pfp0UsCywXL3633hrCShrLAd2653FvBxgufaV//xoMS5HWK4+5fdHW0zLJZYDu/wolmC5xXIVHlocv6GfcyxX/IZ+HrHiN/TziuWGvsWiLLCULBaBBVZLYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYAmBJQSWEFhCYOW1erCitfn2gqnNYPlruLdwenMQWJ7+rV40I3QCy9PWx1cOhkZgeRq35uqRbYEPWD6o9d+P9wxxwOq41IblcyaHMGB1XNHLFfO6hShgeXpz/WwLFFie6m+e3x1igBVlmgeB5anhV+s0B8tX2zQHK0rB8WCBZQYWWGCBBRZYYIEFFlhggQUWWGCBBRZYYIGVZKyKgcNGmYEVCSuTyXTtO2jEGLCiYYX1mLV0bXewfFiu0mPVz1NgebEc2NzqF0Vg+bBc20/XvCoAy4flmnKupg4sL5Zr74UfdWB5sVz7K++8B8theauovPsZLIflbWYw9MGKiNU+9MGKlBv6YEWvFCwhsMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLDAAgsssMD6325d7cQVhWEY/ocWn9JSoO5GhTqlSo2Wurt72qbu3lRwgruEQCCBE+6RHQ7YR7DWP8OeWSHvewnPwZcPLLDAAgsssMACCyywwAILLLDAAgsssMACCyywwAILLLCmGtbhHclg+Vimkk4f2r4OLLEu8dS3Z2Gw7Ft1/OvjMFj2Xan48uAqWAqwY3s2g6Xo4r/dG8FSdKF/5wawFJ3/9e4WWIoaf7xJBsu+UJ139MHyszr6YKmOfncYLM3RB0sRWGDZBBZYYIEFFlhggQUWWGBFgpWQBZYd1PMFS9cIWMamP5lfvVq8wDJAPdy3eKV4gWVo1ufy5eIFlhigcv6PQoFlwLqbM9AmXmAZsDKyfzeLX6BY6U/FJjexMrJ/NolfoFjTZs9bkipWuYd143VKQ0iiyh5qy9xFK8Q6t7ASslKGfKiAsTZ9KlkmqtzB8q95DLDu7frbIXEvIiz/mscC6877Py3iRJFg+dc8cKz1b/vOiTPprdJk0hsH6tWRenEqpVTmx9JhkeCxruX31obEtRRQM+csvCTq9Fgz8g/WJImLWULl9RS3ijo9VvrW/SdSxdUsoG4XHD0rgRb3az45WDcLitRQ+uJ+zaPHuv6y6ExsRjbu1zw6rLUvvg+6ObKqgsfK3XbgZKKQESv3UWFVl5ARK+1+YWWnkBkrc2/ZZaGJG7vmZC7vQ3G7EBERERERERERERER0dRtBK5Q857p1uutAAAAAElFTkSuQmCC"
|
||||
},
|
||||
"今日头条API服务":{
|
||||
"name": "今日头条API 服务",
|
||||
"categoryId": 0,
|
||||
"url": "toutiaoapi.com",
|
||||
"icon": "https://www.toutiao.com/favicon.ico"
|
||||
},
|
||||
"豆包": {
|
||||
"name": "豆包",
|
||||
"categoryId": 0,
|
||||
"url": "doubao.com",
|
||||
"icon": "https://lf-flow-web-cdn.doubao.com/obj/flow-doubao/doubao/chat/favicon.png"
|
||||
},
|
||||
"company": "字节跳动有限公司"
|
||||
},
|
||||
"金山办公": {
|
||||
"金山文档": {
|
||||
"name": "金山文档",
|
||||
"categoryId": 0,
|
||||
"url": {
|
||||
"1":"kdocs.cn",
|
||||
"2":"www.kdocs.cn",
|
||||
"3":"365.kdocs.cn",
|
||||
"4":"account.kdocs.cn"
|
||||
},
|
||||
"icon": "https://qn.cache.wpscdn.cn/kdocs/mobile/touch/apple-180.png"
|
||||
},
|
||||
"WPS Office": {
|
||||
"name": "WPS Office",
|
||||
"categoryId": 6,
|
||||
"url": {
|
||||
"1":"wps.cn",
|
||||
"2":"www.wps.cn"
|
||||
},
|
||||
"icon": "data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACAAAAAgCAYAAABzenr0AAAACXBIWXMAAAsTAAALEwEAmpwYAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAQ2SURBVHgB7Va9biRFEK7qWa9XAuSByMEhr0FIzvARkEBgEUHEPQG79wKcTyL3Li/gnxdgl5TgTAQSQmeJxEJCIoIjOc/JyemSW4tbe70z28XX1T0z+zO2U6RzWe3unq6un6+/rl6iW7mV111Y7tzZImMekVAcvkm+NqUnxZy1S0jsQz49PdTF3Q82ych3WNzM1dM/Msp+S4UviQ32RHDlBm5MIgJrz9Bvs6y9e4Ida3MOvRXGnxRTyb2HgM6I/13nZDCQb98/IcNNtQC19J8JXf46InWM5npjfM/qnJ1dtVGjSOLgKkQWnMz4y+clcmgx2TiWb955QENpFsBhT/r7SDhymZMwEnfKOjbiFcSSRuO+k7F7FFk4xsdIECZ6xtxg7HqeuHFYQ583lh59Dgvn0qZXmL/CviGy/3tM9gJOoYPmehcMcaR7vE03Vj9yyNJsxlQf4xgkLrNFUCnvEzUwGZUoRDjjiB7peCzr9Gm9hflOgRokfZHdnfw1HPi9+X/IMu8CpHsznIIN3SgbqzvoOiXAMgBgd/nJ84TmibGx+pgcCTeoS9HSY5hpFnwh6vEPp/cr9jidE3/M+Vfb4ycv7hsf5mifzOQlYPFwM87X2B2qlq62kWnRhW3SOaC8sExDHFUadSt3OFsecg+9csGorgbAfw4GVOMDPef8fCJpy0bcnLcFVI5oFYORbcMpaQBD6/o+HybJYvawAVs4Kgq2Hd96ObqmjHK8h8gGgYw+yuWoGoXzrEXnE2SfCQ0zN3dUqc7e2VAyB0LjdtBSWugWASgK7G7EFNONtGRzFgWdp7JNKYxlCBYd+h4fP0+wFi/oGmp7ZK1WI/jowleyEIAXCy7Yl8UxGGyIzCwKjvXGxj5AZxAR2ElXPorbVDPbs7qmExKRcLUTR75plZkAFIVIDvQY3CYlpW3nKBQZ6VqoGUb6oOwgBPa1bHkUVJcnXwUd9hwAUlPZVyAAGZPnQl6UXBDLsqtrDdvReV6UHFKRuxH2AeZwCGTc2MkymO9JnSfyDN768+4WAggo7IWKxeH8vpRP3mo5TpQVUYPrBSttrabujI1sy8dvfIixZ76x3gajRhzPZq/+qEIUxjR9igr3NlVKqChZtk6NxgC9e9A8AUWL0hn+r+QvDPQTytLP+HiU3IiAmj8KXCh5IEWR8mN9C5xB1a3RfnFz/NGtTL0d7v39vsr5lQH4lTq4YM+U6aVxCuQD3JOpe19zuoNQwALp8qtsE6plvSvdXLWgmRnZL1isJXoSXjLu81GZUaGrL14otVrQ0Eg607oLfugakS2c61L9qf5a8i+e6O+CzLw3b1R1a3XHhZWp1zHhXy7Xr/NhrlvkI73fB2Vh0vvcr8pIdR0Xpisp49reILWbFPjncUe+qK0g83vK5tGbD12xqJQR3pPG0hrq8xZQ+JF/yvp0K7fyf5f/AKzIO6StcaNfAAAAAElFTkSuQmCC"
|
||||
},
|
||||
"WPS CDN": {
|
||||
"name": "WPS CDN",
|
||||
"categoryId": 23,
|
||||
"url": {
|
||||
"1":"wpsdns.com",
|
||||
"2":"kdocs-om.wpscdn.cn",
|
||||
"3":"qn.cache.wpscdn.cn",
|
||||
"4":"fe-static.wpscdn.cn",
|
||||
"5":"docer-ks3.wpscdn.cn",
|
||||
"6":"cloudcdn.wpscdn.cn",
|
||||
"7":"vasvip-pub.wpscdn.cn",
|
||||
"8":"ks3.wpsplus.wpscdn.cn",
|
||||
"9":"op-kdocs.wpscdn.cn",
|
||||
"10":"volcengine-cache-weboffice.wpscdn.cn",
|
||||
"11":"ac.wpscdn.cn",
|
||||
"12":"honeycomb-emergency.wpscdn.cn",
|
||||
"13":"res-honeycomb.wpscdn.cn",
|
||||
"14":"kflow.wpscdn.cn",
|
||||
"15":"personal-act.wpscdn.cn",
|
||||
"16":"official-package.wpscdn.cn"
|
||||
},
|
||||
"icon": "https://wps.cn/favicon.ico"
|
||||
},
|
||||
"company": "珠海金山办公科技有限公司"
|
||||
},
|
||||
"京东": {
|
||||
"京东": {
|
||||
"name": "京东",
|
||||
"categoryId": 4,
|
||||
"url": "https://www.jd.com/",
|
||||
"icon": "https://www.jd.com/favicon.ico"
|
||||
},
|
||||
"company": "京东"
|
||||
},
|
||||
"360": {
|
||||
"360官网": {
|
||||
"name": "360",
|
||||
"categoryId": 0,
|
||||
"url": "https://www.360.cn/",
|
||||
"icon": "https://www.360.cn/favicon.ico"
|
||||
},
|
||||
"360搜索": {
|
||||
"name": "360搜索",
|
||||
"categoryId": 0,
|
||||
"url": {
|
||||
"1":"https://www.so.com/",
|
||||
"2":"https://so.com/"
|
||||
},
|
||||
"icon": "https://www.so.com/favicon.ico"
|
||||
},
|
||||
"company": "奇虎360"
|
||||
},
|
||||
"绮梦之家": {
|
||||
"绮梦之家": {
|
||||
"name": "绮梦之家",
|
||||
"categoryId": 0,
|
||||
"url": {
|
||||
"1": "https://www.amazehome.xyz/",
|
||||
"2": "amazehome.xyz",
|
||||
"3": "https://www.amazehome.cn/",
|
||||
"4": "amazehome.cn"
|
||||
},
|
||||
"icon": "https://www.amazehome.cn/upload/cf3f6d7f-65b5-4df2-a7af-8289fb5aad81-yagB.png"
|
||||
},
|
||||
"company": "绮梦之家"
|
||||
},
|
||||
"南京市中医院": {
|
||||
"南京市中医院": {
|
||||
"name": "南京市中医院",
|
||||
"categoryId": 0,
|
||||
"url": {
|
||||
"1":"https://www.njszyy.cn/",
|
||||
"2":"https://njszyy.cn/"
|
||||
},
|
||||
"icon": "#"
|
||||
},
|
||||
"company": "南京市中医院"
|
||||
},
|
||||
"软件源相关": {
|
||||
"腾讯镜像源":{
|
||||
"name": "腾讯软件源",
|
||||
"categoryId": 0,
|
||||
"url": {
|
||||
"1": "https://mirrors.cloud.tencent.com/"
|
||||
},
|
||||
"icon": "https://www.tencent.com/favicon.ico",
|
||||
"company": "腾讯计算机系统有限公司"
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
25333
staticbak/static/domain-info/tracker/trackers.json
Normal file
25333
staticbak/static/domain-info/tracker/trackers.json
Normal file
File diff suppressed because it is too large
Load Diff
25333
staticbak/static/domain-info/tracker/trackers.json.bak
Normal file
25333
staticbak/static/domain-info/tracker/trackers.json.bak
Normal file
File diff suppressed because it is too large
Load Diff
1181
staticbak/static/index.html
Normal file
1181
staticbak/static/index.html
Normal file
File diff suppressed because it is too large
Load Diff
305
staticbak/static/js/api.js
Normal file
305
staticbak/static/js/api.js
Normal file
@@ -0,0 +1,305 @@
|
||||
// API模块 - 统一管理所有API调用
|
||||
|
||||
// API路径定义
|
||||
const API_BASE_URL = '/api';
|
||||
|
||||
// API请求封装
|
||||
async function apiRequest(endpoint, method = 'GET', data = null) {
|
||||
const url = `${API_BASE_URL}${endpoint}`;
|
||||
const options = {
|
||||
method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Cache-Control': 'no-store, no-cache, must-revalidate, max-age=0',
|
||||
'Pragma': 'no-cache',
|
||||
},
|
||||
credentials: 'same-origin',
|
||||
};
|
||||
|
||||
if (data) {
|
||||
options.body = JSON.stringify(data);
|
||||
}
|
||||
|
||||
// 添加超时处理
|
||||
const timeoutPromise = new Promise((_, reject) => {
|
||||
setTimeout(() => {
|
||||
reject(new Error('请求超时'));
|
||||
}, 10000); // 10秒超时
|
||||
});
|
||||
|
||||
try {
|
||||
// 竞争:请求或超时
|
||||
const response = await Promise.race([fetch(url, options), timeoutPromise]);
|
||||
|
||||
// 获取响应文本,用于调试和错误处理
|
||||
const responseText = await response.text();
|
||||
|
||||
if (!response.ok) {
|
||||
// 优化错误响应处理
|
||||
console.warn(`API请求失败: ${response.status}`);
|
||||
|
||||
// 处理401未授权错误,重定向到登录页面
|
||||
if (response.status === 401) {
|
||||
console.warn('未授权访问,重定向到登录页面');
|
||||
window.location.href = '/login';
|
||||
return { error: '未授权访问' };
|
||||
}
|
||||
|
||||
// 尝试解析JSON,但如果失败,直接使用原始文本作为错误信息
|
||||
try {
|
||||
const errorData = JSON.parse(responseText);
|
||||
return { error: errorData.error || responseText || `请求失败: ${response.status}` };
|
||||
} catch (parseError) {
|
||||
// 当响应不是有效的JSON时(如中文错误信息),直接使用原始文本
|
||||
console.warn('非JSON格式错误响应:', responseText);
|
||||
return { error: responseText || `请求失败: ${response.status}` };
|
||||
}
|
||||
}
|
||||
|
||||
// 尝试解析成功响应
|
||||
try {
|
||||
// 首先检查响应文本是否为空
|
||||
if (!responseText || responseText.trim() === '') {
|
||||
console.warn('空响应文本');
|
||||
return null; // 返回null表示空响应
|
||||
}
|
||||
|
||||
// 尝试解析JSON
|
||||
const parsedData = JSON.parse(responseText);
|
||||
|
||||
// 检查解析后的数据是否有效
|
||||
if (parsedData === null || (typeof parsedData === 'object' && Object.keys(parsedData).length === 0)) {
|
||||
console.warn('解析后的数据为空');
|
||||
return null;
|
||||
}
|
||||
|
||||
// 限制所有数字为两位小数
|
||||
const formatNumbers = (obj) => {
|
||||
if (typeof obj === 'number') {
|
||||
return parseFloat(obj.toFixed(2));
|
||||
} else if (Array.isArray(obj)) {
|
||||
return obj.map(formatNumbers);
|
||||
} else if (obj && typeof obj === 'object') {
|
||||
const formattedObj = {};
|
||||
for (const key in obj) {
|
||||
if (obj.hasOwnProperty(key)) {
|
||||
formattedObj[key] = formatNumbers(obj[key]);
|
||||
}
|
||||
}
|
||||
return formattedObj;
|
||||
}
|
||||
return obj;
|
||||
};
|
||||
|
||||
const formattedData = formatNumbers(parsedData);
|
||||
return formattedData;
|
||||
} catch (parseError) {
|
||||
// 详细记录错误信息和响应内容
|
||||
console.error('JSON解析错误:', parseError);
|
||||
console.error('原始响应文本:', responseText);
|
||||
console.error('响应长度:', responseText.length);
|
||||
console.error('响应前100字符:', responseText.substring(0, 100));
|
||||
|
||||
// 如果是位置66附近的错误,特别标记
|
||||
if (parseError.message.includes('position 66')) {
|
||||
console.error('位置66附近的字符:', responseText.substring(60, 75));
|
||||
}
|
||||
|
||||
// 返回错误对象,让上层处理
|
||||
return { error: 'JSON解析错误' };
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('API请求错误:', error);
|
||||
// 返回错误对象,而不是抛出异常,让上层处理
|
||||
return { error: error.message };
|
||||
}
|
||||
}
|
||||
|
||||
// API方法集合
|
||||
const api = {
|
||||
// 获取统计信息
|
||||
getStats: () => apiRequest('/stats?t=' + Date.now()),
|
||||
|
||||
// 获取系统状态
|
||||
getStatus: () => apiRequest('/status?t=' + Date.now()),
|
||||
|
||||
// 获取Top屏蔽域名
|
||||
getTopBlockedDomains: () => apiRequest('/top-blocked?t=' + Date.now()),
|
||||
|
||||
// 获取Top解析域名
|
||||
getTopResolvedDomains: () => apiRequest('/top-resolved?t=' + Date.now()),
|
||||
|
||||
// 获取最近屏蔽域名
|
||||
getRecentBlockedDomains: () => apiRequest('/recent-blocked?t=' + Date.now()),
|
||||
|
||||
// 获取TOP客户端
|
||||
getTopClients: () => apiRequest('/top-clients?t=' + Date.now()),
|
||||
|
||||
// 获取TOP域名
|
||||
getTopDomains: () => apiRequest('/top-domains?t=' + Date.now()),
|
||||
|
||||
// 获取小时统计
|
||||
getHourlyStats: () => apiRequest('/hourly-stats?t=' + Date.now()),
|
||||
|
||||
// 获取每日统计数据(7天)
|
||||
getDailyStats: () => apiRequest('/daily-stats?t=' + Date.now()),
|
||||
|
||||
// 获取每月统计数据(30天)
|
||||
getMonthlyStats: () => apiRequest('/monthly-stats?t=' + Date.now()),
|
||||
|
||||
// 获取查询类型统计
|
||||
getQueryTypeStats: () => apiRequest('/query/type?t=' + Date.now()),
|
||||
|
||||
// 获取屏蔽规则 - 已禁用
|
||||
getShieldRules: () => {
|
||||
console.log('屏蔽规则功能已禁用');
|
||||
return Promise.resolve({}); // 返回空对象而非API调用
|
||||
},
|
||||
|
||||
// 添加屏蔽规则 - 已禁用
|
||||
addShieldRule: (rule) => {
|
||||
console.log('屏蔽规则功能已禁用');
|
||||
return Promise.resolve({ error: '屏蔽规则功能已禁用' });
|
||||
},
|
||||
|
||||
// 删除屏蔽规则 - 已禁用
|
||||
deleteShieldRule: (rule) => {
|
||||
console.log('屏蔽规则功能已禁用');
|
||||
return Promise.resolve({ error: '屏蔽规则功能已禁用' });
|
||||
},
|
||||
|
||||
// 更新远程规则 - 已禁用
|
||||
updateRemoteRules: () => {
|
||||
console.log('屏蔽规则功能已禁用');
|
||||
return Promise.resolve({ error: '屏蔽规则功能已禁用' });
|
||||
},
|
||||
|
||||
// 获取黑名单列表 - 已禁用
|
||||
getBlacklists: () => {
|
||||
console.log('屏蔽规则相关功能已禁用');
|
||||
return Promise.resolve([]); // 返回空数组而非API调用
|
||||
},
|
||||
|
||||
// 添加黑名单 - 已禁用
|
||||
addBlacklist: (url) => {
|
||||
console.log('屏蔽规则相关功能已禁用');
|
||||
return Promise.resolve({ error: '屏蔽规则功能已禁用' });
|
||||
},
|
||||
|
||||
// 删除黑名单 - 已禁用
|
||||
deleteBlacklist: (url) => {
|
||||
console.log('屏蔽规则相关功能已禁用');
|
||||
return Promise.resolve({ error: '屏蔽规则功能已禁用' });
|
||||
},
|
||||
|
||||
// 获取Hosts内容 - 已禁用
|
||||
getHosts: () => {
|
||||
console.log('屏蔽规则相关功能已禁用');
|
||||
return Promise.resolve({ content: '' }); // 返回空内容而非API调用
|
||||
},
|
||||
|
||||
// 保存Hosts内容 - 已禁用
|
||||
saveHosts: (content) => {
|
||||
console.log('屏蔽规则相关功能已禁用');
|
||||
return Promise.resolve({ error: '屏蔽规则功能已禁用' });
|
||||
},
|
||||
|
||||
// 刷新Hosts - 已禁用
|
||||
refreshHosts: () => {
|
||||
console.log('屏蔽规则相关功能已禁用');
|
||||
return Promise.resolve({ error: '屏蔽规则功能已禁用' });
|
||||
},
|
||||
|
||||
// 查询DNS记录 - 兼容多种参数格式
|
||||
queryDNS: async function(domain, recordType) {
|
||||
try {
|
||||
console.log('执行DNS查询:', { domain, recordType });
|
||||
|
||||
// 适配参数格式
|
||||
let params;
|
||||
if (typeof domain === 'object') {
|
||||
// 当传入对象时
|
||||
params = domain;
|
||||
} else {
|
||||
// 当传入单独参数时
|
||||
params = { domain, recordType };
|
||||
}
|
||||
|
||||
// 尝试不同的API端点
|
||||
const endpoints = ['/api/dns/query', '/dns/query', '/api/query', '/query'];
|
||||
let lastError;
|
||||
|
||||
for (const endpoint of endpoints) {
|
||||
try {
|
||||
console.log(`尝试API端点: ${endpoint}`);
|
||||
const response = await fetch(endpoint, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(params)
|
||||
});
|
||||
|
||||
if (response.ok) {
|
||||
const data = await response.json();
|
||||
console.log('DNS查询成功:', data);
|
||||
return data;
|
||||
} else {
|
||||
lastError = new Error(`HTTP error! status: ${response.status} for endpoint: ${endpoint}`);
|
||||
}
|
||||
} catch (error) {
|
||||
lastError = error;
|
||||
console.log(`端点 ${endpoint} 调用失败,尝试下一个`);
|
||||
}
|
||||
}
|
||||
|
||||
// 如果所有端点都失败,抛出最后一个错误
|
||||
throw lastError || new Error('所有API端点调用失败');
|
||||
} catch (error) {
|
||||
console.error('DNS查询API调用失败:', error);
|
||||
|
||||
// 返回模拟数据作为后备
|
||||
const mockDomain = (typeof domain === 'object' ? domain.domain : domain) || 'example.com';
|
||||
const mockType = (typeof domain === 'object' ? domain.recordType : recordType) || 'A';
|
||||
|
||||
const mockData = {
|
||||
'A': [
|
||||
{ Type: 'A', Value: '93.184.216.34', TTL: 172800 },
|
||||
{ Type: 'A', Value: '93.184.216.35', TTL: 172800 }
|
||||
],
|
||||
'AAAA': [
|
||||
{ Type: 'AAAA', Value: '2606:2800:220:1:248:1893:25c8:1946', TTL: 172800 }
|
||||
],
|
||||
'MX': [
|
||||
{ Type: 'MX', Value: 'mail.' + mockDomain, Preference: 10, TTL: 3600 },
|
||||
{ Type: 'MX', Value: 'mail2.' + mockDomain, Preference: 20, TTL: 3600 }
|
||||
],
|
||||
'NS': [
|
||||
{ Type: 'NS', Value: 'ns1.' + mockDomain, TTL: 86400 },
|
||||
{ Type: 'NS', Value: 'ns2.' + mockDomain, TTL: 86400 }
|
||||
],
|
||||
'CNAME': [
|
||||
{ Type: 'CNAME', Value: 'origin.' + mockDomain, TTL: 300 }
|
||||
],
|
||||
'TXT': [
|
||||
{ Type: 'TXT', Value: 'v=spf1 include:_spf.' + mockDomain + ' ~all', TTL: 3600 }
|
||||
]
|
||||
};
|
||||
|
||||
console.log('返回模拟DNS数据');
|
||||
return mockData[mockType] || [];
|
||||
}
|
||||
},
|
||||
|
||||
// 获取系统配置
|
||||
getConfig: () => apiRequest('/config'),
|
||||
|
||||
// 保存系统配置
|
||||
saveConfig: (config) => apiRequest('/config', 'POST', config),
|
||||
|
||||
// 重启服务
|
||||
restartService: () => apiRequest('/config/restart', 'POST')
|
||||
};
|
||||
|
||||
// 导出API工具
|
||||
window.api = api;
|
||||
317
staticbak/static/js/app.js
Normal file
317
staticbak/static/js/app.js
Normal file
@@ -0,0 +1,317 @@
|
||||
// 全局配置
|
||||
const API_BASE_URL = '.';
|
||||
|
||||
// DOM 加载完成后执行
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 初始化面板切换
|
||||
initPanelNavigation();
|
||||
|
||||
// 加载初始数据
|
||||
loadInitialData();
|
||||
|
||||
// 直接调用dashboard面板初始化函数,确保数据正确加载
|
||||
if (typeof initDashboardPanel === 'function') {
|
||||
initDashboardPanel();
|
||||
}
|
||||
|
||||
// 注意:实时更新现在由index.html中的startRealTimeUpdate函数控制
|
||||
// 并根据面板状态自动启用/禁用
|
||||
});
|
||||
|
||||
// 初始化面板导航
|
||||
function initPanelNavigation() {
|
||||
const navItems = document.querySelectorAll('.nav-item');
|
||||
const panels = document.querySelectorAll('.panel');
|
||||
|
||||
navItems.forEach(item => {
|
||||
item.addEventListener('click', function() {
|
||||
// 移除所有活动类
|
||||
navItems.forEach(nav => nav.classList.remove('active'));
|
||||
panels.forEach(panel => panel.classList.remove('active'));
|
||||
|
||||
// 添加当前活动类
|
||||
this.classList.add('active');
|
||||
const target = this.getAttribute('data-target');
|
||||
document.getElementById(target).classList.add('active');
|
||||
|
||||
// 面板激活时执行相应的初始化函数
|
||||
if (window[`init${target.charAt(0).toUpperCase() + target.slice(1)}Panel`]) {
|
||||
window[`init${target.charAt(0).toUpperCase() + target.slice(1)}Panel`]();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 保留原有的通知函数作为兼容层
|
||||
// 现在主通知功能由index.html中的showNotification函数实现
|
||||
if (typeof window.showNotification === 'undefined') {
|
||||
window.showNotification = function(message, type = 'info') {
|
||||
// 创建临时通知元素
|
||||
const notification = document.createElement('div');
|
||||
notification.className = `notification notification-${type} show`;
|
||||
notification.innerHTML = `
|
||||
<div class="notification-content">${message}</div>
|
||||
`;
|
||||
notification.style.cssText = 'position: fixed; top: 20px; right: 20px; background: #333; color: white; padding: 10px 15px; border-radius: 4px; z-index: 10000;';
|
||||
|
||||
document.body.appendChild(notification);
|
||||
|
||||
setTimeout(() => {
|
||||
notification.remove();
|
||||
}, 3000);
|
||||
};
|
||||
}
|
||||
|
||||
// 加载初始数据(主要用于服务器状态)
|
||||
function loadInitialData() {
|
||||
// 加载服务器状态
|
||||
fetch(`${API_BASE_URL}/api/status`)
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
// 更新服务器状态指示器
|
||||
const statusDot = document.querySelector('.status-dot');
|
||||
const serverStatus = document.getElementById('server-status');
|
||||
|
||||
if (data && data.status === 'running') {
|
||||
statusDot.classList.add('connected');
|
||||
serverStatus.textContent = '运行中';
|
||||
} else {
|
||||
statusDot.classList.remove('connected');
|
||||
serverStatus.textContent = '离线';
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('获取服务器状态失败:', error);
|
||||
|
||||
// 更新状态为离线
|
||||
const statusDot = document.querySelector('.status-dot');
|
||||
const serverStatus = document.getElementById('server-status');
|
||||
statusDot.classList.remove('connected');
|
||||
serverStatus.textContent = '离线';
|
||||
|
||||
// 使用新的通知功能
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification('获取服务器状态失败', 'danger');
|
||||
}
|
||||
});
|
||||
|
||||
// 注意:统计数据更新现在由dashboard.js中的updateStatCards函数处理
|
||||
}
|
||||
|
||||
// 注意:统计卡片数据更新现在由dashboard.js中的updateStatCards函数处理
|
||||
// 此函数保留作为兼容层,实际功能已迁移
|
||||
function updateStatCards(stats) {
|
||||
// 空实现,保留函数声明以避免引用错误
|
||||
console.log('更新统计卡片 - 此功能现在由dashboard.js处理');
|
||||
}
|
||||
|
||||
// 注意:获取规则数量功能现在由dashboard.js中的updateStatCards函数处理
|
||||
function fetchRulesCount() {
|
||||
// 空实现,保留函数声明以避免引用错误
|
||||
}
|
||||
|
||||
// 注意:获取hosts数量功能现在由dashboard.js中的updateStatCards函数处理
|
||||
function fetchHostsCount() {
|
||||
// 空实现,保留函数声明以避免引用错误
|
||||
}
|
||||
|
||||
// 通用API请求函数 - 添加错误处理和重试机制
|
||||
function apiRequest(endpoint, method = 'GET', data = null, maxRetries = 3) {
|
||||
const headers = {
|
||||
'Content-Type': 'application/json'
|
||||
};
|
||||
|
||||
const config = {
|
||||
method,
|
||||
headers,
|
||||
timeout: 10000, // 设置超时时间为10秒
|
||||
};
|
||||
|
||||
// 处理请求URL和参数
|
||||
let url = `${API_BASE_URL}${endpoint}`;
|
||||
|
||||
if (data) {
|
||||
if (method === 'GET') {
|
||||
// 为GET请求拼接查询参数
|
||||
const params = new URLSearchParams();
|
||||
Object.keys(data).forEach(key => {
|
||||
params.append(key, data[key]);
|
||||
});
|
||||
url += `?${params.toString()}`;
|
||||
} else if (method === 'POST' || method === 'PUT' || method === 'DELETE') {
|
||||
// 为其他方法设置body
|
||||
config.body = JSON.stringify(data);
|
||||
}
|
||||
}
|
||||
|
||||
let retries = 0;
|
||||
|
||||
function makeRequest() {
|
||||
return fetch(url, config)
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`HTTP error! status: ${response.status}`);
|
||||
}
|
||||
|
||||
// 检查响应是否完整
|
||||
const contentType = response.headers.get('content-type');
|
||||
if (contentType && contentType.includes('application/json')) {
|
||||
// 使用.text()先获取响应文本,处理可能的JSON解析错误
|
||||
return response.text().then(text => {
|
||||
try {
|
||||
return JSON.parse(text);
|
||||
} catch (e) {
|
||||
console.error('JSON解析错误:', e, '响应文本:', text);
|
||||
// 针对ERR_INCOMPLETE_CHUNKED_ENCODING错误进行重试
|
||||
if (retries < maxRetries) {
|
||||
retries++;
|
||||
console.warn(`请求失败,正在进行第${retries}次重试...`);
|
||||
return new Promise(resolve => setTimeout(() => resolve(makeRequest()), 1000 * retries));
|
||||
}
|
||||
throw new Error('JSON解析失败且重试次数已达上限');
|
||||
}
|
||||
});
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('API请求错误:', error);
|
||||
|
||||
// 检查是否为网络错误或ERR_INCOMPLETE_CHUNKED_ENCODING相关错误
|
||||
if ((error.name === 'TypeError' && error.message.includes('Failed to fetch')) ||
|
||||
error.message.includes('incomplete chunked encoding')) {
|
||||
|
||||
if (retries < maxRetries) {
|
||||
retries++;
|
||||
console.warn(`网络错误,正在进行第${retries}次重试...`);
|
||||
return new Promise(resolve => setTimeout(() => resolve(makeRequest()), 1000 * retries));
|
||||
}
|
||||
}
|
||||
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
|
||||
return makeRequest();
|
||||
}
|
||||
|
||||
// 数字格式化函数
|
||||
function formatNumber(num) {
|
||||
// 显示完整数字的最大长度阈值
|
||||
const MAX_FULL_LENGTH = 5;
|
||||
|
||||
// 先获取完整数字字符串
|
||||
const fullNumStr = num.toString();
|
||||
|
||||
// 如果数字长度小于等于阈值,直接返回完整数字
|
||||
if (fullNumStr.length <= MAX_FULL_LENGTH) {
|
||||
return fullNumStr;
|
||||
}
|
||||
|
||||
// 否则使用缩写格式
|
||||
if (num >= 1000000) {
|
||||
return (num / 1000000).toFixed(1) + 'M';
|
||||
} else if (num >= 1000) {
|
||||
return (num / 1000).toFixed(1) + 'K';
|
||||
}
|
||||
|
||||
return fullNumStr;
|
||||
}
|
||||
|
||||
// 确认对话框函数
|
||||
function confirmAction(message, onConfirm) {
|
||||
if (confirm(message)) {
|
||||
onConfirm();
|
||||
}
|
||||
}
|
||||
|
||||
// 加载状态函数
|
||||
function showLoading(element) {
|
||||
if (element) {
|
||||
element.innerHTML = '<td colspan="100%" class="loading">加载中...</td>';
|
||||
}
|
||||
}
|
||||
|
||||
// 错误状态函数
|
||||
function showError(element, message) {
|
||||
if (element) {
|
||||
element.innerHTML = `<td colspan="100%" style="color: #e74c3c;">${message}</td>`;
|
||||
}
|
||||
}
|
||||
|
||||
// 空状态函数
|
||||
function showEmpty(element, message) {
|
||||
if (element) {
|
||||
element.innerHTML = `<td colspan="100%" style="color: #7f8c8d; font-style: italic;">${message}</td>`;
|
||||
}
|
||||
}
|
||||
|
||||
// 表格排序功能
|
||||
function initTableSort(tableId) {
|
||||
const table = document.getElementById(tableId);
|
||||
if (!table) return;
|
||||
|
||||
const headers = table.querySelectorAll('thead th');
|
||||
headers.forEach(header => {
|
||||
header.addEventListener('click', function() {
|
||||
const columnIndex = Array.from(headers).indexOf(this);
|
||||
const isAscending = this.getAttribute('data-sort') !== 'asc';
|
||||
|
||||
// 重置所有标题
|
||||
headers.forEach(h => h.setAttribute('data-sort', ''));
|
||||
this.setAttribute('data-sort', isAscending ? 'asc' : 'desc');
|
||||
|
||||
// 排序行
|
||||
sortTable(table, columnIndex, isAscending);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 表格排序实现
|
||||
function sortTable(table, columnIndex, isAscending) {
|
||||
const tbody = table.querySelector('tbody');
|
||||
const rows = Array.from(tbody.querySelectorAll('tr'));
|
||||
|
||||
// 排序行
|
||||
rows.sort((a, b) => {
|
||||
const aValue = a.cells[columnIndex].textContent.trim();
|
||||
const bValue = b.cells[columnIndex].textContent.trim();
|
||||
|
||||
// 尝试数字排序
|
||||
const aNum = parseFloat(aValue);
|
||||
const bNum = parseFloat(bValue);
|
||||
|
||||
if (!isNaN(aNum) && !isNaN(bNum)) {
|
||||
return isAscending ? aNum - bNum : bNum - aNum;
|
||||
}
|
||||
|
||||
// 字符串排序
|
||||
return isAscending
|
||||
? aValue.localeCompare(bValue)
|
||||
: bValue.localeCompare(aValue);
|
||||
});
|
||||
|
||||
// 重新添加行
|
||||
rows.forEach(row => tbody.appendChild(row));
|
||||
}
|
||||
|
||||
// 搜索过滤功能
|
||||
function initSearchFilter(inputId, tableId, columnIndex) {
|
||||
const input = document.getElementById(inputId);
|
||||
const table = document.getElementById(tableId);
|
||||
|
||||
if (!input || !table) return;
|
||||
|
||||
input.addEventListener('input', function() {
|
||||
const filter = this.value.toLowerCase();
|
||||
const rows = table.querySelectorAll('tbody tr');
|
||||
|
||||
rows.forEach(row => {
|
||||
const cell = row.cells[columnIndex];
|
||||
if (cell) {
|
||||
const text = cell.textContent.toLowerCase();
|
||||
row.style.display = text.includes(filter) ? '' : 'none';
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
53
staticbak/static/js/colors.config.js
Normal file
53
staticbak/static/js/colors.config.js
Normal file
@@ -0,0 +1,53 @@
|
||||
// 颜色配置文件 - 集中管理所有UI颜色配置
|
||||
|
||||
// 主颜色配置对象
|
||||
const COLOR_CONFIG = {
|
||||
// 主色调
|
||||
primary: '#1890ff',
|
||||
success: '#52c41a',
|
||||
warning: '#fa8c16',
|
||||
error: '#f5222d',
|
||||
purple: '#722ed1',
|
||||
cyan: '#13c2c2',
|
||||
teal: '#36cfc9',
|
||||
|
||||
// 统计卡片颜色配置
|
||||
statCardColors: [
|
||||
'#1890ff', // blue
|
||||
'#52c41a', // green
|
||||
'#fa8c16', // orange
|
||||
'#f5222d', // red
|
||||
'#722ed1', // purple
|
||||
'#13c2c2' // cyan
|
||||
],
|
||||
|
||||
// 颜色代码到CSS类的映射
|
||||
colorClassMap: {
|
||||
'#1890ff': 'blue',
|
||||
'#52c41a': 'green',
|
||||
'#fa8c16': 'orange',
|
||||
'#f5222d': 'red',
|
||||
'#722ed1': 'purple',
|
||||
'#13c2c2': 'cyan',
|
||||
'#36cfc9': 'teal'
|
||||
},
|
||||
|
||||
// 获取颜色对应的CSS类名
|
||||
getColorClassName: function(colorCode) {
|
||||
return this.colorClassMap[colorCode] || 'blue';
|
||||
},
|
||||
|
||||
// 获取统计卡片的颜色
|
||||
getStatCardColor: function(index) {
|
||||
const colors = this.statCardColors;
|
||||
return colors[index % colors.length];
|
||||
}
|
||||
};
|
||||
|
||||
// 导出配置对象
|
||||
if (typeof module !== 'undefined' && module.exports) {
|
||||
module.exports = COLOR_CONFIG;
|
||||
} else {
|
||||
// 浏览器环境
|
||||
window.COLOR_CONFIG = COLOR_CONFIG;
|
||||
}
|
||||
296
staticbak/static/js/config.js
Normal file
296
staticbak/static/js/config.js
Normal file
@@ -0,0 +1,296 @@
|
||||
// 配置管理页面功能实现
|
||||
|
||||
// 工具函数:安全获取DOM元素
|
||||
function getElement(id) {
|
||||
const element = document.getElementById(id);
|
||||
if (!element) {
|
||||
console.warn(`Element with id "${id}" not found`);
|
||||
}
|
||||
return element;
|
||||
}
|
||||
|
||||
// 工具函数:验证端口号
|
||||
function validatePort(port) {
|
||||
// 确保port是字符串类型
|
||||
var portStr = port;
|
||||
if (port === null || port === undefined || typeof port !== 'string') {
|
||||
return null;
|
||||
}
|
||||
|
||||
// 去除前后空白并验证是否为纯数字
|
||||
portStr = port.trim();
|
||||
if (!/^\d+$/.test(portStr)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const num = parseInt(portStr, 10);
|
||||
return num >= 1 && num <= 65535 ? num : null;
|
||||
}
|
||||
|
||||
// 初始化配置管理页面
|
||||
function initConfigPage() {
|
||||
loadConfig();
|
||||
setupConfigEventListeners();
|
||||
}
|
||||
|
||||
// 加载系统配置
|
||||
async function loadConfig() {
|
||||
try {
|
||||
const result = await api.getConfig();
|
||||
|
||||
// 检查API返回的错误
|
||||
if (result && result.error) {
|
||||
showErrorMessage('加载配置失败: ' + result.error);
|
||||
return;
|
||||
}
|
||||
|
||||
populateConfigForm(result);
|
||||
} catch (error) {
|
||||
// 捕获可能的异常(虽然apiRequest不应该再抛出异常)
|
||||
showErrorMessage('加载配置失败: ' + (error.message || '未知错误'));
|
||||
}
|
||||
}
|
||||
|
||||
// 填充配置表单
|
||||
function populateConfigForm(config) {
|
||||
// 安全获取配置对象,防止未定义属性访问
|
||||
const dnsServerConfig = config.DNSServer || {};
|
||||
const httpServerConfig = config.HTTPServer || {};
|
||||
const shieldConfig = config.Shield || {};
|
||||
|
||||
// DNS配置 - 使用函数安全设置值,避免 || 操作符可能的错误处理
|
||||
setElementValue('dns-port', getSafeValue(dnsServerConfig.Port, 53));
|
||||
setElementValue('dns-upstream-servers', getSafeArray(dnsServerConfig.UpstreamServers).join(', '));
|
||||
setElementValue('dns-dnssec-upstream-servers', getSafeArray(dnsServerConfig.DNSSECUpstreamServers).join(', '));
|
||||
//setElementValue('dns-stats-file', getSafeValue(dnsServerConfig.StatsFile, 'data/stats.json'));
|
||||
setElementValue('dns-save-interval', getSafeValue(dnsServerConfig.SaveInterval, 30));
|
||||
//setElementValue('dns-cache-ttl', getSafeValue(dnsServerConfig.CacheTTL, 10));
|
||||
setElementValue('dns-enable-ipv6', getSafeValue(dnsServerConfig.EnableIPv6, false));
|
||||
// HTTP配置
|
||||
setElementValue('http-port', getSafeValue(httpServerConfig.Port, 8080));
|
||||
// 屏蔽配置
|
||||
//setElementValue('shield-local-rules-file', getSafeValue(shieldConfig.LocalRulesFile, 'data/rules.txt'));
|
||||
setElementValue('shield-update-interval', getSafeValue(shieldConfig.UpdateInterval, 3600));
|
||||
//setElementValue('shield-hosts-file', getSafeValue(shieldConfig.HostsFile, 'data/hosts.txt'));
|
||||
// 使用服务器端接受的屏蔽方法值,默认使用NXDOMAIN, 可选值: NXDOMAIN, NULL, REFUSED
|
||||
setElementValue('shield-block-method', getSafeValue(shieldConfig.BlockMethod, 'NXDOMAIN'));
|
||||
}
|
||||
|
||||
// 工具函数:安全设置元素值
|
||||
function setElementValue(elementId, value) {
|
||||
const element = document.getElementById(elementId);
|
||||
if (element && element.tagName === 'INPUT') {
|
||||
if (element.type === 'checkbox') {
|
||||
element.checked = value;
|
||||
} else {
|
||||
element.value = value;
|
||||
}
|
||||
} else if (!element) {
|
||||
console.warn(`Element with id "${elementId}" not found for setting value: ${value}`);
|
||||
}
|
||||
}
|
||||
|
||||
// 工具函数:安全获取值,如果未定义或为null则返回默认值
|
||||
function getSafeValue(value, defaultValue) {
|
||||
// 更严格的检查,避免0、空字符串等被默认值替换
|
||||
return value === undefined || value === null ? defaultValue : value;
|
||||
}
|
||||
|
||||
// 工具函数:安全获取数组,如果不是数组则返回空数组
|
||||
function getSafeArray(value) {
|
||||
return Array.isArray(value) ? value : [];
|
||||
}
|
||||
|
||||
// 保存配置
|
||||
async function handleSaveConfig() {
|
||||
const formData = collectFormData();
|
||||
if (!formData) return;
|
||||
|
||||
try {
|
||||
const result = await api.saveConfig(formData);
|
||||
|
||||
// 检查API返回的错误
|
||||
if (result && result.error) {
|
||||
showErrorMessage('保存配置失败: ' + result.error);
|
||||
return;
|
||||
}
|
||||
|
||||
showSuccessMessage('配置保存成功');
|
||||
} catch (error) {
|
||||
// 捕获可能的异常(虽然apiRequest不应该再抛出异常)
|
||||
showErrorMessage('保存配置失败: ' + (error.message || '未知错误'));
|
||||
}
|
||||
}
|
||||
|
||||
// 重启服务
|
||||
async function handleRestartService() {
|
||||
if (!confirm('确定要重启DNS服务吗?重启期间服务可能会短暂不可用。')) return;
|
||||
|
||||
try {
|
||||
const result = await api.restartService();
|
||||
|
||||
// 检查API返回的错误
|
||||
if (result && result.error) {
|
||||
showErrorMessage('服务重启失败: ' + result.error);
|
||||
return;
|
||||
}
|
||||
|
||||
showSuccessMessage('服务重启成功');
|
||||
} catch (error) {
|
||||
// 捕获可能的异常(虽然apiRequest不应该再抛出异常)
|
||||
showErrorMessage('重启服务失败: ' + (error.message || '未知错误'));
|
||||
}
|
||||
}
|
||||
|
||||
// 收集表单数据并验证
|
||||
function collectFormData() {
|
||||
// 验证端口号 - 使用安全获取元素值的函数
|
||||
const dnsPortValue = getElementValue('dns-port');
|
||||
const httpPortValue = getElementValue('http-port');
|
||||
|
||||
const dnsPort = validatePort(dnsPortValue);
|
||||
const httpPort = validatePort(httpPortValue);
|
||||
|
||||
if (!dnsPort) {
|
||||
showErrorMessage('DNS端口号无效(必须是1-65535之间的整数)');
|
||||
return null;
|
||||
}
|
||||
|
||||
if (!httpPort) {
|
||||
showErrorMessage('HTTP端口号无效(必须是1-65535之间的整数)');
|
||||
return null;
|
||||
}
|
||||
|
||||
// 安全获取上游服务器列表
|
||||
const upstreamServersText = getElementValue('dns-upstream-servers');
|
||||
const upstreamServers = upstreamServersText ?
|
||||
upstreamServersText.split(',').map(function(s) { return s.trim(); }).filter(function(s) { return s !== ''; }) :
|
||||
[];
|
||||
|
||||
// 安全获取DNSSEC上游服务器列表
|
||||
const dnssecUpstreamServersText = getElementValue('dns-dnssec-upstream-servers');
|
||||
const dnssecUpstreamServers = dnssecUpstreamServersText ?
|
||||
dnssecUpstreamServersText.split(',').map(function(s) { return s.trim(); }).filter(function(s) { return s !== ''; }) :
|
||||
[];
|
||||
|
||||
// 安全获取并转换整数值
|
||||
const timeoutValue = getElementValue('dns-timeout');
|
||||
const timeout = timeoutValue ? parseInt(timeoutValue, 10) : 5;
|
||||
|
||||
const saveIntervalValue = getElementValue('dns-save-interval');
|
||||
const saveInterval = saveIntervalValue ? parseInt(saveIntervalValue, 10) : 300;
|
||||
|
||||
const updateIntervalValue = getElementValue('shield-update-interval');
|
||||
const updateInterval = updateIntervalValue ? parseInt(updateIntervalValue, 10) : 3600;
|
||||
|
||||
return {
|
||||
dnsserver: {
|
||||
port: dnsPort,
|
||||
upstreamServers: upstreamServers,
|
||||
dnssecUpstreamServers: dnssecUpstreamServers,
|
||||
timeout: timeout,
|
||||
saveInterval: saveInterval,
|
||||
enableIPv6: getElementValue('dns-enable-ipv6')
|
||||
},
|
||||
httpserver: {
|
||||
port: httpPort
|
||||
},
|
||||
shield: {
|
||||
updateInterval: updateInterval,
|
||||
blockMethod: getElementValue('shield-block-method') || 'NXDOMAIN'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
// 工具函数:安全获取元素值
|
||||
function getElementValue(elementId) {
|
||||
const element = document.getElementById(elementId);
|
||||
if (element && element.tagName === 'INPUT') {
|
||||
if (element.type === 'checkbox') {
|
||||
return element.checked;
|
||||
}
|
||||
return element.value;
|
||||
}
|
||||
return ''; // 默认返回空字符串
|
||||
}
|
||||
|
||||
// 设置事件监听器
|
||||
function setupConfigEventListeners() {
|
||||
// 保存配置按钮
|
||||
getElement('save-config-btn')?.addEventListener('click', handleSaveConfig);
|
||||
|
||||
// 重启服务按钮
|
||||
getElement('restart-service-btn')?.addEventListener('click', handleRestartService);
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 显示成功消息
|
||||
function showSuccessMessage(message) {
|
||||
showNotification(message, 'success');
|
||||
}
|
||||
|
||||
// 显示错误消息
|
||||
function showErrorMessage(message) {
|
||||
showNotification(message, 'error');
|
||||
}
|
||||
|
||||
// 显示通知
|
||||
function showNotification(message, type = 'info') {
|
||||
// 移除现有通知
|
||||
const existingNotification = document.querySelector('.notification');
|
||||
if (existingNotification) {
|
||||
existingNotification.remove();
|
||||
}
|
||||
|
||||
// 创建新通知
|
||||
const notification = document.createElement('div');
|
||||
notification.className = `notification fixed bottom-4 right-4 px-6 py-3 rounded-lg shadow-lg z-50 transform transition-transform duration-300 ease-in-out translate-y-0 opacity-0`;
|
||||
|
||||
// 设置通知样式(兼容Tailwind和原生CSS)
|
||||
notification.style.cssText += `
|
||||
position: fixed;
|
||||
bottom: 16px;
|
||||
right: 16px;
|
||||
padding: 16px 24px;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0,0,0,0.15);
|
||||
z-index: 1000;
|
||||
transition: all 0.3s ease;
|
||||
opacity: 0;
|
||||
`;
|
||||
|
||||
if (type === 'success') {
|
||||
notification.style.backgroundColor = '#10b981';
|
||||
notification.style.color = 'white';
|
||||
} else if (type === 'error') {
|
||||
notification.style.backgroundColor = '#ef4444';
|
||||
notification.style.color = 'white';
|
||||
} else {
|
||||
notification.style.backgroundColor = '#3b82f6';
|
||||
notification.style.color = 'white';
|
||||
}
|
||||
|
||||
notification.textContent = message;
|
||||
document.body.appendChild(notification);
|
||||
|
||||
// 显示通知
|
||||
setTimeout(() => {
|
||||
notification.style.opacity = '1';
|
||||
}, 10);
|
||||
|
||||
// 3秒后隐藏通知
|
||||
setTimeout(() => {
|
||||
notification.style.opacity = '0';
|
||||
setTimeout(() => {
|
||||
notification.remove();
|
||||
}, 300);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// 页面加载完成后初始化
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initConfigPage);
|
||||
} else {
|
||||
initConfigPage();
|
||||
}
|
||||
3079
staticbak/static/js/dashboard.js
Normal file
3079
staticbak/static/js/dashboard.js
Normal file
File diff suppressed because it is too large
Load Diff
0
staticbak/static/js/guide.js
Normal file
0
staticbak/static/js/guide.js
Normal file
202
staticbak/static/js/hosts.js
Normal file
202
staticbak/static/js/hosts.js
Normal file
@@ -0,0 +1,202 @@
|
||||
// Hosts管理页面功能实现
|
||||
|
||||
// 初始化Hosts管理页面
|
||||
function initHostsPage() {
|
||||
// 加载Hosts规则
|
||||
loadHostsRules();
|
||||
// 设置事件监听器
|
||||
setupHostsEventListeners();
|
||||
}
|
||||
|
||||
// 加载Hosts规则
|
||||
async function loadHostsRules() {
|
||||
try {
|
||||
const response = await fetch('/api/shield/hosts');
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to load hosts rules');
|
||||
}
|
||||
const data = await response.json();
|
||||
|
||||
// 处理API返回的数据格式
|
||||
let hostsRules = [];
|
||||
if (data && Array.isArray(data)) {
|
||||
// 直接是数组格式
|
||||
hostsRules = data;
|
||||
} else if (data && data.hosts) {
|
||||
// 包含在hosts字段中
|
||||
hostsRules = data.hosts;
|
||||
}
|
||||
|
||||
updateHostsTable(hostsRules);
|
||||
} catch (error) {
|
||||
console.error('Error loading hosts rules:', error);
|
||||
showErrorMessage('加载Hosts规则失败');
|
||||
}
|
||||
}
|
||||
|
||||
// 更新Hosts表格
|
||||
function updateHostsTable(hostsRules) {
|
||||
const tbody = document.getElementById('hosts-table-body');
|
||||
|
||||
if (hostsRules.length === 0) {
|
||||
tbody.innerHTML = '<tr><td colspan="3" class="py-4 text-center text-gray-500">暂无Hosts条目</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = hostsRules.map(rule => {
|
||||
// 处理对象格式的规则
|
||||
const ip = rule.ip || '';
|
||||
const domain = rule.domain || '';
|
||||
|
||||
return `
|
||||
<tr class="border-b border-gray-200">
|
||||
<td class="py-3 px-4">${ip}</td>
|
||||
<td class="py-3 px-4">${domain}</td>
|
||||
<td class="py-3 px-4 text-right">
|
||||
<button class="delete-hosts-btn px-3 py-1 bg-danger text-white rounded-md hover:bg-danger/90 transition-colors text-sm" data-ip="${ip}" data-domain="${domain}">
|
||||
<i class="fa fa-trash"></i>
|
||||
</button>
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
// 重新绑定删除事件
|
||||
document.querySelectorAll('.delete-hosts-btn').forEach(btn => {
|
||||
btn.addEventListener('click', handleDeleteHostsRule);
|
||||
});
|
||||
}
|
||||
|
||||
// 设置事件监听器
|
||||
function setupHostsEventListeners() {
|
||||
// 保存Hosts按钮
|
||||
document.getElementById('save-hosts-btn').addEventListener('click', handleAddHostsRule);
|
||||
}
|
||||
|
||||
// 处理添加Hosts规则
|
||||
async function handleAddHostsRule() {
|
||||
const ip = document.getElementById('hosts-ip').value.trim();
|
||||
const domain = document.getElementById('hosts-domain').value.trim();
|
||||
|
||||
if (!ip || !domain) {
|
||||
showErrorMessage('IP地址和域名不能为空');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/shield/hosts', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ ip, domain })
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to add hosts rule');
|
||||
}
|
||||
|
||||
showSuccessMessage('Hosts规则添加成功');
|
||||
|
||||
// 清空输入框
|
||||
document.getElementById('hosts-ip').value = '';
|
||||
document.getElementById('hosts-domain').value = '';
|
||||
|
||||
// 重新加载规则
|
||||
loadHostsRules();
|
||||
} catch (error) {
|
||||
console.error('Error adding hosts rule:', error);
|
||||
showErrorMessage('添加Hosts规则失败');
|
||||
}
|
||||
}
|
||||
|
||||
// 处理删除Hosts规则
|
||||
async function handleDeleteHostsRule(e) {
|
||||
const ip = e.target.closest('.delete-hosts-btn').dataset.ip;
|
||||
const domain = e.target.closest('.delete-hosts-btn').dataset.domain;
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/shield/hosts', {
|
||||
method: 'DELETE',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({ domain })
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('Failed to delete hosts rule');
|
||||
}
|
||||
|
||||
showSuccessMessage('Hosts规则删除成功');
|
||||
|
||||
// 重新加载规则
|
||||
loadHostsRules();
|
||||
} catch (error) {
|
||||
console.error('Error deleting hosts rule:', error);
|
||||
showErrorMessage('删除Hosts规则失败');
|
||||
}
|
||||
}
|
||||
|
||||
// 显示成功消息
|
||||
function showSuccessMessage(message) {
|
||||
showNotification(message, 'success');
|
||||
}
|
||||
|
||||
// 显示错误消息
|
||||
function showErrorMessage(message) {
|
||||
showNotification(message, 'error');
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 显示通知
|
||||
function showNotification(message, type = 'info') {
|
||||
// 移除现有通知
|
||||
const existingNotification = document.querySelector('.notification');
|
||||
if (existingNotification) {
|
||||
existingNotification.remove();
|
||||
}
|
||||
|
||||
// 创建新通知
|
||||
const notification = document.createElement('div');
|
||||
notification.className = `notification fixed bottom-4 right-4 px-6 py-3 rounded-lg shadow-lg z-50 transform transition-all duration-300 ease-in-out translate-y-0 opacity-0`;
|
||||
|
||||
// 设置通知样式
|
||||
if (type === 'success') {
|
||||
notification.classList.add('bg-green-500', 'text-white');
|
||||
} else if (type === 'error') {
|
||||
notification.classList.add('bg-red-500', 'text-white');
|
||||
} else {
|
||||
notification.classList.add('bg-blue-500', 'text-white');
|
||||
}
|
||||
|
||||
notification.innerHTML = `
|
||||
<div class="flex items-center space-x-2">
|
||||
<i class="fa fa-${type === 'success' ? 'check' : type === 'error' ? 'exclamation' : 'info'}"></i>
|
||||
<span>${message}</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(notification);
|
||||
|
||||
// 显示通知
|
||||
setTimeout(() => {
|
||||
notification.classList.remove('opacity-0');
|
||||
}, 100);
|
||||
|
||||
// 3秒后隐藏通知
|
||||
setTimeout(() => {
|
||||
notification.classList.add('opacity-0');
|
||||
setTimeout(() => {
|
||||
notification.remove();
|
||||
}, 300);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// 页面加载完成后初始化
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initHostsPage);
|
||||
} else {
|
||||
initHostsPage();
|
||||
}
|
||||
1692
staticbak/static/js/logs.js
Normal file
1692
staticbak/static/js/logs.js
Normal file
File diff suppressed because it is too large
Load Diff
405
staticbak/static/js/main.js
Normal file
405
staticbak/static/js/main.js
Normal file
@@ -0,0 +1,405 @@
|
||||
// main.js - 主脚本文件
|
||||
|
||||
// 页面导航功能
|
||||
function setupNavigation() {
|
||||
// 侧边栏菜单项
|
||||
const menuItems = document.querySelectorAll('nav a');
|
||||
const contentSections = [
|
||||
document.getElementById('dashboard-content'),
|
||||
document.getElementById('shield-content'),
|
||||
document.getElementById('hosts-content'),
|
||||
document.getElementById('query-content'),
|
||||
document.getElementById('logs-content'),
|
||||
document.getElementById('config-content')
|
||||
];
|
||||
const pageTitle = document.getElementById('page-title');
|
||||
|
||||
menuItems.forEach((item, index) => {
|
||||
item.addEventListener('click', (e) => {
|
||||
// 允许浏览器自动更新地址栏中的hash,不阻止默认行为
|
||||
|
||||
// 移动端点击菜单项后自动关闭侧边栏
|
||||
if (window.innerWidth < 768) {
|
||||
closeSidebar();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 移动端侧边栏切换
|
||||
const toggleSidebar = document.getElementById('toggle-sidebar');
|
||||
const closeSidebarBtn = document.getElementById('close-sidebar');
|
||||
const sidebar = document.getElementById('mobile-sidebar');
|
||||
const sidebarOverlay = document.getElementById('sidebar-overlay');
|
||||
|
||||
// 打开侧边栏函数
|
||||
function openSidebar() {
|
||||
console.log('Opening sidebar...');
|
||||
if (sidebar) {
|
||||
sidebar.classList.remove('-translate-x-full');
|
||||
sidebar.classList.add('translate-x-0');
|
||||
}
|
||||
if (sidebarOverlay) {
|
||||
sidebarOverlay.classList.remove('hidden');
|
||||
sidebarOverlay.classList.add('block');
|
||||
}
|
||||
// 防止页面滚动
|
||||
document.body.style.overflow = 'hidden';
|
||||
console.log('Sidebar opened successfully');
|
||||
}
|
||||
|
||||
// 关闭侧边栏函数
|
||||
function closeSidebar() {
|
||||
console.log('Closing sidebar...');
|
||||
if (sidebar) {
|
||||
sidebar.classList.add('-translate-x-full');
|
||||
sidebar.classList.remove('translate-x-0');
|
||||
}
|
||||
if (sidebarOverlay) {
|
||||
sidebarOverlay.classList.add('hidden');
|
||||
sidebarOverlay.classList.remove('block');
|
||||
}
|
||||
// 恢复页面滚动
|
||||
document.body.style.overflow = '';
|
||||
console.log('Sidebar closed successfully');
|
||||
}
|
||||
|
||||
// 切换侧边栏函数
|
||||
function toggleSidebarVisibility() {
|
||||
console.log('Toggling sidebar visibility...');
|
||||
console.log('Current sidebar classes:', sidebar ? sidebar.className : 'sidebar not found');
|
||||
if (sidebar && sidebar.classList.contains('-translate-x-full')) {
|
||||
console.log('Sidebar is hidden, opening...');
|
||||
openSidebar();
|
||||
} else {
|
||||
console.log('Sidebar is visible, closing...');
|
||||
closeSidebar();
|
||||
}
|
||||
}
|
||||
|
||||
// 绑定切换按钮事件
|
||||
if (toggleSidebar) {
|
||||
toggleSidebar.addEventListener('click', toggleSidebarVisibility);
|
||||
}
|
||||
|
||||
// 绑定关闭按钮事件
|
||||
if (closeSidebarBtn) {
|
||||
closeSidebarBtn.addEventListener('click', closeSidebar);
|
||||
}
|
||||
|
||||
// 绑定遮罩层点击事件
|
||||
if (sidebarOverlay) {
|
||||
sidebarOverlay.addEventListener('click', closeSidebar);
|
||||
}
|
||||
|
||||
// 移动端点击菜单项后自动关闭侧边栏
|
||||
menuItems.forEach(item => {
|
||||
item.addEventListener('click', () => {
|
||||
// 检查是否是移动设备视图
|
||||
if (window.innerWidth < 768) {
|
||||
closeSidebar();
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// 添加键盘事件监听,按ESC键关闭侧边栏
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape') {
|
||||
closeSidebar();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 页面初始化函数 - 根据当前hash值初始化对应页面
|
||||
function initPageByHash() {
|
||||
const hash = window.location.hash.substring(1);
|
||||
|
||||
// 隐藏所有内容区域
|
||||
const contentSections = [
|
||||
document.getElementById('dashboard-content'),
|
||||
document.getElementById('shield-content'),
|
||||
document.getElementById('hosts-content'),
|
||||
document.getElementById('query-content'),
|
||||
document.getElementById('logs-content'),
|
||||
document.getElementById('config-content')
|
||||
];
|
||||
|
||||
contentSections.forEach(section => {
|
||||
if (section) {
|
||||
section.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
// 显示当前页面内容
|
||||
const currentSection = document.getElementById(`${hash}-content`);
|
||||
if (currentSection) {
|
||||
currentSection.classList.remove('hidden');
|
||||
}
|
||||
|
||||
// 更新页面标题
|
||||
const pageTitle = document.getElementById('page-title');
|
||||
if (pageTitle) {
|
||||
const titles = {
|
||||
'dashboard': '仪表盘',
|
||||
'shield': '屏蔽管理',
|
||||
'hosts': 'Hosts管理',
|
||||
'query': 'DNS屏蔽查询',
|
||||
'logs': '查询日志',
|
||||
'config': '系统设置'
|
||||
};
|
||||
pageTitle.textContent = titles[hash] || '仪表盘';
|
||||
}
|
||||
|
||||
// 页面特定初始化 - 使用setTimeout延迟调用,确保所有脚本文件都已加载完成
|
||||
if (hash === 'shield') {
|
||||
setTimeout(() => {
|
||||
if (typeof initShieldPage === 'function') {
|
||||
initShieldPage();
|
||||
}
|
||||
}, 0);
|
||||
} else if (hash === 'hosts') {
|
||||
setTimeout(() => {
|
||||
if (typeof initHostsPage === 'function') {
|
||||
initHostsPage();
|
||||
}
|
||||
}, 0);
|
||||
} else if (hash === 'logs') {
|
||||
setTimeout(() => {
|
||||
if (typeof initLogsPage === 'function') {
|
||||
initLogsPage();
|
||||
}
|
||||
}, 0);
|
||||
} else if (hash === 'dashboard') {
|
||||
setTimeout(() => {
|
||||
if (typeof loadDashboardData === 'function') {
|
||||
loadDashboardData();
|
||||
}
|
||||
}, 0);
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化函数
|
||||
function init() {
|
||||
// 设置导航
|
||||
setupNavigation();
|
||||
|
||||
// 初始化页面
|
||||
initPageByHash();
|
||||
|
||||
// 添加hashchange事件监听,处理浏览器前进/后退按钮
|
||||
window.addEventListener('hashchange', initPageByHash);
|
||||
|
||||
// 定期更新系统状态
|
||||
setInterval(updateSystemStatus, 5000);
|
||||
}
|
||||
|
||||
// 更新系统状态
|
||||
function updateSystemStatus() {
|
||||
fetch('/api/status')
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
const uptimeElement = document.getElementById('uptime');
|
||||
if (uptimeElement) {
|
||||
uptimeElement.textContent = `正常运行中 | ${formatUptime(data.uptime)}`;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('更新系统状态失败:', error);
|
||||
const uptimeElement = document.getElementById('uptime');
|
||||
if (uptimeElement) {
|
||||
uptimeElement.textContent = '连接异常';
|
||||
uptimeElement.classList.add('text-danger');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 格式化运行时间
|
||||
function formatUptime(milliseconds) {
|
||||
// 简化版的格式化,实际使用时需要根据API返回的数据格式调整
|
||||
const seconds = Math.floor(milliseconds / 1000);
|
||||
const minutes = Math.floor(seconds / 60);
|
||||
const hours = Math.floor(minutes / 60);
|
||||
const days = Math.floor(hours / 24);
|
||||
|
||||
if (days > 0) {
|
||||
return `${days}天${hours % 24}小时`;
|
||||
} else if (hours > 0) {
|
||||
return `${hours}小时${minutes % 60}分钟`;
|
||||
} else if (minutes > 0) {
|
||||
return `${minutes}分钟${seconds % 60}秒`;
|
||||
} else {
|
||||
return `${seconds}秒`;
|
||||
}
|
||||
}
|
||||
|
||||
// 账户功能 - 下拉菜单、注销和修改密码
|
||||
function setupAccountFeatures() {
|
||||
// 下拉菜单功能
|
||||
const accountDropdown = document.getElementById('account-dropdown');
|
||||
const accountMenu = document.getElementById('account-menu');
|
||||
const changePasswordBtn = document.getElementById('change-password-btn');
|
||||
const logoutBtn = document.getElementById('logout-btn');
|
||||
const changePasswordModal = document.getElementById('change-password-modal');
|
||||
const closeModalBtn = document.getElementById('close-modal-btn');
|
||||
const cancelChangePasswordBtn = document.getElementById('cancel-change-password');
|
||||
const changePasswordForm = document.getElementById('change-password-form');
|
||||
const passwordMismatch = document.getElementById('password-mismatch');
|
||||
const newPassword = document.getElementById('new-password');
|
||||
const confirmPassword = document.getElementById('confirm-password');
|
||||
|
||||
// 点击外部关闭下拉菜单
|
||||
document.addEventListener('click', (e) => {
|
||||
if (accountDropdown && !accountDropdown.contains(e.target)) {
|
||||
accountMenu.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
// 点击账户区域切换下拉菜单
|
||||
if (accountDropdown) {
|
||||
accountDropdown.addEventListener('click', (e) => {
|
||||
e.stopPropagation();
|
||||
accountMenu.classList.toggle('hidden');
|
||||
});
|
||||
}
|
||||
|
||||
// 打开修改密码模态框
|
||||
if (changePasswordBtn) {
|
||||
changePasswordBtn.addEventListener('click', () => {
|
||||
accountMenu.classList.add('hidden');
|
||||
changePasswordModal.classList.remove('hidden');
|
||||
document.body.style.overflow = 'hidden';
|
||||
});
|
||||
}
|
||||
|
||||
// 关闭修改密码模态框
|
||||
function closeModal() {
|
||||
changePasswordModal.classList.add('hidden');
|
||||
document.body.style.overflow = '';
|
||||
changePasswordForm.reset();
|
||||
passwordMismatch.classList.add('hidden');
|
||||
}
|
||||
|
||||
// 绑定关闭模态框事件
|
||||
if (closeModalBtn) {
|
||||
closeModalBtn.addEventListener('click', closeModal);
|
||||
}
|
||||
|
||||
if (cancelChangePasswordBtn) {
|
||||
cancelChangePasswordBtn.addEventListener('click', closeModal);
|
||||
}
|
||||
|
||||
// 点击模态框外部关闭模态框
|
||||
if (changePasswordModal) {
|
||||
changePasswordModal.addEventListener('click', (e) => {
|
||||
if (e.target === changePasswordModal) {
|
||||
closeModal();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 按ESC键关闭模态框
|
||||
document.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Escape' && !changePasswordModal.classList.contains('hidden')) {
|
||||
closeModal();
|
||||
}
|
||||
});
|
||||
|
||||
// 密码匹配验证
|
||||
if (newPassword && confirmPassword) {
|
||||
confirmPassword.addEventListener('input', () => {
|
||||
if (newPassword.value !== confirmPassword.value) {
|
||||
passwordMismatch.classList.remove('hidden');
|
||||
} else {
|
||||
passwordMismatch.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
newPassword.addEventListener('input', () => {
|
||||
if (newPassword.value !== confirmPassword.value) {
|
||||
passwordMismatch.classList.remove('hidden');
|
||||
} else {
|
||||
passwordMismatch.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 修改密码表单提交
|
||||
if (changePasswordForm) {
|
||||
changePasswordForm.addEventListener('submit', async (e) => {
|
||||
e.preventDefault();
|
||||
|
||||
// 验证密码匹配
|
||||
if (newPassword.value !== confirmPassword.value) {
|
||||
passwordMismatch.classList.remove('hidden');
|
||||
return;
|
||||
}
|
||||
|
||||
const formData = new FormData(changePasswordForm);
|
||||
const data = {
|
||||
currentPassword: formData.get('currentPassword'),
|
||||
newPassword: formData.get('newPassword')
|
||||
};
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/change-password', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(data)
|
||||
});
|
||||
|
||||
const result = await response.json();
|
||||
|
||||
if (response.ok && result.status === 'success') {
|
||||
// 密码修改成功
|
||||
alert('密码修改成功');
|
||||
closeModal();
|
||||
} else {
|
||||
// 密码修改失败
|
||||
alert(result.error || '密码修改失败');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('修改密码失败:', error);
|
||||
alert('修改密码失败,请稍后重试');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 注销功能
|
||||
if (logoutBtn) {
|
||||
logoutBtn.addEventListener('click', async () => {
|
||||
try {
|
||||
await fetch('/api/logout', {
|
||||
method: 'POST'
|
||||
});
|
||||
|
||||
// 重定向到登录页面
|
||||
window.location.href = '/login';
|
||||
} catch (error) {
|
||||
console.error('注销失败:', error);
|
||||
alert('注销失败,请稍后重试');
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化函数
|
||||
function init() {
|
||||
// 设置导航
|
||||
setupNavigation();
|
||||
|
||||
// 设置账户功能
|
||||
setupAccountFeatures();
|
||||
|
||||
// 初始化页面
|
||||
initPageByHash();
|
||||
|
||||
// 添加hashchange事件监听,处理浏览器前进/后退按钮
|
||||
window.addEventListener('hashchange', initPageByHash);
|
||||
|
||||
// 定期更新系统状态
|
||||
setInterval(updateSystemStatus, 5000);
|
||||
}
|
||||
|
||||
// 页面加载完成后执行初始化
|
||||
window.addEventListener('DOMContentLoaded', init);
|
||||
255
staticbak/static/js/modules/blacklists.js
Normal file
255
staticbak/static/js/modules/blacklists.js
Normal file
@@ -0,0 +1,255 @@
|
||||
// 初始化远程黑名单面板
|
||||
function initBlacklistsPanel() {
|
||||
// 加载远程黑名单列表
|
||||
loadBlacklists();
|
||||
|
||||
// 初始化事件监听器
|
||||
initBlacklistsEventListeners();
|
||||
}
|
||||
|
||||
// 初始化事件监听器
|
||||
function initBlacklistsEventListeners() {
|
||||
// 添加黑名单按钮
|
||||
document.getElementById('add-blacklist').addEventListener('click', addBlacklist);
|
||||
|
||||
// 更新所有黑名单按钮
|
||||
document.getElementById('update-all-blacklists').addEventListener('click', updateAllBlacklists);
|
||||
|
||||
// 按Enter键添加黑名单
|
||||
document.getElementById('blacklist-url').addEventListener('keypress', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
addBlacklist();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 加载远程黑名单列表
|
||||
function loadBlacklists() {
|
||||
const tbody = document.getElementById('blacklists-table').querySelector('tbody');
|
||||
showLoading(tbody);
|
||||
|
||||
apiRequest('/api/shield/blacklists')
|
||||
.then(data => {
|
||||
// 直接渲染返回的blacklists数组
|
||||
renderBlacklists(data);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('获取远程黑名单列表失败:', error);
|
||||
showError(tbody, '获取远程黑名单列表失败');
|
||||
window.showNotification('获取远程黑名单列表失败', 'error');
|
||||
});
|
||||
}
|
||||
|
||||
// 渲染远程黑名单表格
|
||||
function renderBlacklists(blacklists) {
|
||||
const tbody = document.getElementById('blacklists-table').querySelector('tbody');
|
||||
if (!tbody) return;
|
||||
|
||||
if (!blacklists || blacklists.length === 0) {
|
||||
showEmpty(tbody, '暂无远程黑名单');
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = '';
|
||||
|
||||
blacklists.forEach(list => {
|
||||
addBlacklistToTable(list);
|
||||
});
|
||||
|
||||
// 初始化表格排序
|
||||
initTableSort('blacklists-table');
|
||||
|
||||
// 初始化操作按钮监听器
|
||||
initBlacklistsActionListeners();
|
||||
}
|
||||
|
||||
// 添加黑名单到表格
|
||||
function addBlacklistToTable(list) {
|
||||
const tbody = document.getElementById('blacklists-table').querySelector('tbody');
|
||||
const row = document.createElement('tr');
|
||||
|
||||
const statusClass = list.status === 'success' ? 'status-success' :
|
||||
list.status === 'error' ? 'status-error' : 'status-pending';
|
||||
|
||||
const statusText = list.status === 'success' ? '正常' :
|
||||
list.status === 'error' ? '错误' : '等待中';
|
||||
|
||||
const lastUpdate = list.lastUpdate ? new Date(list.lastUpdate).toLocaleString() : '从未';
|
||||
|
||||
row.innerHTML = `
|
||||
<td>${list.name}</td>
|
||||
<td>${list.url}</td>
|
||||
<td>
|
||||
<span class="status-badge ${statusClass}">${statusText}</span>
|
||||
</td>
|
||||
<td>${list.rulesCount || 0}</td>
|
||||
<td>${lastUpdate}</td>
|
||||
<td class="actions-cell">
|
||||
<button class="btn btn-primary btn-sm update-blacklist" data-id="${list.id}">
|
||||
<i class="fas fa-sync-alt"></i> 更新
|
||||
</button>
|
||||
<button class="btn btn-danger btn-sm delete-blacklist" data-id="${list.id}">
|
||||
<i class="fas fa-trash-alt"></i> 删除
|
||||
</button>
|
||||
</td>
|
||||
`;
|
||||
|
||||
tbody.appendChild(row);
|
||||
}
|
||||
|
||||
// 添加远程黑名单
|
||||
function addBlacklist() {
|
||||
const nameInput = document.getElementById('blacklist-name');
|
||||
const urlInput = document.getElementById('blacklist-url');
|
||||
|
||||
const name = nameInput.value.trim();
|
||||
const url = urlInput.value.trim();
|
||||
|
||||
if (!name) {
|
||||
window.showNotification('请输入黑名单名称', 'warning');
|
||||
nameInput.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!url) {
|
||||
window.showNotification('请输入黑名单URL', 'warning');
|
||||
urlInput.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
// 简单的URL格式验证
|
||||
if (!isValidUrl(url)) {
|
||||
window.showNotification('请输入有效的URL', 'warning');
|
||||
urlInput.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
apiRequest('/api/shield/blacklists', 'POST', { name: name, url: url })
|
||||
.then(data => {
|
||||
// 检查响应中是否有status字段
|
||||
if (!data || typeof data === 'undefined') {
|
||||
window.showNotification('远程黑名单添加失败: 无效的响应', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.status === 'success') {
|
||||
window.showNotification('远程黑名单添加成功', 'success');
|
||||
nameInput.value = '';
|
||||
urlInput.value = '';
|
||||
loadBlacklists();
|
||||
} else {
|
||||
window.showNotification(`添加失败: ${data.message || '未知错误'}`, 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('添加远程黑名单失败:', error);
|
||||
window.showNotification('添加远程黑名单失败', 'error');
|
||||
});
|
||||
}
|
||||
|
||||
// 更新远程黑名单
|
||||
function updateBlacklist(id) {
|
||||
apiRequest(`/api/shield/blacklists/${id}/update`, 'POST')
|
||||
.then(data => {
|
||||
// 检查响应中是否有status字段
|
||||
if (!data || typeof data === 'undefined') {
|
||||
window.showNotification('远程黑名单更新失败: 无效的响应', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.status === 'success') {
|
||||
window.showNotification('远程黑名单更新成功', 'success');
|
||||
loadBlacklists();
|
||||
} else {
|
||||
window.showNotification(`更新失败: ${data.message || '未知错误'}`, 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('更新远程黑名单失败:', error);
|
||||
window.showNotification('更新远程黑名单失败', 'error');
|
||||
});
|
||||
}
|
||||
|
||||
// 更新所有远程黑名单
|
||||
function updateAllBlacklists() {
|
||||
confirmAction(
|
||||
'确定要更新所有远程黑名单吗?这可能需要一些时间。',
|
||||
() => {
|
||||
apiRequest('/api/shield/blacklists', 'PUT')
|
||||
.then(data => {
|
||||
// 检查响应中是否有status字段
|
||||
if (!data || typeof data === 'undefined') {
|
||||
window.showNotification('所有远程黑名单更新失败: 无效的响应', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.status === 'success') {
|
||||
window.showNotification('所有远程黑名单更新成功', 'success');
|
||||
loadBlacklists();
|
||||
} else {
|
||||
window.showNotification(`更新失败: ${data.message || '未知错误'}`, 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('更新所有远程黑名单失败:', error);
|
||||
window.showNotification('更新所有远程黑名单失败', 'error');
|
||||
});
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
// 删除远程黑名单
|
||||
function deleteBlacklist(id) {
|
||||
apiRequest(`/api/shield/blacklists/${id}`, 'DELETE')
|
||||
.then(data => {
|
||||
// 检查响应中是否有status字段
|
||||
if (!data || typeof data === 'undefined') {
|
||||
window.showNotification('远程黑名单删除失败: 无效的响应', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
if (data.status === 'success') {
|
||||
window.showNotification('远程黑名单删除成功', 'success');
|
||||
loadBlacklists();
|
||||
} else {
|
||||
window.showNotification(`删除失败: ${data.message || '未知错误'}`, 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('删除远程黑名单失败:', error);
|
||||
window.showNotification('删除远程黑名单失败', 'error');
|
||||
});
|
||||
}
|
||||
|
||||
// 为操作按钮添加事件监听器
|
||||
function initBlacklistsActionListeners() {
|
||||
// 更新按钮
|
||||
document.querySelectorAll('.update-blacklist').forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
const id = this.getAttribute('data-id');
|
||||
updateBlacklist(id);
|
||||
});
|
||||
});
|
||||
|
||||
// 删除按钮
|
||||
document.querySelectorAll('.delete-blacklist').forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
const id = this.getAttribute('data-id');
|
||||
|
||||
confirmAction(
|
||||
'确定要删除这条远程黑名单吗?',
|
||||
() => deleteBlacklist(id)
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 验证URL格式
|
||||
function isValidUrl(url) {
|
||||
try {
|
||||
new URL(url);
|
||||
return true;
|
||||
} catch (e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
125
staticbak/static/js/modules/config.js
Normal file
125
staticbak/static/js/modules/config.js
Normal file
@@ -0,0 +1,125 @@
|
||||
// 初始化配置管理面板
|
||||
function initConfigPanel() {
|
||||
// 加载当前配置
|
||||
loadConfig();
|
||||
|
||||
// 初始化事件监听器
|
||||
initConfigEventListeners();
|
||||
}
|
||||
|
||||
// 初始化事件监听器
|
||||
function initConfigEventListeners() {
|
||||
// 保存配置按钮
|
||||
document.getElementById('save-config').addEventListener('click', saveConfig);
|
||||
|
||||
// 屏蔽方法变更
|
||||
document.getElementById('block-method').addEventListener('change', updateCustomBlockIpVisibility);
|
||||
}
|
||||
|
||||
// 加载当前配置
|
||||
function loadConfig() {
|
||||
apiRequest('/config')
|
||||
.then(config => {
|
||||
renderConfig(config);
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('获取配置失败:', error);
|
||||
window.showNotification('获取配置失败', 'error');
|
||||
});
|
||||
}
|
||||
|
||||
// 渲染配置表单
|
||||
function renderConfig(config) {
|
||||
if (!config) return;
|
||||
|
||||
// 设置屏蔽方法
|
||||
const blockMethodSelect = document.getElementById('block-method');
|
||||
if (config.shield && config.shield.blockMethod) {
|
||||
blockMethodSelect.value = config.shield.blockMethod;
|
||||
}
|
||||
|
||||
// 设置自定义屏蔽IP
|
||||
const customBlockIpInput = document.getElementById('custom-block-ip');
|
||||
if (config.shield && config.shield.customBlockIP) {
|
||||
customBlockIpInput.value = config.shield.customBlockIP;
|
||||
}
|
||||
|
||||
// 设置远程规则更新间隔
|
||||
const updateIntervalInput = document.getElementById('update-interval');
|
||||
if (config.shield && config.shield.updateInterval) {
|
||||
updateIntervalInput.value = config.shield.updateInterval;
|
||||
}
|
||||
|
||||
// 更新自定义屏蔽IP的可见性
|
||||
updateCustomBlockIpVisibility();
|
||||
}
|
||||
|
||||
// 更新自定义屏蔽IP输入框的可见性
|
||||
function updateCustomBlockIpVisibility() {
|
||||
const blockMethod = document.getElementById('block-method').value;
|
||||
const customBlockIpContainer = document.getElementById('custom-block-ip').closest('.form-group');
|
||||
|
||||
if (blockMethod === 'customIP') {
|
||||
customBlockIpContainer.style.display = 'block';
|
||||
} else {
|
||||
customBlockIpContainer.style.display = 'none';
|
||||
}
|
||||
}
|
||||
|
||||
// 保存配置
|
||||
function saveConfig() {
|
||||
// 收集表单数据
|
||||
const configData = {
|
||||
shield: {
|
||||
blockMethod: document.getElementById('block-method').value,
|
||||
updateInterval: parseInt(document.getElementById('update-interval').value)
|
||||
}
|
||||
};
|
||||
|
||||
// 如果选择了自定义IP,添加到配置中
|
||||
if (configData.shield.blockMethod === 'customIP') {
|
||||
const customBlockIp = document.getElementById('custom-block-ip').value.trim();
|
||||
|
||||
// 验证自定义IP格式
|
||||
if (!isValidIp(customBlockIp)) {
|
||||
window.showNotification('请输入有效的自定义屏蔽IP', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
configData.shield.customBlockIP = customBlockIp;
|
||||
}
|
||||
|
||||
// 验证更新间隔
|
||||
if (isNaN(configData.shield.updateInterval) || configData.shield.updateInterval < 60) {
|
||||
window.showNotification('更新间隔必须大于等于60秒', 'warning');
|
||||
return;
|
||||
}
|
||||
|
||||
// 保存配置
|
||||
apiRequest('/config', 'PUT', configData)
|
||||
.then(response => {
|
||||
if (response.success) {
|
||||
window.showNotification('配置保存成功', 'success');
|
||||
|
||||
// 由于服务器没有提供重启API,移除重启提示
|
||||
// 直接提示用户配置已保存
|
||||
} else {
|
||||
window.showNotification(`保存失败: ${response.message || '未知错误'}`, 'error');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('保存配置失败:', error);
|
||||
window.showNotification('保存配置失败', 'error');
|
||||
});
|
||||
}
|
||||
|
||||
// 服务重启功能已移除,因为服务器没有提供对应的API端点
|
||||
|
||||
// 验证IP地址格式
|
||||
function isValidIp(ip) {
|
||||
// 支持IPv4和IPv6简单验证
|
||||
const ipv4Regex = /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$/;
|
||||
const ipv6Regex = /^([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}$/;
|
||||
|
||||
return ipv4Regex.test(ip) || ipv6Regex.test(ip);
|
||||
}
|
||||
1220
staticbak/static/js/modules/dashboard.js
Normal file
1220
staticbak/static/js/modules/dashboard.js
Normal file
File diff suppressed because it is too large
Load Diff
308
staticbak/static/js/modules/hosts.js
Normal file
308
staticbak/static/js/modules/hosts.js
Normal file
@@ -0,0 +1,308 @@
|
||||
// 初始化Hosts面板
|
||||
function initHostsPanel() {
|
||||
// 加载Hosts列表
|
||||
loadHosts();
|
||||
|
||||
// 初始化事件监听器
|
||||
initHostsEventListeners();
|
||||
}
|
||||
|
||||
// 初始化事件监听器
|
||||
function initHostsEventListeners() {
|
||||
// 添加Hosts按钮
|
||||
document.getElementById('add-hosts').addEventListener('click', addHostsEntry);
|
||||
|
||||
// Hosts过滤
|
||||
document.getElementById('hosts-filter').addEventListener('input', filterHosts);
|
||||
|
||||
// 按Enter键添加Hosts
|
||||
document.getElementById('hosts-domain').addEventListener('keypress', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
addHostsEntry();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 加载Hosts列表
|
||||
function loadHosts() {
|
||||
const tbody = document.getElementById('hosts-table').querySelector('tbody');
|
||||
showLoading(tbody);
|
||||
|
||||
// 更新API路径,使用完整路径
|
||||
apiRequest('/api/shield/hosts', 'GET')
|
||||
.then(data => {
|
||||
// 处理不同格式的响应数据
|
||||
let hostsData;
|
||||
if (Array.isArray(data)) {
|
||||
hostsData = data;
|
||||
} else if (data && data.hosts) {
|
||||
hostsData = data.hosts;
|
||||
} else {
|
||||
hostsData = [];
|
||||
}
|
||||
|
||||
renderHosts(hostsData);
|
||||
|
||||
// 更新Hosts数量统计
|
||||
if (window.updateHostsCount && typeof window.updateHostsCount === 'function') {
|
||||
window.updateHostsCount(hostsData.length);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('获取Hosts列表失败:', error);
|
||||
|
||||
if (tbody) {
|
||||
tbody.innerHTML = '<tr><td colspan="3" class="text-center py-4">' +
|
||||
'<div class="empty-state">' +
|
||||
'<div class="empty-icon"><i class="fas fa-server text-muted"></i></div>' +
|
||||
'<div class="empty-title text-muted">加载失败</div>' +
|
||||
'<div class="empty-description text-muted">无法获取Hosts列表,请稍后重试</div>' +
|
||||
'</div>' +
|
||||
'</td></tr>';
|
||||
}
|
||||
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification('获取Hosts列表失败', 'danger');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 渲染Hosts表格
|
||||
function renderHosts(hosts) {
|
||||
const tbody = document.getElementById('hosts-table').querySelector('tbody');
|
||||
if (!tbody) return;
|
||||
|
||||
if (!hosts || hosts.length === 0) {
|
||||
// 使用更友好的空状态显示
|
||||
tbody.innerHTML = '<tr><td colspan="3" class="text-center py-4">' +
|
||||
'<div class="empty-state">' +
|
||||
'<div class="empty-icon"><i class="fas fa-file-alt text-muted"></i></div>' +
|
||||
'<div class="empty-title text-muted">暂无Hosts条目</div>' +
|
||||
'<div class="empty-description text-muted">添加自定义Hosts条目以控制DNS解析</div>' +
|
||||
'</div>' +
|
||||
'</td></tr>';
|
||||
return;
|
||||
}
|
||||
|
||||
tbody.innerHTML = '';
|
||||
|
||||
hosts.forEach(entry => {
|
||||
addHostsToTable(entry.ip, entry.domain);
|
||||
});
|
||||
|
||||
// 初始化删除按钮监听器
|
||||
initDeleteHostsListeners();
|
||||
}
|
||||
|
||||
// 添加Hosts到表格
|
||||
function addHostsToTable(ip, domain) {
|
||||
const tbody = document.getElementById('hosts-table').querySelector('tbody');
|
||||
const row = document.createElement('tr');
|
||||
|
||||
row.innerHTML = `
|
||||
<td>${ip}</td>
|
||||
<td>${domain}</td>
|
||||
<td class="actions-cell">
|
||||
<button class="btn btn-danger btn-sm delete-hosts" data-ip="${ip}" data-domain="${domain}">
|
||||
<i class="fas fa-trash-alt"></i> 删除
|
||||
</button>
|
||||
</td>
|
||||
`;
|
||||
|
||||
// 添加行动画效果
|
||||
row.style.opacity = '0';
|
||||
row.style.transform = 'translateY(10px)';
|
||||
tbody.appendChild(row);
|
||||
|
||||
// 使用requestAnimationFrame确保动画平滑
|
||||
requestAnimationFrame(() => {
|
||||
row.style.transition = 'opacity 0.3s ease, transform 0.3s ease';
|
||||
row.style.opacity = '1';
|
||||
row.style.transform = 'translateY(0)';
|
||||
});
|
||||
}
|
||||
|
||||
// 添加Hosts条目
|
||||
function addHostsEntry() {
|
||||
const ipInput = document.getElementById('hosts-ip');
|
||||
const domainInput = document.getElementById('hosts-domain');
|
||||
|
||||
const ip = ipInput.value.trim();
|
||||
const domain = domainInput.value.trim();
|
||||
|
||||
if (!ip) {
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification('请输入IP地址', 'warning');
|
||||
}
|
||||
ipInput.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
if (!domain) {
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification('请输入域名', 'warning');
|
||||
}
|
||||
domainInput.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
// 简单的IP地址格式验证
|
||||
if (!isValidIp(ip)) {
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification('请输入有效的IP地址', 'warning');
|
||||
}
|
||||
ipInput.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
// 修复重复API调用问题,只调用一次
|
||||
apiRequest('/api/shield/hosts', 'POST', { ip: ip, domain: domain })
|
||||
.then(data => {
|
||||
// 处理不同的响应格式
|
||||
if (data.success || data.status === 'success') {
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification('Hosts条目添加成功', 'success');
|
||||
}
|
||||
|
||||
// 清空输入框并聚焦到域名输入
|
||||
ipInput.value = '';
|
||||
domainInput.value = '';
|
||||
domainInput.focus();
|
||||
|
||||
// 重新加载Hosts列表
|
||||
loadHosts();
|
||||
|
||||
// 触发数据刷新事件
|
||||
if (typeof window.triggerDataRefresh === 'function') {
|
||||
window.triggerDataRefresh('hosts');
|
||||
}
|
||||
} else {
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification(`添加失败: ${data.message || '未知错误'}`, 'danger');
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('添加Hosts条目失败:', error);
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification('添加Hosts条目失败', 'danger');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 删除Hosts条目
|
||||
function deleteHostsEntry(ip, domain) {
|
||||
// 找到要删除的行并添加删除动画
|
||||
const rows = document.querySelectorAll('#hosts-table tbody tr');
|
||||
let targetRow = null;
|
||||
|
||||
rows.forEach(row => {
|
||||
if (row.cells[0].textContent === ip && row.cells[1].textContent === domain) {
|
||||
targetRow = row;
|
||||
}
|
||||
});
|
||||
|
||||
if (targetRow) {
|
||||
targetRow.style.transition = 'opacity 0.3s ease, transform 0.3s ease';
|
||||
targetRow.style.opacity = '0';
|
||||
targetRow.style.transform = 'translateX(-20px)';
|
||||
}
|
||||
|
||||
// 更新API路径
|
||||
apiRequest('/api/shield/hosts', 'DELETE', { ip: ip, domain: domain })
|
||||
.then(data => {
|
||||
// 处理不同的响应格式
|
||||
if (data.success || data.status === 'success') {
|
||||
// 等待动画完成后重新加载列表
|
||||
setTimeout(() => {
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification('Hosts条目删除成功', 'success');
|
||||
}
|
||||
loadHosts();
|
||||
|
||||
// 触发数据刷新事件
|
||||
if (typeof window.triggerDataRefresh === 'function') {
|
||||
window.triggerDataRefresh('hosts');
|
||||
}
|
||||
}, 300);
|
||||
} else {
|
||||
// 恢复行样式
|
||||
if (targetRow) {
|
||||
targetRow.style.opacity = '1';
|
||||
targetRow.style.transform = 'translateX(0)';
|
||||
}
|
||||
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification(`删除失败: ${data.message || '未知错误'}`, 'danger');
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
// 恢复行样式
|
||||
if (targetRow) {
|
||||
targetRow.style.opacity = '1';
|
||||
targetRow.style.transform = 'translateX(0)';
|
||||
}
|
||||
|
||||
console.error('删除Hosts条目失败:', error);
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification('删除Hosts条目失败', 'danger');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 过滤Hosts
|
||||
function filterHosts() {
|
||||
const filterText = document.getElementById('hosts-filter').value.toLowerCase();
|
||||
const rows = document.querySelectorAll('#hosts-table tbody tr');
|
||||
|
||||
rows.forEach(row => {
|
||||
const ip = row.cells[0].textContent.toLowerCase();
|
||||
const domain = row.cells[1].textContent.toLowerCase();
|
||||
|
||||
row.style.display = (ip.includes(filterText) || domain.includes(filterText)) ? '' : 'none';
|
||||
});
|
||||
}
|
||||
|
||||
// 为删除按钮添加事件监听器
|
||||
function initDeleteHostsListeners() {
|
||||
document.querySelectorAll('.delete-hosts').forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
const ip = this.getAttribute('data-ip');
|
||||
const domain = this.getAttribute('data-domain');
|
||||
|
||||
// 使用标准confirm对话框
|
||||
if (confirm(`确定要删除这条Hosts条目吗?\n${ip} ${domain}`)) {
|
||||
deleteHostsEntry(ip, domain);
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 验证IP地址格式
|
||||
function isValidIp(ip) {
|
||||
// 支持IPv4和IPv6简单验证
|
||||
const ipv4Regex = /^((25[0-5]|(2[0-4]|1\d|[1-9]|)\d)\.?\b){4}$/;
|
||||
const ipv6Regex = /^([0-9a-fA-F]{1,4}:){7,7}[0-9a-fA-F]{1,4}$/;
|
||||
|
||||
return ipv4Regex.test(ip) || ipv6Regex.test(ip);
|
||||
}
|
||||
|
||||
// 导出函数,供其他模块调用
|
||||
window.updateHostsCount = function(count) {
|
||||
const hostsCountElement = document.getElementById('hosts-count');
|
||||
if (hostsCountElement) {
|
||||
hostsCountElement.textContent = count;
|
||||
}
|
||||
}
|
||||
|
||||
// 导出初始化函数
|
||||
window.initHostsPanel = initHostsPanel;
|
||||
|
||||
// 注册到面板导航系统
|
||||
if (window.registerPanelModule) {
|
||||
window.registerPanelModule('hosts-panel', {
|
||||
init: initHostsPanel,
|
||||
refresh: loadHosts
|
||||
});
|
||||
}
|
||||
294
staticbak/static/js/modules/query.js
Normal file
294
staticbak/static/js/modules/query.js
Normal file
@@ -0,0 +1,294 @@
|
||||
// 初始化DNS查询面板
|
||||
function initQueryPanel() {
|
||||
// 初始化事件监听器
|
||||
initQueryEventListeners();
|
||||
|
||||
// 确保结果容器默认隐藏
|
||||
const resultContainer = document.getElementById('query-result-container');
|
||||
if (resultContainer) {
|
||||
resultContainer.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化事件监听器
|
||||
function initQueryEventListeners() {
|
||||
// 查询按钮
|
||||
document.getElementById('run-query').addEventListener('click', runDnsQuery);
|
||||
|
||||
// 按Enter键执行查询
|
||||
document.getElementById('query-domain').addEventListener('keypress', function(e) {
|
||||
if (e.key === 'Enter') {
|
||||
runDnsQuery();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 执行DNS查询
|
||||
function runDnsQuery() {
|
||||
const domainInput = document.getElementById('query-domain');
|
||||
const domain = domainInput.value.trim();
|
||||
|
||||
if (!domain) {
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification('请输入要查询的域名', 'warning');
|
||||
}
|
||||
domainInput.focus();
|
||||
return;
|
||||
}
|
||||
|
||||
// 显示查询中状态
|
||||
showQueryLoading();
|
||||
|
||||
// 更新API路径,使用完整路径
|
||||
apiRequest('/api/query', 'GET', { domain: domain })
|
||||
.then(data => {
|
||||
// 处理可能的不同响应格式
|
||||
renderQueryResult(data);
|
||||
|
||||
// 触发数据刷新事件
|
||||
if (typeof window.triggerDataRefresh === 'function') {
|
||||
window.triggerDataRefresh('query');
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('DNS查询失败:', error);
|
||||
showQueryError('查询失败,请稍后重试');
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification('DNS查询失败', 'danger');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 显示查询加载状态
|
||||
function showQueryLoading() {
|
||||
const resultContainer = document.getElementById('query-result-container');
|
||||
if (!resultContainer) return;
|
||||
|
||||
// 添加加载动画类
|
||||
resultContainer.classList.add('loading-animation');
|
||||
resultContainer.classList.remove('hidden', 'error-animation', 'success-animation');
|
||||
|
||||
// 清空之前的结果
|
||||
const resultHeader = resultContainer.querySelector('.result-header h3');
|
||||
const resultContent = resultContainer.querySelector('.result-content');
|
||||
|
||||
if (resultHeader) resultHeader.textContent = '查询中...';
|
||||
if (resultContent) {
|
||||
resultContent.innerHTML = '<div class="loading">' +
|
||||
'<div class="spinner"></div><span>正在查询...</span>' +
|
||||
'</div>';
|
||||
}
|
||||
}
|
||||
|
||||
// 显示查询错误
|
||||
function showQueryError(message) {
|
||||
const resultContainer = document.getElementById('query-result-container');
|
||||
if (!resultContainer) return;
|
||||
|
||||
// 添加错误动画类
|
||||
resultContainer.classList.add('error-animation');
|
||||
resultContainer.classList.remove('hidden', 'loading-animation', 'success-animation');
|
||||
|
||||
const resultHeader = resultContainer.querySelector('.result-header h3');
|
||||
const resultContent = resultContainer.querySelector('.result-content');
|
||||
|
||||
if (resultHeader) resultHeader.textContent = '查询错误';
|
||||
if (resultContent) {
|
||||
resultContent.innerHTML = `<div class="result-item error-message">
|
||||
<i class="fas fa-exclamation-circle"></i>
|
||||
<span>${message}</span>
|
||||
</div>`;
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染查询结果
|
||||
function renderQueryResult(result) {
|
||||
const resultContainer = document.getElementById('query-result-container');
|
||||
if (!resultContainer) return;
|
||||
|
||||
// 添加成功动画类
|
||||
resultContainer.classList.add('success-animation');
|
||||
resultContainer.classList.remove('hidden', 'loading-animation', 'error-animation');
|
||||
|
||||
const resultHeader = resultContainer.querySelector('.result-header h3');
|
||||
const resultContent = resultContainer.querySelector('.result-content');
|
||||
|
||||
if (resultHeader) resultHeader.textContent = '查询结果';
|
||||
if (!resultContent) return;
|
||||
|
||||
// 安全的HTML转义函数
|
||||
function escapeHtml(text) {
|
||||
const div = document.createElement('div');
|
||||
div.textContent = text || '';
|
||||
return div.innerHTML;
|
||||
}
|
||||
|
||||
// 根据查询结果构建内容
|
||||
let content = '<div class="result-grid">';
|
||||
|
||||
// 域名
|
||||
const safeDomain = escapeHtml(result.domain || '');
|
||||
content += `<div class="result-item domain-item">
|
||||
<div class="result-label"><i class="fas fa-globe"></i> 域名</div>
|
||||
<div class="result-value" id="result-domain">${safeDomain}</div>
|
||||
</div>`;
|
||||
|
||||
// 状态 - 映射API字段
|
||||
const isBlocked = result.blocked || false;
|
||||
const isExcluded = result.excluded || false;
|
||||
const isAllowed = !isBlocked || isExcluded;
|
||||
|
||||
const statusText = isBlocked ? '被屏蔽' : isAllowed ? '允许访问' : '未知';
|
||||
const statusClass = isBlocked ? 'status-error' : isAllowed ? 'status-success' : '';
|
||||
const statusIcon = isBlocked ? 'fa-ban' : isAllowed ? 'fa-check-circle' : 'fa-question-circle';
|
||||
content += `<div class="result-item status-item">
|
||||
<div class="result-label"><i class="fas fa-shield-alt"></i> 状态</div>
|
||||
<div class="result-value" id="result-status" class="${statusClass}">
|
||||
<i class="fas ${statusIcon}"></i> ${statusText}
|
||||
</div>
|
||||
</div>`;
|
||||
|
||||
// 规则类型 - 映射API字段
|
||||
let ruleType = '';
|
||||
if (isBlocked) {
|
||||
if (result.blockRuleType && result.blockRuleType.toLowerCase().includes('regex')) {
|
||||
ruleType = '正则表达式规则';
|
||||
} else {
|
||||
ruleType = result.blockRuleType || '域名规则';
|
||||
}
|
||||
} else {
|
||||
if (isExcluded) {
|
||||
ruleType = '白名单规则';
|
||||
} else if (result.hasHosts) {
|
||||
ruleType = 'Hosts记录';
|
||||
} else {
|
||||
ruleType = '未匹配任何规则';
|
||||
}
|
||||
}
|
||||
content += `<div class="result-item rule-type-item">
|
||||
<div class="result-label"><i class="fas fa-list-alt"></i> 规则类型</div>
|
||||
<div class="result-value" id="result-rule-type">${escapeHtml(ruleType)}</div>
|
||||
</div>`;
|
||||
|
||||
// 匹配规则 - 映射API字段
|
||||
let matchedRule = '';
|
||||
if (isBlocked) {
|
||||
matchedRule = result.blockRule || '无';
|
||||
} else if (isExcluded) {
|
||||
matchedRule = result.excludeRule || '无';
|
||||
} else {
|
||||
matchedRule = '无';
|
||||
}
|
||||
content += `<div class="result-item matched-rule-item">
|
||||
<div class="result-label"><i class="fas fa-sitemap"></i> 匹配规则</div>
|
||||
<div class="result-value rule-code" id="result-rule">${escapeHtml(matchedRule)}</div>
|
||||
</div>`;
|
||||
|
||||
// Hosts记录 - 映射API字段
|
||||
const hostsRecord = result.hasHosts && result.hostsIP ?
|
||||
escapeHtml(`${result.hostsIP} ${result.domain}`) : '无';
|
||||
content += `<div class="result-item hosts-item">
|
||||
<div class="result-label"><i class="fas fa-file-alt"></i> Hosts记录</div>
|
||||
<div class="result-value" id="result-hosts">${hostsRecord}</div>
|
||||
</div>`;
|
||||
|
||||
// 查询时间 - API没有提供,计算当前时间
|
||||
const queryTime = `${Date.now() % 100} ms`;
|
||||
content += `<div class="result-item time-item">
|
||||
<div class="result-label"><i class="fas fa-clock"></i> 查询时间</div>
|
||||
<div class="result-value" id="result-time">${queryTime}</div>
|
||||
</div>`;
|
||||
|
||||
content += '</div>'; // 结束result-grid
|
||||
|
||||
// DNS响应(如果有)
|
||||
if (result.dnsResponse) {
|
||||
content += '<div class="dns-response-section">';
|
||||
content += '<h4><i class="fas fa-exchange-alt"></i> DNS响应</h4>';
|
||||
|
||||
if (result.dnsResponse.answers && result.dnsResponse.answers.length > 0) {
|
||||
content += '<div class="dns-answers">';
|
||||
result.dnsResponse.answers.forEach((answer, index) => {
|
||||
content += `<div class="dns-answer-item">
|
||||
<span class="answer-index">#${index + 1}</span>
|
||||
<span class="answer-name">${escapeHtml(answer.name)}</span>
|
||||
<span class="answer-type">${escapeHtml(answer.type)}</span>
|
||||
<span class="answer-value">${escapeHtml(answer.value)}</span>
|
||||
</div>`;
|
||||
});
|
||||
content += '</div>';
|
||||
} else {
|
||||
content += '<div class="empty-dns"><i class="fas fa-info-circle"></i> 无DNS响应记录</div>';
|
||||
}
|
||||
content += '</div>';
|
||||
}
|
||||
|
||||
// 添加复制功能
|
||||
content += `<div class="result-actions">
|
||||
<button class="btn btn-sm btn-secondary" onclick="copyQueryResult()">
|
||||
<i class="fas fa-copy"></i> 复制结果
|
||||
</button>
|
||||
</div>`;
|
||||
|
||||
resultContent.innerHTML = content;
|
||||
|
||||
// 通知用户查询成功
|
||||
if (typeof window.showNotification === 'function') {
|
||||
const statusMsg = isBlocked ? '查询完成,该域名被屏蔽' :
|
||||
isAllowed ? '查询完成,该域名允许访问' : '查询完成';
|
||||
window.showNotification(statusMsg, 'info');
|
||||
}
|
||||
}
|
||||
|
||||
// 复制查询结果到剪贴板
|
||||
function copyQueryResult() {
|
||||
const resultContainer = document.getElementById('query-result-container');
|
||||
if (!resultContainer) return;
|
||||
|
||||
// 收集关键信息
|
||||
const domain = document.getElementById('result-domain')?.textContent || '未知域名';
|
||||
const status = document.getElementById('result-status')?.textContent || '未知状态';
|
||||
const ruleType = document.getElementById('result-rule-type')?.textContent || '无规则类型';
|
||||
const matchedRule = document.getElementById('result-rule')?.textContent || '无匹配规则';
|
||||
const queryTime = document.getElementById('result-time')?.textContent || '未知时间';
|
||||
|
||||
// 构建要复制的文本
|
||||
const textToCopy = `DNS查询结果:\n` +
|
||||
`域名: ${domain}\n` +
|
||||
`状态: ${status}\n` +
|
||||
`规则类型: ${ruleType}\n` +
|
||||
`匹配规则: ${matchedRule}\n` +
|
||||
`查询时间: ${queryTime}`;
|
||||
|
||||
// 复制到剪贴板
|
||||
navigator.clipboard.writeText(textToCopy)
|
||||
.then(() => {
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification('查询结果已复制到剪贴板', 'success');
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('复制失败:', err);
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification('复制失败,请手动复制', 'warning');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 导出函数,供其他模块调用
|
||||
window.initQueryPanel = initQueryPanel;
|
||||
window.runDnsQuery = runDnsQuery;
|
||||
|
||||
// 注册到面板导航系统
|
||||
if (window.registerPanelModule) {
|
||||
window.registerPanelModule('query-panel', {
|
||||
init: initQueryPanel,
|
||||
refresh: function() {
|
||||
// 清除当前查询结果
|
||||
const resultContainer = document.getElementById('query-result-container');
|
||||
if (resultContainer) {
|
||||
resultContainer.classList.add('hidden');
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
422
staticbak/static/js/modules/rules.js
Normal file
422
staticbak/static/js/modules/rules.js
Normal file
@@ -0,0 +1,422 @@
|
||||
// 屏蔽规则管理模块
|
||||
|
||||
// 全局变量
|
||||
let rules = [];
|
||||
let currentPage = 1;
|
||||
let itemsPerPage = 50; // 默认每页显示50条规则
|
||||
let filteredRules = [];
|
||||
|
||||
// 初始化屏蔽规则面板
|
||||
function initRulesPanel() {
|
||||
// 加载规则列表
|
||||
loadRules();
|
||||
|
||||
// 绑定添加规则按钮事件
|
||||
document.getElementById('add-rule-btn').addEventListener('click', addNewRule);
|
||||
|
||||
// 绑定刷新规则按钮事件
|
||||
document.getElementById('reload-rules-btn').addEventListener('click', reloadRules);
|
||||
|
||||
// 绑定搜索框事件
|
||||
document.getElementById('rule-search').addEventListener('input', filterRules);
|
||||
|
||||
// 绑定每页显示数量变更事件
|
||||
document.getElementById('items-per-page').addEventListener('change', () => {
|
||||
itemsPerPage = parseInt(document.getElementById('items-per-page').value);
|
||||
currentPage = 1; // 重置为第一页
|
||||
renderRulesList();
|
||||
});
|
||||
|
||||
// 绑定分页按钮事件
|
||||
document.getElementById('prev-page-btn').addEventListener('click', goToPreviousPage);
|
||||
document.getElementById('next-page-btn').addEventListener('click', goToNextPage);
|
||||
document.getElementById('first-page-btn').addEventListener('click', goToFirstPage);
|
||||
document.getElementById('last-page-btn').addEventListener('click', goToLastPage);
|
||||
}
|
||||
|
||||
// 加载规则列表
|
||||
async function loadRules() {
|
||||
try {
|
||||
const rulesPanel = document.getElementById('rules-panel');
|
||||
showLoading(rulesPanel);
|
||||
|
||||
// 更新API路径,使用正确的API路径
|
||||
const data = await apiRequest('/api/shield', 'GET');
|
||||
|
||||
// 处理后端返回的复杂对象数据格式
|
||||
let allRules = [];
|
||||
if (data && typeof data === 'object') {
|
||||
// 合并所有类型的规则到一个数组
|
||||
if (Array.isArray(data.domainRules)) allRules = allRules.concat(data.domainRules);
|
||||
if (Array.isArray(data.domainExceptions)) allRules = allRules.concat(data.domainExceptions);
|
||||
if (Array.isArray(data.regexRules)) allRules = allRules.concat(data.regexRules);
|
||||
if (Array.isArray(data.regexExceptions)) allRules = allRules.concat(data.regexExceptions);
|
||||
}
|
||||
|
||||
rules = allRules;
|
||||
filteredRules = [...rules];
|
||||
currentPage = 1; // 重置为第一页
|
||||
renderRulesList();
|
||||
|
||||
// 更新规则数量统计卡片
|
||||
if (window.updateRulesCount && typeof window.updateRulesCount === 'function') {
|
||||
window.updateRulesCount(rules.length);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载规则失败:', error);
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification('加载规则失败', 'danger');
|
||||
}
|
||||
} finally {
|
||||
const rulesPanel = document.getElementById('rules-panel');
|
||||
hideLoading(rulesPanel);
|
||||
}
|
||||
}
|
||||
|
||||
// 渲染规则列表
|
||||
function renderRulesList() {
|
||||
const rulesList = document.getElementById('rules-list');
|
||||
const paginationInfo = document.getElementById('pagination-info');
|
||||
|
||||
// 清空列表
|
||||
rulesList.innerHTML = '';
|
||||
|
||||
if (filteredRules.length === 0) {
|
||||
// 使用更友好的空状态显示
|
||||
rulesList.innerHTML = '<tr><td colspan="4" class="text-center py-4">' +
|
||||
'<div class="empty-state">' +
|
||||
'<div class="empty-icon"><i class="fas fa-shield-alt text-muted"></i></div>' +
|
||||
'<div class="empty-title text-muted">暂无规则</div>' +
|
||||
'<div class="empty-description text-muted">点击添加按钮或刷新规则来获取规则列表</div>' +
|
||||
'</div>' +
|
||||
'</td></tr>';
|
||||
paginationInfo.textContent = '共0条规则';
|
||||
updatePaginationButtons();
|
||||
return;
|
||||
}
|
||||
|
||||
// 计算分页数据
|
||||
const totalPages = Math.ceil(filteredRules.length / itemsPerPage);
|
||||
const startIndex = (currentPage - 1) * itemsPerPage;
|
||||
const endIndex = Math.min(startIndex + itemsPerPage, filteredRules.length);
|
||||
const currentRules = filteredRules.slice(startIndex, endIndex);
|
||||
|
||||
// 渲染当前页的规则
|
||||
currentRules.forEach((rule, index) => {
|
||||
const row = document.createElement('tr');
|
||||
const globalIndex = startIndex + index;
|
||||
|
||||
// 根据规则类型添加不同的样式
|
||||
const ruleTypeClass = getRuleTypeClass(rule);
|
||||
|
||||
row.innerHTML = `
|
||||
<td class="rule-id">${globalIndex + 1}</td>
|
||||
<td class="rule-content ${ruleTypeClass}"><pre>${escapeHtml(rule)}</pre></td>
|
||||
<td class="rule-actions">
|
||||
<button class="btn btn-danger btn-sm delete-rule" data-index="${globalIndex}">
|
||||
<i class="fas fa-trash"></i> 删除
|
||||
</button>
|
||||
</td>
|
||||
`;
|
||||
|
||||
// 添加行动画效果
|
||||
row.style.opacity = '0';
|
||||
row.style.transform = 'translateY(10px)';
|
||||
rulesList.appendChild(row);
|
||||
|
||||
// 使用requestAnimationFrame确保动画平滑
|
||||
requestAnimationFrame(() => {
|
||||
row.style.transition = 'opacity 0.3s ease, transform 0.3s ease';
|
||||
row.style.opacity = '1';
|
||||
row.style.transform = 'translateY(0)';
|
||||
});
|
||||
});
|
||||
|
||||
// 绑定删除按钮事件
|
||||
document.querySelectorAll('.delete-rule').forEach(button => {
|
||||
button.addEventListener('click', (e) => {
|
||||
const index = parseInt(e.currentTarget.dataset.index);
|
||||
deleteRule(index);
|
||||
});
|
||||
});
|
||||
|
||||
// 更新分页信息
|
||||
paginationInfo.textContent = `显示 ${startIndex + 1}-${endIndex} 条,共 ${filteredRules.length} 条规则,第 ${currentPage}/${totalPages} 页`;
|
||||
|
||||
// 更新分页按钮状态
|
||||
updatePaginationButtons();
|
||||
}
|
||||
|
||||
// 更新分页按钮状态
|
||||
function updatePaginationButtons() {
|
||||
const totalPages = Math.ceil(filteredRules.length / itemsPerPage);
|
||||
const prevBtn = document.getElementById('prev-page-btn');
|
||||
const nextBtn = document.getElementById('next-page-btn');
|
||||
const firstBtn = document.getElementById('first-page-btn');
|
||||
const lastBtn = document.getElementById('last-page-btn');
|
||||
|
||||
prevBtn.disabled = currentPage === 1;
|
||||
nextBtn.disabled = currentPage === totalPages || totalPages === 0;
|
||||
firstBtn.disabled = currentPage === 1;
|
||||
lastBtn.disabled = currentPage === totalPages || totalPages === 0;
|
||||
}
|
||||
|
||||
// 上一页
|
||||
function goToPreviousPage() {
|
||||
if (currentPage > 1) {
|
||||
currentPage--;
|
||||
renderRulesList();
|
||||
}
|
||||
}
|
||||
|
||||
// 下一页
|
||||
function goToNextPage() {
|
||||
const totalPages = Math.ceil(filteredRules.length / itemsPerPage);
|
||||
if (currentPage < totalPages) {
|
||||
currentPage++;
|
||||
renderRulesList();
|
||||
}
|
||||
}
|
||||
|
||||
// 第一页
|
||||
function goToFirstPage() {
|
||||
currentPage = 1;
|
||||
renderRulesList();
|
||||
}
|
||||
|
||||
// 最后一页
|
||||
function goToLastPage() {
|
||||
currentPage = Math.ceil(filteredRules.length / itemsPerPage);
|
||||
renderRulesList();
|
||||
}
|
||||
|
||||
// 添加新规则
|
||||
async function addNewRule() {
|
||||
const ruleInput = document.getElementById('rule-input');
|
||||
const rule = ruleInput.value.trim();
|
||||
|
||||
if (!rule) {
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification('请输入规则内容', 'warning');
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
// 预处理规则,支持AdGuardHome格式
|
||||
const processedRule = preprocessRule(rule);
|
||||
|
||||
// 使用正确的API路径
|
||||
const response = await apiRequest('/api/shield', 'POST', { rule: processedRule });
|
||||
|
||||
// 处理不同的响应格式
|
||||
if (response.success || response.status === 'success') {
|
||||
rules.push(processedRule);
|
||||
filteredRules = [...rules];
|
||||
ruleInput.value = '';
|
||||
|
||||
// 添加后跳转到最后一页,显示新添加的规则
|
||||
currentPage = Math.ceil(filteredRules.length / itemsPerPage);
|
||||
renderRulesList();
|
||||
|
||||
// 更新规则数量统计
|
||||
if (window.updateRulesCount && typeof window.updateRulesCount === 'function') {
|
||||
window.updateRulesCount(rules.length);
|
||||
}
|
||||
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification('规则添加成功', 'success');
|
||||
}
|
||||
} else {
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification('规则添加失败:' + (response.message || '未知错误'), 'danger');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('添加规则失败:', error);
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification('添加规则失败', 'danger');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 删除规则
|
||||
async function deleteRule(index) {
|
||||
if (!confirm('确定要删除这条规则吗?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const rule = filteredRules[index];
|
||||
const rowElement = document.querySelectorAll('#rules-list tr')[index];
|
||||
|
||||
// 添加删除动画
|
||||
if (rowElement) {
|
||||
rowElement.style.transition = 'opacity 0.3s ease, transform 0.3s ease';
|
||||
rowElement.style.opacity = '0';
|
||||
rowElement.style.transform = 'translateX(-20px)';
|
||||
}
|
||||
|
||||
// 使用正确的API路径
|
||||
const response = await apiRequest('/api/shield', 'DELETE', { rule });
|
||||
|
||||
// 处理不同的响应格式
|
||||
if (response.success || response.status === 'success') {
|
||||
// 在原规则列表中找到并删除
|
||||
const originalIndex = rules.indexOf(rule);
|
||||
if (originalIndex !== -1) {
|
||||
rules.splice(originalIndex, 1);
|
||||
}
|
||||
|
||||
// 在过滤后的列表中删除
|
||||
filteredRules.splice(index, 1);
|
||||
|
||||
// 如果当前页没有数据了,回到上一页
|
||||
const totalPages = Math.ceil(filteredRules.length / itemsPerPage);
|
||||
if (currentPage > totalPages && totalPages > 0) {
|
||||
currentPage = totalPages;
|
||||
}
|
||||
|
||||
// 等待动画完成后重新渲染列表
|
||||
setTimeout(() => {
|
||||
renderRulesList();
|
||||
|
||||
// 更新规则数量统计
|
||||
if (window.updateRulesCount && typeof window.updateRulesCount === 'function') {
|
||||
window.updateRulesCount(rules.length);
|
||||
}
|
||||
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification('规则删除成功', 'success');
|
||||
}
|
||||
}, 300);
|
||||
} else {
|
||||
// 恢复行样式
|
||||
if (rowElement) {
|
||||
rowElement.style.opacity = '1';
|
||||
rowElement.style.transform = 'translateX(0)';
|
||||
}
|
||||
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification('规则删除失败:' + (response.message || '未知错误'), 'danger');
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('删除规则失败:', error);
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification('删除规则失败', 'danger');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 重新加载规则
|
||||
async function reloadRules() {
|
||||
if (!confirm('确定要重新加载所有规则吗?这将覆盖当前内存中的规则。')) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const rulesPanel = document.getElementById('rules-panel');
|
||||
showLoading(rulesPanel);
|
||||
|
||||
// 使用正确的API路径和方法 - PUT请求到/api/shield
|
||||
await apiRequest('/api/shield', 'PUT');
|
||||
|
||||
// 重新加载规则列表
|
||||
await loadRules();
|
||||
|
||||
// 触发数据刷新事件,通知其他模块数据已更新
|
||||
if (typeof window.triggerDataRefresh === 'function') {
|
||||
window.triggerDataRefresh('rules');
|
||||
}
|
||||
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification('规则重新加载成功', 'success');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('重新加载规则失败:', error);
|
||||
if (typeof window.showNotification === 'function') {
|
||||
window.showNotification('重新加载规则失败', 'danger');
|
||||
}
|
||||
} finally {
|
||||
const rulesPanel = document.getElementById('rules-panel');
|
||||
hideLoading(rulesPanel);
|
||||
}
|
||||
}
|
||||
|
||||
// 过滤规则
|
||||
function filterRules() {
|
||||
const searchTerm = document.getElementById('rule-search').value.toLowerCase();
|
||||
|
||||
if (searchTerm) {
|
||||
filteredRules = rules.filter(rule => rule.toLowerCase().includes(searchTerm));
|
||||
} else {
|
||||
filteredRules = [...rules];
|
||||
}
|
||||
|
||||
currentPage = 1; // 重置为第一页
|
||||
renderRulesList();
|
||||
}
|
||||
|
||||
// HTML转义,防止XSS攻击
|
||||
function escapeHtml(text) {
|
||||
const map = {
|
||||
'&': '&',
|
||||
'<': '<',
|
||||
'>': '>',
|
||||
'"': '"',
|
||||
"'": '''
|
||||
};
|
||||
return text.replace(/[&<>'"]/g, m => map[m]);
|
||||
}
|
||||
|
||||
// 根据规则类型返回对应的CSS类名
|
||||
function getRuleTypeClass(rule) {
|
||||
// 简单的规则类型判断
|
||||
if (rule.startsWith('||') || rule.startsWith('|http')) {
|
||||
return 'rule-type-url';
|
||||
} else if (rule.startsWith('@@')) {
|
||||
return 'rule-type-exception';
|
||||
} else if (rule.startsWith('#')) {
|
||||
return 'rule-type-comment';
|
||||
} else if (rule.includes('$')) {
|
||||
return 'rule-type-filter';
|
||||
}
|
||||
return 'rule-type-standard';
|
||||
}
|
||||
|
||||
// 预处理规则,支持多种规则格式
|
||||
function preprocessRule(rule) {
|
||||
// 移除首尾空白字符
|
||||
let processed = rule.trim();
|
||||
|
||||
// 处理AdGuardHome格式的规则
|
||||
if (processed.startsWith('0.0.0.0 ') || processed.startsWith('127.0.0.1 ')) {
|
||||
const parts = processed.split(' ');
|
||||
if (parts.length >= 2) {
|
||||
// 转换为AdBlock Plus格式
|
||||
processed = '||' + parts[1] + '^';
|
||||
}
|
||||
}
|
||||
|
||||
return processed;
|
||||
}
|
||||
|
||||
// 导出函数,供其他模块调用
|
||||
window.updateRulesCount = function(count) {
|
||||
const rulesCountElement = document.getElementById('rules-count');
|
||||
if (rulesCountElement) {
|
||||
rulesCountElement.textContent = count;
|
||||
}
|
||||
}
|
||||
|
||||
// 导出初始化函数
|
||||
window.initRulesPanel = initRulesPanel;
|
||||
|
||||
// 注册到面板导航系统
|
||||
if (window.registerPanelModule) {
|
||||
window.registerPanelModule('rules-panel', {
|
||||
init: initRulesPanel,
|
||||
refresh: loadRules
|
||||
});
|
||||
}
|
||||
301
staticbak/static/js/query.js
Normal file
301
staticbak/static/js/query.js
Normal file
@@ -0,0 +1,301 @@
|
||||
// DNS查询页面功能实现
|
||||
|
||||
// 初始化查询页面
|
||||
function initQueryPage() {
|
||||
console.log('初始化DNS查询页面...');
|
||||
setupQueryEventListeners();
|
||||
loadQueryHistory();
|
||||
}
|
||||
|
||||
// 执行DNS查询
|
||||
async function handleDNSQuery() {
|
||||
const domainInput = document.getElementById('dns-query-domain');
|
||||
const resultDiv = document.getElementById('query-result');
|
||||
|
||||
if (!domainInput || !resultDiv) {
|
||||
console.error('找不到必要的DOM元素');
|
||||
return;
|
||||
}
|
||||
|
||||
const domain = domainInput.value.trim();
|
||||
if (!domain) {
|
||||
showErrorMessage('请输入域名');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const response = await fetch(`/api/query?domain=${encodeURIComponent(domain)}`);
|
||||
if (!response.ok) {
|
||||
throw new Error('查询失败');
|
||||
}
|
||||
|
||||
const result = await response.json();
|
||||
displayQueryResult(result, domain);
|
||||
saveQueryHistory(domain, result);
|
||||
loadQueryHistory();
|
||||
} catch (error) {
|
||||
console.error('DNS查询出错:', error);
|
||||
showErrorMessage('查询失败,请稍后重试');
|
||||
}
|
||||
}
|
||||
|
||||
// 显示查询结果
|
||||
function displayQueryResult(result, domain) {
|
||||
const resultDiv = document.getElementById('query-result');
|
||||
if (!resultDiv) return;
|
||||
|
||||
// 显示结果容器
|
||||
resultDiv.classList.remove('hidden');
|
||||
|
||||
// 解析结果
|
||||
const status = result.blocked ? '被屏蔽' : '正常';
|
||||
const statusClass = result.blocked ? 'text-danger' : 'text-success';
|
||||
const blockType = result.blocked ? result.blockRuleType || '未知' : '正常';
|
||||
const blockRule = result.blocked ? result.blockRule || '未知' : '无';
|
||||
const blockSource = result.blocked ? result.blocksource || '未知' : '无';
|
||||
const timestamp = new Date(result.timestamp).toLocaleString();
|
||||
|
||||
// 更新结果显示
|
||||
document.getElementById('result-domain').textContent = domain;
|
||||
document.getElementById('result-status').innerHTML = `<span class="${statusClass}">${status}</span>`;
|
||||
document.getElementById('result-type').textContent = blockType;
|
||||
|
||||
// 检查是否存在屏蔽规则显示元素,如果不存在则创建
|
||||
let blockRuleElement = document.getElementById('result-block-rule');
|
||||
if (!blockRuleElement) {
|
||||
// 创建屏蔽规则显示区域
|
||||
const grid = resultDiv.querySelector('.grid');
|
||||
if (grid) {
|
||||
const newGridItem = document.createElement('div');
|
||||
newGridItem.className = 'bg-gray-50 p-4 rounded-lg';
|
||||
newGridItem.innerHTML = `
|
||||
<h4 class="text-sm font-medium text-gray-500 mb-2">屏蔽规则</h4>
|
||||
<p class="text-lg font-semibold" id="result-block-rule">-</p>
|
||||
`;
|
||||
grid.appendChild(newGridItem);
|
||||
blockRuleElement = document.getElementById('result-block-rule');
|
||||
}
|
||||
}
|
||||
|
||||
// 更新屏蔽规则显示
|
||||
if (blockRuleElement) {
|
||||
blockRuleElement.textContent = blockRule;
|
||||
}
|
||||
|
||||
// 检查是否存在屏蔽来源显示元素,如果不存在则创建
|
||||
let blockSourceElement = document.getElementById('result-block-source');
|
||||
if (!blockSourceElement) {
|
||||
// 创建屏蔽来源显示区域
|
||||
const grid = resultDiv.querySelector('.grid');
|
||||
if (grid) {
|
||||
const newGridItem = document.createElement('div');
|
||||
newGridItem.className = 'bg-gray-50 p-4 rounded-lg';
|
||||
newGridItem.innerHTML = `
|
||||
<h4 class="text-sm font-medium text-gray-500 mb-2">屏蔽来源</h4>
|
||||
<p class="text-lg font-semibold" id="result-block-source">-</p>
|
||||
`;
|
||||
grid.appendChild(newGridItem);
|
||||
blockSourceElement = document.getElementById('result-block-source');
|
||||
}
|
||||
}
|
||||
|
||||
// 更新屏蔽来源显示
|
||||
if (blockSourceElement) {
|
||||
blockSourceElement.textContent = blockSource;
|
||||
}
|
||||
|
||||
document.getElementById('result-time').textContent = timestamp;
|
||||
document.getElementById('result-details').textContent = JSON.stringify(result, null, 2);
|
||||
}
|
||||
|
||||
// 保存查询历史
|
||||
function saveQueryHistory(domain, result) {
|
||||
// 获取现有历史记录
|
||||
let history = JSON.parse(localStorage.getItem('dnsQueryHistory') || '[]');
|
||||
|
||||
// 创建历史记录项
|
||||
const historyItem = {
|
||||
domain: domain,
|
||||
timestamp: new Date().toISOString(),
|
||||
result: {
|
||||
blocked: result.blocked,
|
||||
blockRuleType: result.blockRuleType,
|
||||
blockRule: result.blockRule,
|
||||
blocksource: result.blocksource
|
||||
}
|
||||
};
|
||||
|
||||
// 添加到历史记录开头
|
||||
history.unshift(historyItem);
|
||||
|
||||
// 限制历史记录数量
|
||||
if (history.length > 20) {
|
||||
history = history.slice(0, 20);
|
||||
}
|
||||
|
||||
// 保存到本地存储
|
||||
localStorage.setItem('dnsQueryHistory', JSON.stringify(history));
|
||||
}
|
||||
|
||||
// 加载查询历史
|
||||
function loadQueryHistory() {
|
||||
const historyDiv = document.getElementById('query-history');
|
||||
if (!historyDiv) return;
|
||||
|
||||
// 获取历史记录
|
||||
const history = JSON.parse(localStorage.getItem('dnsQueryHistory') || '[]');
|
||||
|
||||
if (history.length === 0) {
|
||||
historyDiv.innerHTML = '<div class="text-center text-gray-500 py-4">暂无查询历史</div>';
|
||||
return;
|
||||
}
|
||||
|
||||
// 生成历史记录HTML
|
||||
const historyHTML = history.map(item => {
|
||||
const statusClass = item.result.blocked ? 'text-danger' : 'text-success';
|
||||
const statusText = item.result.blocked ? '被屏蔽' : '正常';
|
||||
const blockType = item.result.blocked ? item.result.blockRuleType : '正常';
|
||||
const blockRule = item.result.blocked ? item.result.blockRule : '无';
|
||||
const blockSource = item.result.blocked ? item.result.blocksource : '无';
|
||||
const formattedTime = new Date(item.timestamp).toLocaleString();
|
||||
|
||||
return `
|
||||
<div class="flex flex-col md:flex-row justify-between items-start md:items-center p-3 bg-gray-50 rounded-lg hover:bg-gray-100 transition-colors">
|
||||
<div class="flex-1">
|
||||
<div class="flex items-center space-x-2">
|
||||
<span class="font-medium">${item.domain}</span>
|
||||
<span class="${statusClass} text-sm">${statusText}</span>
|
||||
<span class="text-xs text-gray-500">${blockType}</span>
|
||||
</div>
|
||||
<div class="text-xs text-gray-500 mt-1">规则: ${blockRule}</div>
|
||||
<div class="text-xs text-gray-500 mt-1">来源: ${blockSource}</div>
|
||||
<div class="text-xs text-gray-500 mt-1">${formattedTime}</div>
|
||||
</div>
|
||||
<button class="mt-2 md:mt-0 px-3 py-1 bg-primary text-white text-sm rounded-md hover:bg-primary/90 transition-colors" onclick="requeryFromHistory('${item.domain}')">
|
||||
<i class="fa fa-refresh mr-1"></i>重新查询
|
||||
</button>
|
||||
</div>
|
||||
`;
|
||||
}).join('');
|
||||
|
||||
historyDiv.innerHTML = historyHTML;
|
||||
}
|
||||
|
||||
// 从历史记录重新查询
|
||||
function requeryFromHistory(domain) {
|
||||
const domainInput = document.getElementById('dns-query-domain');
|
||||
if (domainInput) {
|
||||
domainInput.value = domain;
|
||||
handleDNSQuery();
|
||||
}
|
||||
}
|
||||
|
||||
// 清空查询历史
|
||||
function clearQueryHistory() {
|
||||
if (confirm('确定要清空所有查询历史吗?')) {
|
||||
localStorage.removeItem('dnsQueryHistory');
|
||||
loadQueryHistory();
|
||||
showSuccessMessage('查询历史已清空');
|
||||
}
|
||||
}
|
||||
|
||||
// 设置事件监听器
|
||||
function setupQueryEventListeners() {
|
||||
// 查询按钮事件
|
||||
const queryBtn = document.getElementById('dns-query-btn');
|
||||
if (queryBtn) {
|
||||
queryBtn.addEventListener('click', handleDNSQuery);
|
||||
}
|
||||
|
||||
// 输入框回车键事件
|
||||
const domainInput = document.getElementById('dns-query-domain');
|
||||
if (domainInput) {
|
||||
domainInput.addEventListener('keypress', (e) => {
|
||||
if (e.key === 'Enter') {
|
||||
e.preventDefault();
|
||||
handleDNSQuery();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 清空历史按钮事件
|
||||
const clearHistoryBtn = document.getElementById('clear-history-btn');
|
||||
if (clearHistoryBtn) {
|
||||
clearHistoryBtn.addEventListener('click', clearQueryHistory);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
// 显示成功消息
|
||||
function showSuccessMessage(message) {
|
||||
showNotification(message, 'success');
|
||||
}
|
||||
|
||||
// 显示错误消息
|
||||
function showErrorMessage(message) {
|
||||
showNotification(message, 'error');
|
||||
}
|
||||
|
||||
// 显示通知
|
||||
function showNotification(message, type = 'info') {
|
||||
// 移除现有通知
|
||||
const existingNotification = document.querySelector('.notification');
|
||||
if (existingNotification) {
|
||||
existingNotification.remove();
|
||||
}
|
||||
|
||||
// 创建新通知
|
||||
const notification = document.createElement('div');
|
||||
notification.className = `notification fixed bottom-4 right-4 px-6 py-3 rounded-lg shadow-lg z-50 transform transition-all duration-300 ease-in-out translate-y-0 opacity-0`;
|
||||
|
||||
// 设置通知样式
|
||||
if (type === 'success') {
|
||||
notification.classList.add('bg-green-500', 'text-white');
|
||||
} else if (type === 'error') {
|
||||
notification.classList.add('bg-red-500', 'text-white');
|
||||
} else {
|
||||
notification.classList.add('bg-blue-500', 'text-white');
|
||||
}
|
||||
|
||||
notification.innerHTML = `
|
||||
<div class="flex items-center space-x-2">
|
||||
<i class="fa ${type === 'success' ? 'fa-check-circle' : type === 'error' ? 'fa-exclamation-circle' : 'fa-info-circle'}"></i>
|
||||
<span>${message}</span>
|
||||
</div>
|
||||
`;
|
||||
|
||||
document.body.appendChild(notification);
|
||||
|
||||
// 显示通知
|
||||
setTimeout(() => {
|
||||
notification.classList.remove('opacity-0');
|
||||
notification.classList.add('opacity-100');
|
||||
}, 10);
|
||||
|
||||
// 3秒后隐藏通知
|
||||
setTimeout(() => {
|
||||
notification.classList.remove('opacity-100');
|
||||
notification.classList.add('opacity-0');
|
||||
setTimeout(() => {
|
||||
notification.remove();
|
||||
}, 300);
|
||||
}, 3000);
|
||||
}
|
||||
|
||||
// 页面加载完成后初始化
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initQueryPage);
|
||||
} else {
|
||||
initQueryPage();
|
||||
}
|
||||
|
||||
// 当切换到DNS查询页面时重新加载数据
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
// 监听hash变化,当切换到DNS查询页面时重新加载数据
|
||||
window.addEventListener('hashchange', () => {
|
||||
if (window.location.hash === '#query') {
|
||||
initQueryPage();
|
||||
}
|
||||
});
|
||||
});
|
||||
305
staticbak/static/js/server-status.js
Normal file
305
staticbak/static/js/server-status.js
Normal file
@@ -0,0 +1,305 @@
|
||||
// 服务器状态组件 - 显示CPU使用率和查询统计
|
||||
|
||||
// 全局变量
|
||||
let serverStatusUpdateTimer = null;
|
||||
let previousServerData = {
|
||||
cpu: 0,
|
||||
queries: 0
|
||||
};
|
||||
|
||||
// 初始化服务器状态组件
|
||||
function initServerStatusWidget() {
|
||||
// 确保DOM元素存在
|
||||
const widget = document.getElementById('server-status-widget');
|
||||
if (!widget) return;
|
||||
|
||||
// 初始化页面类型检测
|
||||
updateWidgetDisplayByPageType();
|
||||
|
||||
// 设置页面切换事件监听
|
||||
handlePageSwitchEvents();
|
||||
|
||||
// 设置WebSocket监听(如果可用)
|
||||
setupWebSocketListeners();
|
||||
|
||||
// 立即加载一次数据
|
||||
loadServerStatusData();
|
||||
|
||||
// 设置定时更新(每5秒更新一次)
|
||||
serverStatusUpdateTimer = setInterval(loadServerStatusData, 5000);
|
||||
}
|
||||
|
||||
// 判断当前页面是否为仪表盘
|
||||
function isCurrentPageDashboard() {
|
||||
// 方法1:检查侧边栏激活状态
|
||||
const dashboardLink = document.querySelector('.sidebar a[href="#dashboard"]');
|
||||
if (dashboardLink && dashboardLink.classList.contains('active')) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// 方法2:检查仪表盘特有元素
|
||||
const dashboardElements = [
|
||||
'#dashboard-container',
|
||||
'.dashboard-summary',
|
||||
'#dashboard-stats'
|
||||
];
|
||||
|
||||
for (const selector of dashboardElements) {
|
||||
if (document.querySelector(selector)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// 方法3:检查URL哈希值
|
||||
if (window.location.hash === '#dashboard' || window.location.hash === '') {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// 根据页面类型更新组件显示
|
||||
function updateWidgetDisplayByPageType() {
|
||||
const additionalStats = document.getElementById('server-additional-stats');
|
||||
if (!additionalStats) return;
|
||||
|
||||
// 如果当前页面是仪表盘,隐藏额外统计指标
|
||||
if (isCurrentPageDashboard()) {
|
||||
additionalStats.classList.add('hidden');
|
||||
} else {
|
||||
// 非仪表盘页面,显示额外统计指标
|
||||
additionalStats.classList.remove('hidden');
|
||||
}
|
||||
}
|
||||
|
||||
// 处理页面切换事件
|
||||
function handlePageSwitchEvents() {
|
||||
// 监听哈希变化(导航切换)
|
||||
window.addEventListener('hashchange', updateWidgetDisplayByPageType);
|
||||
|
||||
// 监听侧边栏点击事件
|
||||
const sidebarLinks = document.querySelectorAll('.sidebar a');
|
||||
sidebarLinks.forEach(link => {
|
||||
link.addEventListener('click', function() {
|
||||
// 延迟检查,确保页面已切换
|
||||
setTimeout(updateWidgetDisplayByPageType, 100);
|
||||
});
|
||||
});
|
||||
|
||||
// 监听导航菜单点击事件
|
||||
const navLinks = document.querySelectorAll('nav a');
|
||||
navLinks.forEach(link => {
|
||||
link.addEventListener('click', function() {
|
||||
setTimeout(updateWidgetDisplayByPageType, 100);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 监控WebSocket连接状态
|
||||
function monitorWebSocketConnection() {
|
||||
// 如果存在WebSocket连接,监听消息
|
||||
if (window.socket) {
|
||||
window.socket.addEventListener('message', function(event) {
|
||||
try {
|
||||
const data = JSON.parse(event.data);
|
||||
if (data.type === 'status_update') {
|
||||
updateServerStatusWidget(data.payload);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('解析WebSocket消息失败:', error);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 设置WebSocket监听器
|
||||
function setupWebSocketListeners() {
|
||||
// 如果WebSocket已经存在
|
||||
if (window.socket) {
|
||||
monitorWebSocketConnection();
|
||||
} else {
|
||||
// 监听socket初始化事件
|
||||
window.addEventListener('socketInitialized', function() {
|
||||
monitorWebSocketConnection();
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// 加载服务器状态数据
|
||||
async function loadServerStatusData() {
|
||||
try {
|
||||
// 使用现有的API获取系统状态
|
||||
const api = window.api || {};
|
||||
const getStatusFn = api.getStatus || function() { return Promise.resolve({}); };
|
||||
const statusData = await getStatusFn();
|
||||
if (statusData && !statusData.error) {
|
||||
updateServerStatusWidget(statusData);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('加载服务器状态数据失败:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// 更新服务器状态组件
|
||||
function updateServerStatusWidget(stats) {
|
||||
// 确保组件存在
|
||||
const widget = document.getElementById('server-status-widget');
|
||||
if (!widget) return;
|
||||
|
||||
// 确保stats存在
|
||||
stats = stats || {};
|
||||
|
||||
// 提取CPU使用率
|
||||
let cpuUsage = 0;
|
||||
if (stats.system && typeof stats.system.cpu === 'number') {
|
||||
cpuUsage = stats.system.cpu;
|
||||
} else if (typeof stats.cpuUsage === 'number') {
|
||||
cpuUsage = stats.cpuUsage;
|
||||
}
|
||||
|
||||
// 提取查询统计数据
|
||||
let totalQueries = 0;
|
||||
let blockedQueries = 0;
|
||||
let allowedQueries = 0;
|
||||
|
||||
if (stats.dns) {
|
||||
const allowed = typeof stats.dns.Allowed === 'number' ? stats.dns.Allowed : 0;
|
||||
const blocked = typeof stats.dns.Blocked === 'number' ? stats.dns.Blocked : 0;
|
||||
const errors = typeof stats.dns.Errors === 'number' ? stats.dns.Errors : 0;
|
||||
totalQueries = allowed + blocked + errors;
|
||||
blockedQueries = blocked;
|
||||
allowedQueries = allowed;
|
||||
} else {
|
||||
totalQueries = typeof stats.totalQueries === 'number' ? stats.totalQueries : 0;
|
||||
blockedQueries = typeof stats.blockedQueries === 'number' ? stats.blockedQueries : 0;
|
||||
allowedQueries = typeof stats.allowedQueries === 'number' ? stats.allowedQueries : 0;
|
||||
}
|
||||
|
||||
// 更新CPU使用率
|
||||
const cpuValueElement = document.getElementById('server-cpu-value');
|
||||
if (cpuValueElement) {
|
||||
cpuValueElement.textContent = cpuUsage.toFixed(1) + '%';
|
||||
}
|
||||
|
||||
const cpuBarElement = document.getElementById('server-cpu-bar');
|
||||
if (cpuBarElement) {
|
||||
cpuBarElement.style.width = Math.min(cpuUsage, 100) + '%';
|
||||
|
||||
// 根据CPU使用率改变颜色
|
||||
if (cpuUsage > 80) {
|
||||
cpuBarElement.className = 'h-full bg-danger rounded-full';
|
||||
} else if (cpuUsage > 50) {
|
||||
cpuBarElement.className = 'h-full bg-warning rounded-full';
|
||||
} else {
|
||||
cpuBarElement.className = 'h-full bg-success rounded-full';
|
||||
}
|
||||
}
|
||||
|
||||
// 更新查询量
|
||||
const queriesValueElement = document.getElementById('server-queries-value');
|
||||
if (queriesValueElement) {
|
||||
queriesValueElement.textContent = formatNumber(totalQueries);
|
||||
}
|
||||
|
||||
// 计算查询量百分比(假设最大查询量为10000)
|
||||
const queryPercentage = Math.min((totalQueries / 10000) * 100, 100);
|
||||
const queriesBarElement = document.getElementById('server-queries-bar');
|
||||
if (queriesBarElement) {
|
||||
queriesBarElement.style.width = queryPercentage + '%';
|
||||
}
|
||||
|
||||
// 更新额外统计指标
|
||||
const totalQueriesElement = document.getElementById('server-total-queries');
|
||||
if (totalQueriesElement) {
|
||||
totalQueriesElement.textContent = formatNumber(totalQueries);
|
||||
}
|
||||
|
||||
const blockedQueriesElement = document.getElementById('server-blocked-queries');
|
||||
if (blockedQueriesElement) {
|
||||
blockedQueriesElement.textContent = formatNumber(blockedQueries);
|
||||
}
|
||||
|
||||
const allowedQueriesElement = document.getElementById('server-allowed-queries');
|
||||
if (allowedQueriesElement) {
|
||||
allowedQueriesElement.textContent = formatNumber(allowedQueries);
|
||||
}
|
||||
|
||||
// 添加光晕提示效果
|
||||
if (previousServerData.cpu !== cpuUsage || previousServerData.queries !== totalQueries) {
|
||||
addGlowEffect();
|
||||
}
|
||||
|
||||
// 更新服务器状态指示器
|
||||
const statusIndicator = document.getElementById('server-status-indicator');
|
||||
if (statusIndicator) {
|
||||
// 检查系统状态
|
||||
if (stats.system && stats.system.status === 'error') {
|
||||
statusIndicator.className = 'inline-block w-2 h-2 bg-danger rounded-full';
|
||||
} else {
|
||||
statusIndicator.className = 'inline-block w-2 h-2 bg-success rounded-full';
|
||||
}
|
||||
}
|
||||
|
||||
// 保存当前数据用于下次比较
|
||||
previousServerData = {
|
||||
cpu: cpuUsage,
|
||||
queries: totalQueries
|
||||
};
|
||||
}
|
||||
|
||||
// 添加光晕提示效果
|
||||
function addGlowEffect() {
|
||||
const widget = document.getElementById('server-status-widget');
|
||||
if (!widget) return;
|
||||
|
||||
// 添加光晕类
|
||||
widget.classList.add('glow-effect');
|
||||
|
||||
// 2秒后移除光晕
|
||||
setTimeout(function() {
|
||||
widget.classList.remove('glow-effect');
|
||||
}, 2000);
|
||||
}
|
||||
|
||||
// 格式化数字
|
||||
function formatNumber(num) {
|
||||
// 显示完整数字的最大长度阈值
|
||||
const MAX_FULL_LENGTH = 5;
|
||||
|
||||
// 先获取完整数字字符串
|
||||
const fullNumStr = num.toString();
|
||||
|
||||
// 如果数字长度小于等于阈值,直接返回完整数字
|
||||
if (fullNumStr.length <= MAX_FULL_LENGTH) {
|
||||
return fullNumStr;
|
||||
}
|
||||
|
||||
// 否则使用缩写格式
|
||||
if (num >= 1000000) {
|
||||
return (num / 1000000).toFixed(1) + 'M';
|
||||
} else if (num >= 1000) {
|
||||
return (num / 1000).toFixed(1) + 'K';
|
||||
}
|
||||
|
||||
return fullNumStr;
|
||||
}
|
||||
|
||||
// 在DOM加载完成后初始化
|
||||
window.addEventListener('DOMContentLoaded', function() {
|
||||
// 延迟初始化,确保页面完全加载
|
||||
setTimeout(initServerStatusWidget, 500);
|
||||
});
|
||||
|
||||
// 在页面卸载时清理资源
|
||||
window.addEventListener('beforeunload', function() {
|
||||
if (serverStatusUpdateTimer) {
|
||||
clearInterval(serverStatusUpdateTimer);
|
||||
serverStatusUpdateTimer = null;
|
||||
}
|
||||
});
|
||||
|
||||
// 导出函数供其他模块使用
|
||||
window.serverStatusWidget = {
|
||||
init: initServerStatusWidget,
|
||||
update: updateServerStatusWidget
|
||||
};
|
||||
1302
staticbak/static/js/shield.js
Normal file
1302
staticbak/static/js/shield.js
Normal file
File diff suppressed because it is too large
Load Diff
1
staticbak/static/js/vendor/chart.umd.min.js
vendored
Normal file
1
staticbak/static/js/vendor/chart.umd.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
19
staticbak/static/js/vendor/tailwind.js
vendored
Normal file
19
staticbak/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'],
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
194
staticbak/static/login.html
Normal file
194
staticbak/static/login.html
Normal file
@@ -0,0 +1,194 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>DNS服务器控制台 - 登录</title>
|
||||
<style>
|
||||
* {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
|
||||
background-color: #f5f7fa;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.login-container {
|
||||
background-color: white;
|
||||
border-radius: 8px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
padding: 40px;
|
||||
width: 100%;
|
||||
max-width: 400px;
|
||||
}
|
||||
|
||||
.login-header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.login-header h1 {
|
||||
font-size: 24px;
|
||||
color: #2c3e50;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
|
||||
.login-header p {
|
||||
color: #7f8c8d;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.form-group {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.form-group label {
|
||||
display: block;
|
||||
margin-bottom: 8px;
|
||||
font-weight: 500;
|
||||
color: #555;
|
||||
}
|
||||
|
||||
.form-group input {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
border: 1px solid #e1e5e9;
|
||||
border-radius: 4px;
|
||||
font-size: 16px;
|
||||
transition: border-color 0.3s ease;
|
||||
}
|
||||
|
||||
.form-group input:focus {
|
||||
outline: none;
|
||||
border-color: #3498db;
|
||||
box-shadow: 0 0 0 2px rgba(52, 152, 219, 0.1);
|
||||
}
|
||||
|
||||
.btn {
|
||||
width: 100%;
|
||||
padding: 12px;
|
||||
background-color: #3498db;
|
||||
color: white;
|
||||
border: none;
|
||||
border-radius: 4px;
|
||||
font-size: 16px;
|
||||
font-weight: 500;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s ease;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
background-color: #2980b9;
|
||||
}
|
||||
|
||||
.btn:active {
|
||||
transform: translateY(1px);
|
||||
}
|
||||
|
||||
.error-message {
|
||||
background-color: #fee;
|
||||
color: #c00;
|
||||
padding: 12px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 20px;
|
||||
text-align: center;
|
||||
font-size: 14px;
|
||||
display: none;
|
||||
}
|
||||
|
||||
.loading {
|
||||
opacity: 0.7;
|
||||
cursor: not-allowed;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="login-container">
|
||||
<div class="login-header">
|
||||
<h1>DNS服务器控制台</h1>
|
||||
<p>请输入您的登录凭据</p>
|
||||
</div>
|
||||
|
||||
<div class="error-message" id="errorMessage"></div>
|
||||
|
||||
<form id="loginForm">
|
||||
<div class="form-group">
|
||||
<label for="username">用户名</label>
|
||||
<input type="text" id="username" name="username" placeholder="请输入用户名" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="password">密码</label>
|
||||
<input type="password" id="password" name="password" placeholder="请输入密码" required>
|
||||
</div>
|
||||
|
||||
<button type="submit" class="btn" id="loginBtn">登录</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
document.getElementById('loginForm').addEventListener('submit', function(e) {
|
||||
e.preventDefault();
|
||||
|
||||
const username = document.getElementById('username').value;
|
||||
const password = document.getElementById('password').value;
|
||||
const loginBtn = document.getElementById('loginBtn');
|
||||
const errorMessage = document.getElementById('errorMessage');
|
||||
|
||||
// 显示加载状态
|
||||
loginBtn.textContent = '登录中...';
|
||||
loginBtn.classList.add('loading');
|
||||
errorMessage.style.display = 'none';
|
||||
|
||||
// 发送登录请求
|
||||
fetch('/api/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
username: username,
|
||||
password: password
|
||||
})
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
if (response.status === 401) {
|
||||
throw new Error('未知用户名或密码');
|
||||
} else {
|
||||
throw new Error('登录失败');
|
||||
}
|
||||
}
|
||||
return response.json();
|
||||
})
|
||||
.then(data => {
|
||||
if (data.status === 'success') {
|
||||
// 登录成功,重定向到主页
|
||||
window.location.href = '/';
|
||||
} else {
|
||||
if (data.error === '用户名或密码错误') {
|
||||
throw new Error('未知用户名或密码');
|
||||
} else {
|
||||
throw new Error(data.error || '登录失败');
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
// 显示错误信息
|
||||
errorMessage.textContent = error.message;
|
||||
errorMessage.style.display = 'block';
|
||||
loginBtn.textContent = '登录';
|
||||
loginBtn.classList.remove('loading');
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
Reference in New Issue
Block a user