├── README.md ├── app.py ├── Beta ├── MosdnsUI_Beta.sh ├── app.py └── index.html ├── MosDNSUI.sh └── index.html /README.md: -------------------------------------------------------------------------------- 1 | # MosDNS 独立监控面板 2 | 3 |  4 | _亮色主题预览_ 5 | 6 |  7 | _暗色主题预览_ 8 | 9 | ## 简介 10 | 11 | MosDNS 独立监控面板是一个简洁、美观且功能强大的 MosDNS 服务实时监控解决方案。它以独立的 Web 应用形式运行(基于 Python Flask),通过抓取 MosDNS 自身的 `/metrics` 接口数据,为您提供直观、易于理解的运行状态报告。 12 | 13 | 本面板与 MosDNS 自带的 UI 并行运行,互不影响。它旨在提供一个专注于监控和管理核心缓存指标的界面,并支持多种主题切换,以适应不同用户偏好,提供流畅无闪烁的交互体验。 14 | 15 | ## 主要功能 16 | 17 | * **实时性能监控**:展示 MosDNS 的请求总数、缓存命中、过期缓存命中、缓存条目数等关键指标。 18 | * **系统资源概览**:监控 MosDNS 进程的 CPU 时间、常驻内存 (RSS)、堆内存、Go 版本、线程数和文件描述符数量等系统信息。 19 | * **多主题支持**:内置 **默认亮色**、**默认暗色**、**赛博朋克**、**静谧森林**、**日落余晖** 和 **高级灰** 多种主题,一键切换,满足您的视觉偏好。图表颜色也会随主题联动。 20 | * **直观的饼图展示**:通过甜甜圈饼图清晰展示缓存的命中率和过期命中率。 21 | * **无闪烁实时更新**:自动刷新时,仅更新数据和图表内容,避免整个页面重绘,提供流畅的视觉体验。 22 | * **智能刷新控制**: 23 | * 提供开关,可选择是否启用 5 秒自动刷新。 24 | * 可手动点击“立即刷新”按钮获取最新数据。 25 | * 显示数据最后一次更新的时间。 26 | * **一键清空 FakeIP 缓存**:方便快捷地执行 MosDNS 的 FakeIP 缓存清空操作。 27 | * **独立的 Web 服务**:作为独立的 Flask 应用运行,不依赖或修改 MosDNS 自身配置,互不干扰。 28 | * **一键部署与回滚脚本**:提供 Bash 脚本,简化部署和卸载流程。 29 | * **集成诊断与修复**:脚本内置诊断功能,可自动检查并尝试修复常见部署问题。 30 | 31 | ## 部署要求 32 | 33 | * **操作系统**:基于 Debian / Ubuntu 的 Linux 发行版(脚本基于 `apt` 包管理器)。 34 | * **MosDNS**:已正确安装并运行,且其 HTTP Metrics 接口(默认为 `http://localhost:9099/metrics`)可访问。 35 | * **Python3**:系统需安装 Python3 及 `python3-venv`。 36 | * **权限**:需要 `sudo` 权限来安装系统依赖、创建服务和配置防火墙。 37 | 38 | ## 快速开始 (一键部署) 39 | 40 | 本项目的核心是一个 Bash 脚本,它会帮助您一键部署所有必要的组件。 41 | 42 | 1. **获取部署脚本**: 43 | 将 `MosDNSUI.sh` 文件下载到您的服务器。最简单的方式是直接从本 GitHub 仓库下载: 44 | ```bash 45 | wget https://raw.githubusercontent.com/Jimmyzxk/MosDNSUI/main/MosDNSUI.sh -O MosDNSUI.sh 46 | ``` 47 | _(请确保上述 `main` 分支和 `MosDNSUI.sh` 路径在您上传后是正确的)_ 48 | 49 | 2. **赋予执行权限**: 50 | ```bash 51 | chmod +x MosDNSUI.sh 52 | ``` 53 | 54 | 3. **运行部署脚本**: 55 | 以 `sudo` 权限运行脚本,它将引导您完成部署过程。 56 | 57 | ```bash 58 | sudo ./MosDNSUI.sh 59 | ``` 60 | 61 | 脚本将提示您选择操作。选择 `部署 MosDNS 监控面板`。 62 | 63 | **脚本部署步骤概览:** 64 | * 检查 MosDNS Metrics 接口可访问性。 65 | * 安装 Python3、pip 和 `python3-venv` 等必要依赖。 66 | * 创建项目目录 `/opt/mosdns_monitor_panel`。 67 | * 创建 Python 虚拟环境并安装 Flask、Gunicorn 和 Requests。 68 | * 创建 Flask 后端应用 `app.py`。 69 | * 创建网站图标 `favicon.png`。 70 | * 创建 HTML 前端页面 `index.html`(**此文件将包含您最新优化的 UI 代码**)。 71 | * 创建 Systemd 服务并启动监控面板。 72 | * 自动配置 UFW 防火墙以允许访问默认端口 `5001`。 73 | 74 | 4. **访问监控面板**: 75 | 部署完成后,您将看到提示信息,通常可以通过以下地址访问您的监控面板: 76 | 77 | ``` 78 | http://<您的服务器IP地址>:5001 79 | ``` 80 | 例如:`http://192.168.1.100:5001` 81 | 82 | 如果您的服务器有域名解析,也可以使用域名访问。 83 | 84 | ## 使用说明 85 | 86 | 监控面板分为以下几个主要区域: 87 | 88 | * **缓存状态卡片**: 89 | * 显示各类缓存(全部、国内、国外、节点)的详细统计数据,包括请求总数、命中数、过期命中数、命中率、过期命中率和条目数。 90 | * 每个卡片顶部有一个状态点,绿色表示有活跃请求,黄色表示无请求。 91 | * 甜甜圈饼图直观展示命中率和过期命中率。 92 | 93 | * **系统信息** (``): 94 | * 展示 MosDNS 进程的启动时间、CPU 占用时间、常驻内存、空闲堆内存、Go 版本、线程数和打开文件描述符数量。 95 | 96 | * **显示卡片** (``): 97 | * 您可以勾选/取消勾选来控制页面上显示哪些缓存卡片。选择后页面会自动刷新以应用更改。 98 | 99 | * **主题切换** (``): 100 | * 提供多种内置主题按钮。点击即可实时切换面板的主题样式。图表颜色、文字颜色、背景颜色等将同步更新,提供一致的视觉体验。 101 | 102 | * **操作与刷新** (``): 103 | * **启用自动刷新 (5s)**:勾选此项,面板将每 5 秒自动获取最新数据并更新页面,且更新过程无闪烁。取消勾选则停止自动刷新。 104 | * **立即刷新**:手动点击此按钮可立即获取最新数据并更新页面,保持无闪烁体验。 105 | * **清空 FakeIP 缓存**:点击此按钮将向 MosDNS 发送指令,清空 FakeIP 缓存。此操作会触发页面重新加载以反映最新状态。 106 | * **最后更新**:显示面板数据最后一次更新的时间。 107 | 108 | ## 配置 109 | 110 | 您可以编辑部署脚本 `MosDNSUI.sh` 文件开头的变量来定制面板: 111 | 112 | * `FLASK_PORT=5001`:Web 监控面板运行的端口。如果您希望在其他端口运行,请在部署前修改此值。 113 | * `MOSDNS_METRICS_URL="http://localhost:9099/metrics"`:MosDNS Metrics 接口的完整 URL。如果您的 MosDNS 监听在不同的 IP 或端口,请相应修改。 114 | * `WEB_USER="www-data"`:运行 Flask 应用的系统用户。默认是 `www-data`,这是一个常见的 Web 服务器用户。 115 | 116 | **请注意:** 如果您在部署后修改了这些配置,需要先执行回滚/清理操作,然后重新部署。 117 | 118 | ## 故障诊断与修复 119 | 120 | 如果监控面板无法正常工作,您可以运行部署脚本中的“一键诊断并尝试修复”选项: 121 | 122 | ```bash 123 | sudo ./MosDNSUI.sh 124 | -------------------------------------------------------------------------------- /app.py: -------------------------------------------------------------------------------- 1 | # app.py - Optimized for the provided index.html 2 | import os 3 | import sys 4 | import requests 5 | from flask import Flask, render_template, jsonify, Response, request 6 | import re 7 | import datetime 8 | 9 | app = Flask(__name__) 10 | 11 | # --- Configuration --- 12 | # 这是唯一需要您确认的地方! 13 | # 请确保这里的地址是您 MosDNS 服务的真实管理地址和端口。 14 | MOSDNS_ADMIN_URL = os.environ.get('MOSDNS_ADMIN_URL', 'http://127.0.0.1:9099') 15 | MOSDNS_METRICS_URL = f"{MOSDNS_ADMIN_URL}/metrics" 16 | 17 | def fetch_mosdns_metrics(): 18 | """从 MosDNS /metrics 接口获取原始文本数据""" 19 | try: 20 | response = requests.get(MOSDNS_METRICS_URL, timeout=5) 21 | response.raise_for_status() 22 | return response.text, None 23 | except requests.exceptions.RequestException as e: 24 | error_message = f"无法连接到 MosDNS metrics 接口: {e}" 25 | print(f"ERROR: {error_message}", file=sys.stderr) 26 | return None, error_message 27 | 28 | def parse_metrics(metrics_text): 29 | """解析 metrics 文本并格式化为前端需要的 JSON 结构""" 30 | data = {"caches": {}, "system": {"go_version": "N/A"}} 31 | 32 | # 使用更高效的单次遍历来解析所有指标 33 | patterns = { 34 | 'cache': re.compile(r'mosdns_cache_(\w+)\{tag="([^"]+)"\}\s+([\d.eE+-]+)'), 35 | 'start_time': re.compile(r'^process_start_time_seconds\s+([\d.eE+-]+)'), 36 | 'cpu_time': re.compile(r'^process_cpu_seconds_total\s+([\d.eE+-]+)'), 37 | 'resident_memory': re.compile(r'^process_resident_memory_bytes\s+([\d.eE+-]+)'), 38 | 'heap_idle_memory': re.compile(r'^go_memstats_heap_idle_bytes\s+([\d.eE+-]+)'), 39 | 'threads': re.compile(r'^go_threads\s+(\d+)'), 40 | 'open_fds': re.compile(r'^process_open_fds\s+(\d+)'), 41 | 'go_version': re.compile(r'go_info\{version="([^"]+)"\}') 42 | } 43 | 44 | for line in metrics_text.split('\n'): 45 | if (match := patterns['cache'].match(line)): 46 | metric, tag, value = match.groups() 47 | if tag not in data["caches"]: 48 | data["caches"][tag] = {} 49 | data["caches"][tag][metric] = float(value) 50 | elif (match := patterns['start_time'].match(line)): 51 | data["system"]["start_time"] = float(match.group(1)) 52 | elif (match := patterns['cpu_time'].match(line)): 53 | data["system"]["cpu_time"] = float(match.group(1)) 54 | elif (match := patterns['resident_memory'].match(line)): 55 | data["system"]["resident_memory"] = float(match.group(1)) 56 | elif (match := patterns['heap_idle_memory'].match(line)): 57 | data["system"]["heap_idle_memory"] = float(match.group(1)) 58 | elif (match := patterns['threads'].match(line)): 59 | data["system"]["threads"] = int(match.group(1)) 60 | elif (match := patterns['open_fds'].match(line)): 61 | data["system"]["open_fds"] = int(match.group(1)) 62 | elif (match := patterns['go_version'].search(line)): 63 | data["system"]["go_version"] = match.group(1) 64 | 65 | # 计算命中率并格式化数据 66 | for tag, metrics in data["caches"].items(): 67 | query_total = metrics.get("query_total", 0) 68 | hit_total = metrics.get("hit_total", 0) 69 | lazy_hit_total = metrics.get("lazy_hit_total", 0) 70 | metrics["hit_rate"] = f"{(hit_total / query_total * 100):.2f}%" if query_total > 0 else "0.00%" 71 | metrics["lazy_hit_rate"] = f"{(lazy_hit_total / query_total * 100):.2f}%" if query_total > 0 else "0.00%" 72 | 73 | if "start_time" in data["system"]: 74 | data["system"]["start_time"] = datetime.datetime.fromtimestamp(data["system"]["start_time"]).strftime('%Y-%m-%d %H:%M:%S') 75 | if "cpu_time" in data["system"]: 76 | data["system"]["cpu_time"] = f'{data["system"]["cpu_time"]:.2f} 秒' 77 | if "resident_memory" in data["system"]: 78 | data["system"]["resident_memory"] = f'{(data["system"]["resident_memory"] / 1024**2):.2f} MB' 79 | if "heap_idle_memory" in data["system"]: 80 | data["system"]["heap_idle_memory"] = f'{(data["system"]["heap_idle_memory"] / 1024**2):.2f} MB' 81 | 82 | return data 83 | 84 | # --- Flask 路由 --- 85 | 86 | @app.route('/') 87 | def index(): 88 | """提供主页面""" 89 | return render_template('index.html') 90 | 91 | @app.route('/api/mosdns_status') 92 | def get_mosdns_status(): 93 | """为前端提供格式化后的 JSON 监控数据""" 94 | metrics_text, error = fetch_mosdns_metrics() 95 | if error: 96 | return jsonify({"error": error}), 502 # 502 Bad Gateway is more appropriate 97 | data = parse_metrics(metrics_text) 98 | return jsonify(data) 99 | 100 | @app.route('/plugins/', methods=['GET', 'POST']) 101 | def proxy_plugins_request(subpath): 102 | """ 103 | 代理所有对 /plugins/ 路径的请求,以处理前端的控制按钮操作。 104 | 例如,前端请求 /plugins/my_fakeiplist/save -> 后端请求 http:///plugins/my_fakeiplist/save 105 | """ 106 | target_url = f"{MOSDNS_ADMIN_URL}/plugins/{subpath}" 107 | print(f"DEBUG: Proxying request to -> {target_url}", file=sys.stderr) 108 | 109 | try: 110 | # 前端所有按钮都是 GET 请求,但为了健壮性,我们依然可以处理 POST 111 | if request.method == 'POST': 112 | resp = requests.post(target_url, timeout=10) 113 | else: 114 | resp = requests.get(target_url, timeout=10) 115 | 116 | resp.raise_for_status() 117 | 118 | # 将 MosDNS 的响应原样返回给浏览器 119 | content_type = resp.headers.get('Content-Type', 'text/plain; charset=utf-8') 120 | return Response(resp.text, status=resp.status_code, content_type=content_type) 121 | 122 | except requests.exceptions.RequestException as e: 123 | error_message = f"代理请求到 MosDNS 失败 ({target_url}): {e}" 124 | print(f"ERROR: {error_message}", file=sys.stderr) 125 | return Response(f"请求 MosDNS 失败: {e}", status=502, mimetype='text/plain') 126 | 127 | if __name__ == '__main__': 128 | # 从环境变量获取端口,默认为 5001 129 | port = int(os.environ.get('FLASK_PORT', 5001)) 130 | # 在生产环境中,建议将 debug 设置为 False 131 | app.run(host='0.0.0.0', port=port, debug=False) 132 | -------------------------------------------------------------------------------- /Beta/MosdnsUI_Beta.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # MosDNS 独立监控面板 - Beta版专用部署脚本 4 | # 作者:ChatGPT & JimmyDADA & Phil Horse 5 | # 版本:9.2 (Beta独立部署版) 6 | # 特点: 7 | # - [独立部署] 使用独立的目录、服务名和端口,与正式版完全隔离,互不干扰。 8 | # - 专为 Beta 版 UI (带背景上传) 设计,自动处理所有依赖和目录。 9 | 10 | # --- 定义颜色和样式 --- 11 | C_GREEN='\033[0;32m'; C_YELLOW='\033[0;33m'; C_RED='\033[0;31m'; C_BLUE='\033[0;34m'; C_CYAN='\033[0;36m'; C_PURPLE='\033[0;35m'; C_BOLD='\033[1m'; C_NC='\033[0m'; 12 | 13 | # --- 辅助日志函数 --- 14 | log_info() { echo -e "${C_GREEN}✔ [信息]${C_NC} $1"; } 15 | log_warn() { echo -e "${C_YELLOW}⚠ [警告]${C_NC} $1"; } 16 | log_error() { echo -e "${C_RED}✖ [错误]${C_NC} $1"; } 17 | log_step() { echo -e "\n${C_PURPLE}🚀 [步骤 ${1}/${2}]${C_NC} ${C_BOLD}$3${C_NC}"; } 18 | log_success() { echo -e "\n${C_GREEN}🎉🎉🎉 $1 🎉🎉🎉${C_NC}"; } 19 | print_line() { echo -e "${C_BLUE}==================================================================${C_NC}"; } 20 | 21 | # --- [BETA版专用配置] --- 22 | FLASK_APP_NAME="mosdns_monitor_panel_beta" 23 | PROJECT_DIR="/opt/$FLASK_APP_NAME" 24 | BACKUP_DIR="$PROJECT_DIR/backups" 25 | UPLOAD_DIR="$PROJECT_DIR/uploads" 26 | FLASK_PORT=5002 # Beta版使用 5002 端口 27 | SYSTEMD_SERVICE_FILE="/etc/systemd/system/$FLASK_APP_NAME.service" 28 | 29 | # 使用您提供的 Beta 版文件下载地址 30 | APP_PY_URL="https://raw.githubusercontent.com/Jimmyzxk/MosDNSUI/refs/heads/main/Beta/app.py" 31 | INDEX_HTML_URL="https://raw.githubusercontent.com/Jimmyzxk/MosDNSUI/refs/heads/main/Beta/index.html" 32 | APP_PY_PATH="$PROJECT_DIR/app.py" 33 | INDEX_HTML_PATH="$PROJECT_DIR/templates/index.html" 34 | 35 | # --- 共享配置 --- 36 | MOSDNS_ADMIN_URL="http://127.0.0.1:9099" 37 | WEB_USER="www-data" 38 | 39 | # --- 辅助命令执行函数 (重构版) --- 40 | run_command() { 41 | local message="$1"; shift 42 | printf " %-55s" "$message" 43 | # shellcheck disable=SC2068 44 | ($@ &>/dev/null) & 45 | local pid=$!; local spin='⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏'; local i=0 46 | while kill -0 $pid 2>/dev/null; do 47 | i=$(( (i+1) % ${#spin[@]} )); printf "${C_CYAN}%s${C_NC}" "${spin:$i:1}"; sleep 0.1; printf "\b"; 48 | done 49 | wait $pid 50 | if [ $? -eq 0 ]; then echo -e "[ ${C_GREEN}成功${C_NC} ]"; return 0; 51 | else echo -e "[ ${C_RED}失败${C_NC} ]"; return 1; fi 52 | } 53 | 54 | # --- 核心功能函数 --- 55 | deploy_beta() { 56 | print_line; echo -e "${C_BLUE} 🚀 开始部署 MosDNS 监控面板 (Beta版) 🚀${C_NC}"; print_line 57 | 58 | log_step 1 5 "环境检测与依赖安装" 59 | run_command "测试 MosDNS 接口..." curl --output /dev/null --silent --head --fail "$MOSDNS_ADMIN_URL/metrics" || { log_error "无法访问 MosDNS 接口。"; return 1; } 60 | if ! id -u "$WEB_USER" >/dev/null 2>&1; then run_command "创建系统用户 '$WEB_USER'..." adduser --system --no-create-home --group "$WEB_USER" || return 1; fi 61 | run_command "更新 apt 缓存..." apt-get update -qq 62 | run_command "安装系统依赖..." apt-get install -y python3 python3-pip python3-flask python3-requests python3-werkzeug curl wget || return 1 63 | 64 | log_step 2 5 "创建 Beta 版项目目录结构" 65 | run_command "创建所有目录 (包括 uploads)..." mkdir -p "$PROJECT_DIR/templates" "$PROJECT_DIR/static" "$BACKUP_DIR" "$UPLOAD_DIR" || return 1 66 | 67 | log_step 3 5 "下载 Beta 版核心应用文件" 68 | run_command "下载 app.py (Beta)..." wget -qO "$APP_PY_PATH" "$APP_PY_URL" || { log_error "下载 app.py 失败!"; return 1; } 69 | run_command "下载 index.html (Beta)..." wget -qO "$INDEX_HTML_PATH" "$INDEX_HTML_URL" || { log_error "下载 index.html 失败!"; return 1; } 70 | run_command "设置文件权限..." chown -R "$WEB_USER:$WEB_USER" "$PROJECT_DIR" || return 1 71 | 72 | log_step 4 5 "创建并配置 Beta 版 Systemd 服务" 73 | local python_path; python_path=$(which python3) 74 | cat < "$SYSTEMD_SERVICE_FILE" 75 | [Unit] 76 | Description=MosDNS Monitoring Panel (Beta) 77 | After=network.target 78 | [Service] 79 | User=$WEB_USER 80 | Group=$WEB_USER 81 | WorkingDirectory=$PROJECT_DIR 82 | ExecStart=$python_path app.py 83 | Environment="FLASK_PORT=$FLASK_PORT" 84 | Restart=always 85 | [Install] 86 | WantedBy=multi-user.target 87 | EOF 88 | run_command "创建 Systemd 服务文件 (${FLASK_APP_NAME}.service)..." true 89 | 90 | log_step 5 5 "启动服务并设置开机自启" 91 | run_command "重载 Systemd..." systemctl daemon-reload || return 1 92 | run_command "启用 Beta 服务..." systemctl enable "$FLASK_APP_NAME" || return 1 93 | run_command "重启 Beta 服务..." systemctl restart "$FLASK_APP_NAME" || { log_error "服务启动失败!请检查日志。"; return 1; } 94 | 95 | local ip_addr; ip_addr=$(hostname -I | awk '{print $1}') 96 | print_line; log_success "Beta 版部署完成!" 97 | echo -e "${C_CYAN} 98 | ┌──────────────────────────────────────────────────┐ 99 | │ │ 100 | │ 请在浏览器中访问 Beta 版面板: │ 101 | │ ${C_BOLD}http://${ip_addr}:${FLASK_PORT}${C_NC} │ 102 | │ │ 103 | └──────────────────────────────────────────────────┘ 104 | ${C_NC}" 105 | } 106 | 107 | uninstall_beta() { 108 | log_warn "正在卸载 Beta 版..." 109 | run_command "停止并禁用 Beta 服务" systemctl stop "$FLASK_APP_NAME" && systemctl disable "$FLASK_APP_NAME" 110 | run_command "移除 Beta 服务文件" rm -f "$SYSTEMD_SERVICE_FILE" && systemctl daemon-reload 111 | run_command "移除 Beta 项目目录" rm -rf "$PROJECT_DIR" 112 | log_success "Beta 版卸载完成!" 113 | } 114 | 115 | # --- 主程序逻辑 --- 116 | main() { 117 | clear; print_line 118 | echo -e "${C_BLUE} MosDNS 监控面板 Beta 版管理脚本 ${C_NC}"; print_line; echo "" 119 | if [[ $EUID -ne 0 ]]; then log_error "此脚本必须以 root 用户运行。"; exit 1; fi 120 | 121 | PS3="请选择操作: " 122 | options=("部署 / 重装 Beta 版" "卸载 Beta 版" "退出") 123 | select opt in "${options[@]}"; do 124 | case $opt in 125 | "部署 / 重装 Beta 版") 126 | read -rp "这将覆盖现有 Beta 版部署。确定吗? (y/N): " c; if [[ "$c" =~ ^[yY]$ ]]; then uninstall_beta; deploy_beta; fi; break;; 127 | "卸载 Beta 版") 128 | read -rp "警告:这将删除 Beta 版所有文件和服务!确定吗?(y/N): " c; if [[ "$c" =~ ^[yY]$ ]]; then uninstall_beta; fi; break;; 129 | "退出") break;; 130 | *) echo "无效选项 $REPLY";; 131 | esac 132 | done 133 | echo ""; print_line; echo -e "${C_BLUE} -- 操作完成 --${C_NC}"; print_line 134 | } 135 | 136 | main "$@" 137 | -------------------------------------------------------------------------------- /Beta/app.py: -------------------------------------------------------------------------------- 1 | # Beta/app.py 2 | import os 3 | import sys 4 | import requests 5 | from flask import Flask, render_template, jsonify, Response, request, send_from_directory 6 | from werkzeug.utils import secure_filename 7 | import re 8 | import datetime 9 | 10 | app = Flask(__name__) 11 | 12 | # --- 全局配置 --- 13 | PROJECT_ROOT = os.path.dirname(os.path.abspath(__file__)) 14 | UPLOAD_FOLDER = os.path.join(PROJECT_ROOT, 'uploads') 15 | ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif', 'webp'} 16 | CUSTOM_BG_FILENAME = 'custom_background' 17 | 18 | # 确保上传目录存在 19 | if not os.path.exists(UPLOAD_FOLDER): 20 | os.makedirs(UPLOAD_FOLDER) 21 | 22 | app.config['UPLOAD_FOLDER'] = UPLOAD_FOLDER 23 | 24 | MOSDNS_ADMIN_URL = os.environ.get('MOSDNS_ADMIN_URL', 'http://127.0.0.1:9099') 25 | 26 | # --- 辅助函数 --- 27 | def allowed_file(filename): 28 | """检查文件后缀是否允许""" 29 | return '.' in filename and filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS 30 | 31 | def get_current_background_filename(): 32 | """检查是否存在自定义背景文件,并返回其完整文件名""" 33 | for ext in ALLOWED_EXTENSIONS: 34 | filepath = os.path.join(app.config['UPLOAD_FOLDER'], f"{CUSTOM_BG_FILENAME}.{ext}") 35 | if os.path.exists(filepath): 36 | return f"{CUSTOM_BG_FILENAME}.{ext}" 37 | return None 38 | 39 | # --- 数据获取与解析 --- 40 | def fetch_mosdns_metrics(): 41 | try: 42 | response = requests.get(f"{MOSDNS_ADMIN_URL}/metrics", timeout=5) 43 | response.raise_for_status() 44 | return response.text, None 45 | except requests.exceptions.RequestException as e: 46 | return None, f"无法连接到 MosDNS metrics 接口: {e}" 47 | 48 | def parse_metrics(metrics_text): 49 | data = {"caches": {}, "system": {"go_version": "N/A"}} 50 | patterns = { 51 | 'cache': re.compile(r'mosdns_cache_(\w+)\{tag="([^"]+)"\}\s+([\d.eE+-]+)'), 52 | 'start_time': re.compile(r'^process_start_time_seconds\s+([\d.eE+-]+)'), 53 | 'cpu_time': re.compile(r'^process_cpu_seconds_total\s+([\d.eE+-]+)'), 54 | 'resident_memory': re.compile(r'^process_resident_memory_bytes\s+([\d.eE+-]+)'), 55 | 'heap_idle_memory': re.compile(r'^go_memstats_heap_idle_bytes\s+([\d.eE+-]+)'), 56 | 'threads': re.compile(r'^go_threads\s+(\d+)'), 57 | 'open_fds': re.compile(r'^process_open_fds\s+(\d+)'), 58 | 'go_version': re.compile(r'go_info\{version="([^"]+)"\}') 59 | } 60 | for line in metrics_text.split('\n'): 61 | if (match := patterns['cache'].match(line)): 62 | metric, tag, value = match.groups() 63 | if tag not in data["caches"]: data["caches"][tag] = {} 64 | data["caches"][tag][metric] = float(value) 65 | elif (match := patterns['start_time'].match(line)): data["system"]["start_time"] = float(match.group(1)) 66 | elif (match := patterns['cpu_time'].match(line)): data["system"]["cpu_time"] = float(match.group(1)) 67 | elif (match := patterns['resident_memory'].match(line)): data["system"]["resident_memory"] = float(match.group(1)) 68 | elif (match := patterns['heap_idle_memory'].match(line)): data["system"]["heap_idle_memory"] = float(match.group(1)) 69 | elif (match := patterns['threads'].match(line)): data["system"]["threads"] = int(match.group(1)) 70 | elif (match := patterns['open_fds'].match(line)): data["system"]["open_fds"] = int(match.group(1)) 71 | elif (match := patterns['go_version'].search(line)): data["system"]["go_version"] = match.group(1) 72 | 73 | for tag, metrics in data["caches"].items(): 74 | query_total = metrics.get("query_total", 0) 75 | hit_total = metrics.get("hit_total", 0) 76 | lazy_hit_total = metrics.get("lazy_hit_total", 0) 77 | metrics["hit_rate"] = f"{(hit_total / query_total * 100):.2f}%" if query_total > 0 else "0.00%" 78 | metrics["lazy_hit_rate"] = f"{(lazy_hit_total / query_total * 100):.2f}%" if query_total > 0 else "0.00%" 79 | 80 | if "start_time" in data["system"] and data["system"]["start_time"]: data["system"]["start_time"] = datetime.datetime.fromtimestamp(data["system"]["start_time"]).strftime('%Y-%m-%d %H:%M:%S') 81 | if "cpu_time" in data["system"] and data["system"]["cpu_time"]: data["system"]["cpu_time"] = f'{data["system"]["cpu_time"]:.2f} 秒' 82 | if "resident_memory" in data["system"] and data["system"]["resident_memory"]: data["system"]["resident_memory"] = f'{(data["system"]["resident_memory"] / 1024**2):.2f} MB' 83 | if "heap_idle_memory" in data["system"] and data["system"]["heap_idle_memory"]: data["system"]["heap_idle_memory"] = f'{(data["system"]["heap_idle_memory"] / 1024**2):.2f} MB' 84 | 85 | return data 86 | 87 | # --- Flask 路由 --- 88 | 89 | @app.route('/') 90 | def index(): 91 | return render_template('index.html') 92 | 93 | @app.route('/api/mosdns_status') 94 | def get_mosdns_status(): 95 | metrics_text, error = fetch_mosdns_metrics() 96 | if error: return jsonify({"error": error}), 502 97 | return jsonify(parse_metrics(metrics_text)) 98 | 99 | @app.route('/plugins/', methods=['GET']) 100 | def proxy_plugins_request(subpath): 101 | target_url = f"{MOSDNS_ADMIN_URL}/plugins/{subpath}" 102 | try: 103 | resp = requests.get(target_url, timeout=10) 104 | resp.raise_for_status() 105 | content_type = resp.headers.get('Content-Type', 'text/plain; charset=utf-8') 106 | return Response(resp.text, status=resp.status_code, content_type=content_type) 107 | except requests.exceptions.RequestException as e: 108 | return Response(f"请求 MosDNS 失败: {e}", status=502, mimetype='text/plain') 109 | 110 | # --- 背景图片 API --- 111 | 112 | @app.route('/api/background_status') 113 | def get_background_status(): 114 | filename = get_current_background_filename() 115 | if filename: return jsonify({"status": "custom", "url": f"/backgrounds/{filename}"}) 116 | return jsonify({"status": "default"}) 117 | 118 | @app.route('/backgrounds/') 119 | def serve_background(filename): 120 | return send_from_directory(app.config['UPLOAD_FOLDER'], filename) 121 | 122 | @app.route('/api/upload_background', methods=['POST']) 123 | def upload_background(): 124 | if 'background_image' not in request.files: return jsonify({"error": "请求中没有文件部分"}), 400 125 | file = request.files['background_image'] 126 | if file.filename == '': return jsonify({"error": "未选择文件"}), 400 127 | if file and allowed_file(file.filename): 128 | try: 129 | old_filename = get_current_background_filename() 130 | if old_filename: os.remove(os.path.join(app.config['UPLOAD_FOLDER'], old_filename)) 131 | ext = file.filename.rsplit('.', 1)[1].lower() 132 | new_filename = f"{CUSTOM_BG_FILENAME}.{ext}" 133 | file.save(os.path.join(app.config['UPLOAD_FOLDER'], new_filename)) 134 | return jsonify({"success": True, "url": f"/backgrounds/{new_filename}"}) 135 | except Exception as e: return jsonify({"error": f"保存文件失败: {e}"}), 500 136 | return jsonify({"error": "文件类型不允许"}), 400 137 | 138 | @app.route('/api/remove_background', methods=['POST']) 139 | def remove_background(): 140 | filename = get_current_background_filename() 141 | if filename: 142 | try: 143 | os.remove(os.path.join(app.config['UPLOAD_FOLDER'], filename)) 144 | return jsonify({"success": True}) 145 | except OSError as e: return jsonify({"error": f"删除文件失败: {e}"}), 500 146 | return jsonify({"success": True, "message": "没有自定义背景可删除"}) 147 | 148 | if __name__ == '__main__': 149 | port = int(os.environ.get('FLASK_PORT', 5002)) 150 | app.run(host='0.0.0.0', port=port, debug=False) 151 | -------------------------------------------------------------------------------- /MosDNSUI.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | # MosDNS 独立监控面板 - 一键部署、更新、恢复脚本 4 | # 作者:ChatGPT & JimmyDADA & Phil Horse 5 | # 版本:7.3 (终极视觉修复版) 6 | # 特点: 7 | # - [UI/UX] 重构日志输出和命令执行函数,彻底解决终端乱码问题,输出更专业。 8 | # - 保持了所有核心功能:自动部署、更新、恢复、诊断。 9 | # - 保持了最佳兼容性:通过外部下载和系统 apt 安装。 10 | 11 | # --- 定义颜色和样式 --- 12 | C_GREEN='\033[0;32m' 13 | C_YELLOW='\033[0;33m' 14 | C_RED='\033[0;31m' 15 | C_BLUE='\033[0;34m' 16 | C_CYAN='\033[0;36m' 17 | C_PURPLE='\033[0;35m' 18 | C_BOLD='\033[1m' 19 | C_NC='\033[0m' # No Color 20 | 21 | # --- 辅助日志函数 --- 22 | log_info() { echo -e "${C_GREEN}✔ [信息]${C_NC} $1"; } 23 | log_warn() { echo -e "${C_YELLOW}⚠ [警告]${C_NC} $1"; } 24 | log_error() { echo -e "${C_RED}✖ [错误]${C_NC} $1"; } 25 | log_step() { echo -e "\n${C_PURPLE}🚀 [步骤 ${1}/${2}]${C_NC} ${C_BOLD}$3${C_NC}"; } 26 | log_success() { echo -e "\n${C_GREEN}🎉🎉🎉 $1 🎉🎉🎉${C_NC}"; } 27 | print_line() { echo -e "${C_BLUE}============================================================${C_NC}"; } 28 | 29 | # --- 全局变量 --- 30 | FLASK_APP_NAME="mosdns_monitor_panel" 31 | PROJECT_DIR="/opt/$FLASK_APP_NAME" 32 | BACKUP_DIR="$PROJECT_DIR/backups" 33 | FLASK_PORT=5001 34 | MOSDNS_ADMIN_URL="http://127.0.0.1:9099" 35 | WEB_USER="www-data" 36 | SYSTEMD_SERVICE_FILE="/etc/systemd/system/$FLASK_APP_NAME.service" 37 | 38 | # --- 外部下载地址 --- 39 | APP_PY_URL="https://raw.githubusercontent.com/Jimmyzxk/MosDNSUI/main/app.py" 40 | INDEX_HTML_URL="https://raw.githubusercontent.com/Jimmyzxk/MosDNSUI/main/index.html" 41 | APP_PY_PATH="$PROJECT_DIR/app.py" 42 | INDEX_HTML_PATH="$PROJECT_DIR/templates/index.html" 43 | 44 | # --- [重构] 辅助命令执行函数 --- 45 | run_command() { 46 | local message="$1" 47 | shift # 移除消息参数,剩下的是要执行的命令 48 | 49 | # 打印任务描述,使用 printf 控制格式,-55s 表示左对齐,宽度为55 50 | printf " %-55s" "$message" 51 | 52 | # 在子shell中执行命令,并将输出重定向到/dev/null 53 | # shellcheck disable=SC2068 54 | ($@ &>/dev/null) & 55 | local pid=$! 56 | 57 | # 加载动画 58 | local -a spin='⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏' 59 | local i=0 60 | while kill -0 $pid 2>/dev/null; do 61 | i=$(( (i+1) % ${#spin[@]} )) 62 | printf "${C_CYAN}%s${C_NC}" "${spin[$i]}" 63 | sleep 0.1 64 | printf "\b" 65 | done 66 | wait $pid 67 | local ret=$? 68 | 69 | # 打印最终状态 70 | if [ $ret -eq 0 ]; then 71 | echo -e "[ ${C_GREEN}成功${C_NC} ]" 72 | return 0 73 | else 74 | echo -e "[ ${C_RED}失败${C_NC} ]" 75 | # 失败时不需要打印命令,因为主调函数会处理 76 | return 1 77 | fi 78 | } 79 | 80 | # --- 卸载函数 --- 81 | uninstall_monitor() { 82 | log_warn "正在执行卸载/清理操作..." 83 | if systemctl is-active --quiet "$FLASK_APP_NAME"; then 84 | run_command "停止并禁用 Systemd 服务" systemctl stop "$FLASK_APP_NAME" 85 | run_command "禁用 Systemd 服务" systemctl disable "$FLASK_APP_NAME" 86 | fi 87 | if [ -f "$SYSTEMD_SERVICE_FILE" ]; then 88 | run_command "移除 Systemd 服务文件" rm -f "$SYSTEMD_SERVICE_FILE" 89 | run_command "重载 Systemd 配置" systemctl daemon-reload 90 | fi 91 | if [ -d "$PROJECT_DIR" ]; then 92 | run_command "移除项目目录 $PROJECT_DIR" rm -rf "$PROJECT_DIR" 93 | fi 94 | log_success "卸载/清理操作完成!" 95 | } 96 | 97 | # --- 部署函数 --- 98 | deploy_monitor() { 99 | print_line 100 | echo -e "${C_BLUE} 🚀 开始部署 MosDNS 监控面板 v7.3 🚀${C_NC}" 101 | print_line 102 | 103 | log_step 1 5 "环境检测与依赖安装" 104 | run_command "测试 MosDNS 接口..." curl --output /dev/null --silent --head --fail "$MOSDNS_ADMIN_URL/metrics" || { log_error "无法访问 MosDNS 接口。"; return 1; } 105 | 106 | if ! id -u "$WEB_USER" >/dev/null 2>&1; then 107 | run_command "创建系统用户 '$WEB_USER'..." adduser --system --no-create-home --group "$WEB_USER" || return 1 108 | fi 109 | 110 | run_command "更新 apt 缓存..." apt-get update -qq 111 | run_command "安装系统依赖..." apt-get install -y python3 python3-pip python3-flask python3-requests curl wget || return 1 112 | 113 | log_step 2 5 "创建项目目录结构" 114 | run_command "创建主目录及子目录..." mkdir -p "$PROJECT_DIR/templates" "$PROJECT_DIR/static" "$BACKUP_DIR" || return 1 115 | 116 | log_step 3 5 "下载核心应用文件" 117 | run_command "下载 app.py..." wget -qO "$APP_PY_PATH" "$APP_PY_URL" || { log_error "下载 app.py 失败!"; return 1; } 118 | run_command "下载 index.html..." wget -qO "$INDEX_HTML_PATH" "$INDEX_HTML_URL" || { log_error "下载 index.html 失败!"; return 1; } 119 | run_command "设置文件权限..." chown -R "$WEB_USER:$WEB_USER" "$PROJECT_DIR" || return 1 120 | 121 | log_step 4 5 "创建并配置 Systemd 服务" 122 | local python_path; python_path=$(which python3) 123 | cat < "$SYSTEMD_SERVICE_FILE" 124 | [Unit] 125 | Description=MosDNS Monitoring Panel Flask App 126 | After=network.target 127 | [Service] 128 | User=$WEB_USER 129 | Group=$WEB_USER 130 | WorkingDirectory=$PROJECT_DIR 131 | ExecStart=$python_path app.py 132 | Environment="FLASK_PORT=$FLASK_PORT" 133 | Restart=always 134 | [Install] 135 | WantedBy=multi-user.target 136 | EOF 137 | run_command "创建 Systemd 服务文件..." true # 'true' is a dummy command to show status 138 | 139 | log_step 5 5 "启动服务并设置开机自启" 140 | run_command "重载 Systemd..." systemctl daemon-reload || return 1 141 | run_command "启用服务..." systemctl enable "$FLASK_APP_NAME" || return 1 142 | run_command "重启服务..." systemctl restart "$FLASK_APP_NAME" || { 143 | log_error "服务启动失败!" 144 | log_warn "请运行 'sudo journalctl -u $FLASK_APP_NAME -f' 查看详细日志。" 145 | return 1 146 | } 147 | 148 | local ip_addr; ip_addr=$(hostname -I | awk '{print $1}') 149 | print_line 150 | log_success "部署完成!您的监控面板已准备就绪" 151 | echo -e "${C_CYAN} 152 | ┌──────────────────────────────────────────────────┐ 153 | │ │ 154 | │ 请在浏览器中访问以下地址: │ 155 | │ ${C_BOLD}http://${ip_addr}:${FLASK_PORT}${C_NC} │ 156 | │ │ 157 | └──────────────────────────────────────────────────┘ 158 | ${C_NC}" 159 | return 0 160 | } 161 | 162 | # --- 更新函数 --- 163 | update_app() { 164 | print_line; echo -e "${C_BLUE} 🔄 开始一键更新流程 🔄${C_NC}"; print_line 165 | if [ ! -d "$PROJECT_DIR" ]; then log_error "项目目录不存在,请先部署。"; return 1; fi 166 | 167 | local timestamp; timestamp=$(date +"%Y%m%d-%H%M%S") 168 | local current_backup_dir="$BACKUP_DIR/$timestamp" 169 | 170 | run_command "创建备份目录..." mkdir -p "$current_backup_dir/templates" || return 1 171 | run_command "备份 app.py..." cp "$APP_PY_PATH" "$current_backup_dir/app.py" || return 1 172 | run_command "备份 index.html..." cp "$INDEX_HTML_PATH" "$current_backup_dir/templates/index.html" || return 1 173 | 174 | log_info "正在从 GitHub 下载最新版本..." 175 | run_command "下载新版 app.py..." wget -qO "$APP_PY_PATH" "$APP_PY_URL" || { log_error "下载 app.py 失败!"; return 1; } 176 | run_command "下载新版 index.html..." wget -qO "$INDEX_HTML_PATH" "$INDEX_HTML_URL" || { log_error "下载 index.html 失败!"; return 1; } 177 | 178 | run_command "重设文件权限..." chown -R "$WEB_USER:$WEB_USER" "$PROJECT_DIR" 179 | 180 | run_command "重启服务以应用更新..." systemctl restart "$FLASK_APP_NAME" 181 | 182 | log_success "更新成功!请刷新浏览器页面查看新版本。" 183 | } 184 | 185 | # --- 恢复函数 --- 186 | revert_app() { 187 | print_line; echo -e "${C_BLUE} ⏪ 开始版本恢复流程 ⏪${C_NC}"; print_line 188 | if [ ! -d "$BACKUP_DIR" ] || [ -z "$(ls -A "$BACKUP_DIR")" ]; then 189 | log_warn "没有找到任何备份。无法执行恢复操作。" 190 | return 0 191 | fi 192 | 193 | log_info "发现以下备份版本(按时间倒序):" 194 | local backups=(); while IFS= read -r line; do backups+=("$line"); done < <(ls -1r "$BACKUP_DIR") 195 | local i=1 196 | for backup in "${backups[@]}"; do 197 | echo -e " ${C_YELLOW}$i)${C_NC} ${C_CYAN}$backup${C_NC}" 198 | i=$((i+1)) 199 | done 200 | 201 | local selection 202 | read -rp "请输入您要恢复的备份版本编号 (1-${#backups[@]}): " selection 203 | 204 | if ! [[ "$selection" =~ ^[0-9]+$ ]] || [ "$selection" -lt 1 ] || [ "$selection" -gt ${#backups[@]} ]; then 205 | log_error "无效的编号。操作已取消。" 206 | return 1 207 | fi 208 | 209 | local selected_backup_dir="$BACKUP_DIR/${backups[$((selection-1))]}" 210 | log_info "您选择了恢复版本: ${backups[$((selection-1))]}" 211 | read -rp "确定要用此版本覆盖当前文件吗?(y/N): " CONFIRM 212 | 213 | if [[ "$CONFIRM" =~ ^[yY]$ ]]; then 214 | run_command "从 $selected_backup_dir 恢复文件..." \ 215 | cp "$selected_backup_dir/app.py" "$APP_PY_PATH" && \ 216 | cp "$selected_backup_dir/templates/index.html" "$INDEX_HTML_PATH" 217 | run_command "重设文件权限..." chown -R "$WEB_USER:$WEB_USER" "$PROJECT_DIR" 218 | run_command "重启服务以应用恢复..." systemctl restart "$FLASK_APP_NAME" 219 | log_success "恢复成功!请刷新浏览器页面。" 220 | else 221 | log_info "恢复操作已取消。" 222 | fi 223 | } 224 | 225 | # --- 诊断函数 --- 226 | diagnose_and_fix() { 227 | print_line; echo -e "${C_BLUE} 🩺 开始一键诊断流程 🩺${C_NC}"; print_line 228 | 229 | log_info "检查 MosDNS 服务..." 230 | if curl --output /dev/null --silent --head --fail "$MOSDNS_ADMIN_URL/metrics"; then 231 | log_green "✅ MosDNS 服务正常。" 232 | else 233 | log_warn "❌ MosDNS 服务无法访问。请手动检查。" 234 | fi 235 | 236 | log_info "检查监控面板服务..." 237 | if systemctl is-active --quiet "$FLASK_APP_NAME"; then 238 | log_green "✅ 监控面板服务 ($FLASK_APP_NAME) 正在运行。" 239 | else 240 | log_warn "❌ 监控面板服务未运行。尝试重启..." 241 | run_command "重启监控服务..." systemctl restart "$FLASK_APP_NAME" || log_error "重启失败,请查看日志: journalctl -u $FLASK_APP_NAME" 242 | fi 243 | } 244 | 245 | # --- 主程序逻辑 --- 246 | main() { 247 | clear 248 | print_line 249 | echo -e "${C_PURPLE} __ __ ____ ____ _ _ ____ _ _ ___ _ _${C_NC}" 250 | echo -e "${C_PURPLE} | \\/ |/ ___|/ ___| | \\ | | _ \\| \\ | |_ _|| \\ | |${C_NC}" 251 | echo -e "${C_PURPLE} | |\\/| | | _| | | \\| | | | | \\| || | | \\| |${C_NC}" 252 | echo -e "${C_PURPLE} | | | | |_| | |___ | |\\ | |_| | |\\ || | | |\\ |${C_NC}" 253 | echo -e "${C_PURPLE} |_| |_|\\____|\\____| |_| \\_|____/|_| \\_|___||_| \\_|${C_NC}" 254 | echo -e "${C_BLUE} 独立监控面板 - 管理脚本 v7.3${C_NC}" 255 | print_line 256 | echo "" 257 | 258 | if [[ $EUID -ne 0 ]]; then 259 | log_error "此脚本必须以 root 用户运行。请使用 'sudo bash $0'" 260 | exit 1 261 | fi 262 | 263 | echo -e "${C_BOLD}请选择您要执行的操作:${C_NC}" 264 | echo -e " ${C_YELLOW}1)${C_NC} ${C_CYAN}部署 / 重装监控面板${C_NC}" 265 | echo -e " ${C_YELLOW}2)${C_NC} ${C_CYAN}一键更新 (从 GitHub)${C_NC}" 266 | echo -e " ${C_YELLOW}3)${C_NC} ${C_CYAN}一键恢复 (从本地备份)${C_NC}" 267 | echo -e " ${C_YELLOW}4)${C_NC} ${C_CYAN}一键诊断${C_NC}" 268 | echo -e " ${C_YELLOW}5)${C_NC} ${C_RED}卸载监控面板${C_NC}" 269 | echo -e " ${C_YELLOW}6)${C_NC} ${C_CYAN}退出脚本${C_NC}" 270 | echo "" 271 | 272 | local choice 273 | read -rp "请输入选项编号 [1-6]: " choice 274 | 275 | case $choice in 276 | 1) 277 | read -rp "这将覆盖现有部署。确定吗? (y/N): " CONFIRM 278 | if [[ "$CONFIRM" =~ ^[yY]$ ]]; then 279 | uninstall_monitor 280 | deploy_monitor 281 | else 282 | log_info "部署已取消。" 283 | fi 284 | ;; 285 | 2) 286 | read -rp "这将备份当前版本并从GitHub下载最新版。确定吗? (y/N): " CONFIRM 287 | if [[ "$CONFIRM" =~ ^[yY]$ ]]; then 288 | update_app 289 | else 290 | log_info "更新已取消。" 291 | fi 292 | ;; 293 | 3) 294 | revert_app 295 | ;; 296 | 4) 297 | diagnose_and_fix 298 | ;; 299 | 5) 300 | read -rp "警告:这将删除所有相关文件、服务和备份!确定吗?(y/N): " CONFIRM 301 | if [[ "$CONFIRM" =~ ^[yY]$ ]]; then 302 | uninstall_monitor 303 | else 304 | log_info "卸载已取消。" 305 | fi 306 | ;; 307 | 6) 308 | log_info "脚本已退出。" 309 | exit 0 310 | ;; 311 | *) 312 | log_error "无效的选项。" 313 | ;; 314 | esac 315 | 316 | echo "" 317 | print_line 318 | echo -e "${C_BLUE} -- 操作完成 --${C_NC}" 319 | print_line 320 | } 321 | 322 | # --- 脚本入口 --- 323 | main "$@" 324 | -------------------------------------------------------------------------------- /Beta/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | MosDNS 监控面板 (Beta) 7 | 8 | 9 | 10 | 118 | 119 | 120 | MosDNS 监控面板 (Beta) 121 | 122 | 123 | 124 | 125 | 126 | 控制中心 127 | 自动刷新 (5s) 立即刷新 128 | 保存分流规则 清空分流规则 重启 MosDNS 服务 129 | 130 | 131 | 数据查看 132 | 133 | 134 | 135 | 136 | 137 | 系统信息 外观设置 138 | 139 | 140 | 主题切换 141 | 142 | 自定义背景 143 | 144 | 上传背景 145 | 移除背景 146 | 147 | 148 | 149 | 毛玻璃强度 150 | 151 | 152 | 153 | 154 | 155 | 156 | 157 | 158 | 显示缓存卡片 159 | 160 | 161 | 162 | 163 | 164 | 171 | 270 | 271 | 272 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | MosDNS 监控面板 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 177 | 178 | 179 | 180 | MosDNS 监控面板 181 | 182 | 183 | 184 | 185 | 186 | 187 | 188 | 189 | 190 | 191 | 控制中心 192 | 193 | 194 | 195 | 196 | 197 | 198 | 自动刷新 (5s) 199 | 200 | 立即刷新 201 | 202 | 203 | 204 | 保存分流规则 205 | 清空分流规则 206 | 207 | 重启 MosDNS 服务 208 | 209 | 210 | 211 | 212 | 数据查看 213 | 214 | fakeip 域名 215 | realip 域名 216 | 无 V4 域名 217 | 无 V6 域名 218 | 总缓存 219 | CN 缓存 220 | !CN 缓存 221 | 节点缓存 222 | 223 | 224 | 225 | 226 | 227 | 228 | 229 | 系统信息 230 | 外观设置 231 | 232 | 233 | 234 | 235 | 236 | 237 | 主题切换 238 | 239 | 显示缓存卡片 240 | 241 | 242 | 243 | 244 | 245 | 246 | 247 | 260 | 261 | 406 | 407 | 408 | --------------------------------------------------------------------------------