Files
socks-manager/routes/main.py
T
Your Name aa8af6c2e0 feat: SOCKS5 代理管理系统 v1.0
Flask + SQLite + Bootstrap5 暗色主题
- 仪表盘: 在线/离线统计 + 最近代理 + 操作日志
- 代理 CRUD: 增删改 + 启用/禁用 + 单/批量健康检测(SOCKS5)
- 分组管理: 创建/删除/筛选
- 操作日志: 分页查询
- RESTful API + Web 界面
- 管理员登录认证 (SM_ADMIN_PASSWORD 环境变量)
2026-07-16 16:40:20 +08:00

154 lines
5.8 KiB
Python

from flask import Blueprint, render_template, request, redirect, url_for, flash, jsonify
from datetime import datetime, timezone
from auth import login_required
from models import Proxy, Group, Log, db
from health import check_proxy, run_health_checks
bp = Blueprint("main", __name__)
def _log(level, action, target=None, detail=None):
db.session.add(Log(level=level, action=action, target=target, detail=detail))
db.session.commit()
# ── Dashboard ──────────────────────────────────────────────────
@bp.route("/")
@bp.route("/dashboard")
@login_required
def dashboard():
total = Proxy.query.count()
online = Proxy.query.filter_by(status="online", enabled=True).count()
offline = Proxy.query.filter_by(status="offline").count()
unknown = Proxy.query.filter_by(status="unknown").count()
groups = Group.query.count()
recent = Log.query.order_by(Log.timestamp.desc()).limit(20).all()
proxies = Proxy.query.filter_by(enabled=True).order_by(Proxy.updated_at.desc()).limit(10).all()
return render_template("dashboard.html", total=total, online=online, offline=offline,
unknown=unknown, groups=groups, recent=recent, proxies=proxies)
# ── Proxy list ─────────────────────────────────────────────────
@bp.route("/proxies")
@login_required
def proxy_list():
g = request.args.get("group")
query = Proxy.query.order_by(Proxy.updated_at.desc())
if g:
query = query.filter_by(group_id=g)
groups = Group.query.all()
return render_template("proxies.html", proxies=query.all(), groups=groups, active_group=g)
@bp.route("/proxies/<int:pid>", methods=["DELETE"])
@login_required
def delete_proxy(pid):
p = Proxy.query.get_or_404(pid)
_log("WARN", "delete", p.name, f"删除代理 {p.host}:{p.port}")
db.session.delete(p)
db.session.commit()
return redirect(url_for("main.proxy_list"))
@bp.route("/proxies/toggle/<int:pid>")
@login_required
def toggle_proxy(pid):
p = Proxy.query.get_or_404(pid)
p.enabled = not p.enabled
db.session.commit()
_log("INFO", "toggle", p.name, f"{'启用' if p.enabled else '禁用'}")
return redirect(url_for("main.proxy_list"))
# ── Add / Edit ─────────────────────────────────────────────────
@bp.route("/proxies/add", methods=["GET", "POST"])
@login_required
def add_proxy():
if request.method == "POST":
p = Proxy(
name=request.form["name"],
host=request.form["host"],
port=int(request.form["port"]),
username=request.form.get("username"),
password=request.form.get("password"),
group_id=int(request.form["group_id"]) if request.form.get("group_id") else None,
notes=request.form.get("notes"),
)
db.session.add(p)
db.session.commit()
_log("INFO", "add", p.name, f"添加代理 {p.host}:{p.port}")
return redirect(url_for("main.proxy_list"))
groups = Group.query.all()
return render_template("proxy_form.html", p=None, groups=groups, action="add")
@bp.route("/proxies/<int:pid>/edit", methods=["GET", "POST"])
@login_required
def edit_proxy(pid):
p = Proxy.query.get_or_404(pid)
if request.method == "POST":
p.name = request.form["name"]
p.host = request.form["host"]
p.port = int(request.form["port"])
p.username = request.form.get("username")
p.password = request.form.get("password")
p.group_id = int(request.form["group_id"]) if request.form.get("group_id") else None
p.notes = request.form.get("notes")
db.session.commit()
_log("INFO", "update", p.name, f"更新代理 {p.host}:{p.port}")
return redirect(url_for("main.proxy_list"))
groups = Group.query.all()
return render_template("proxy_form.html", p=p, groups=groups, action="edit")
# ── Group ──────────────────────────────────────────────────────
@bp.route("/groups")
@login_required
def group_list():
groups = Group.query.all()
return render_template("groups.html", groups=groups)
@bp.route("/groups/add", methods=["POST"])
@login_required
def add_group():
name = request.form["name"]
if Group.query.filter_by(name=name).first():
return jsonify({"error": "分组已存在"}), 400
g = Group(name=name, color=request.form.get("color", "#6366f1"),
description=request.form.get("description"))
db.session.add(g)
db.session.commit()
_log("INFO", "add", name, "添加分组")
return jsonify({"id": g.id, "name": g.name})
@bp.route("/groups/<int:gid>", methods=["DELETE"])
@login_required
def delete_group(gid):
g = Group.query.get_or_404(gid)
if g.proxies:
return jsonify({"error": "分组下仍有代理,请先移动"}), 400
_log("WARN", "delete", g.name, "删除分组")
db.session.delete(g)
db.session.commit()
return jsonify({"ok": True})
# ── Logs ───────────────────────────────────────────────────────
@bp.route("/logs")
@login_required
def logs():
page = int(request.args.get("page", 1))
ps = db.paginate(Log.query.order_by(Log.timestamp.desc()), page=page, per_page=50)
return render_template("logs.html", pages=ps,
page_start=max(1, ps.page - 2),
page_end=min(ps.pages, ps.page + 2))
@bp.route("/health/check-all")
@login_required
def health_check_all():
results = run_health_checks()
return jsonify(results)