feat: SOCKS5 代理管理系统 v1.0
Flask + SQLite + Bootstrap5 暗色主题 - 仪表盘: 在线/离线统计 + 最近代理 + 操作日志 - 代理 CRUD: 增删改 + 启用/禁用 + 单/批量健康检测(SOCKS5) - 分组管理: 创建/删除/筛选 - 操作日志: 分页查询 - RESTful API + Web 界面 - 管理员登录认证 (SM_ADMIN_PASSWORD 环境变量)
This commit is contained in:
+153
@@ -0,0 +1,153 @@
|
||||
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)
|
||||
Reference in New Issue
Block a user