feat: 添加登录页面,支持外网访问 Web UI

- 新增 /web/login 登录页面
- 修改路由使用 optional JWT 认证
- 前端自动检测 token 并跳转登录
- 更新 .gitignore 排除 venv
This commit is contained in:
2026-03-15 07:28:20 +08:00
parent bb249f0317
commit 03a68c982e
714 changed files with 252771 additions and 34 deletions

View File

@@ -2,78 +2,149 @@
Web UI Blueprint
智队中枢 Web 管理界面
"""
from flask import Blueprint, render_template, request, jsonify
from flask_jwt_extended import jwt_required, get_jwt_identity
from flask import Blueprint, render_template, request, jsonify, redirect, url_for
from flask_jwt_extended import jwt_required, get_jwt_identity, verify_jwt_in_request, optional
from datetime import datetime, timedelta
from app.models import db, User, Session, Agent, Gateway, Message
web_bp = Blueprint('web', __name__, url_prefix='/web')
@web_bp.route('/login')
def login():
"""登录页面"""
return render_template('auth/login.html')
@web_bp.route('/')
@jwt_required()
@jwt_required(optional=True)
def index():
"""首页/仪表盘"""
# 统计数据
stats = {
'online_agents': Agent.query.filter_by(status='online').count(),
'active_sessions': Session.query.filter_by(status='active').count(),
'today_messages': Message.query.filter(
Message.created_at >= datetime.utcnow() - timedelta(days=1)
).count(),
'online_gateways': Gateway.query.filter_by(status='online').count(),
}
# 最近会话
recent_sessions = Session.query.order_by(
Session.last_active_at.desc()
).limit(5).all()
try:
verify_jwt_in_request(optional=True)
user_id = get_jwt_identity()
if not user_id:
# 未登录,返回空数据页面,前端会跳转到登录
stats = {
'online_agents': 0,
'active_sessions': 0,
'today_messages': 0,
'online_gateways': 0,
}
recent_sessions = []
else:
# 已登录,获取真实数据
stats = {
'online_agents': Agent.query.filter_by(status='online').count(),
'active_sessions': Session.query.filter_by(status='active').count(),
'today_messages': Message.query.filter(
Message.created_at >= datetime.utcnow() - timedelta(days=1)
).count(),
'online_gateways': Gateway.query.filter_by(status='online').count(),
}
recent_sessions = Session.query.order_by(
Session.last_active_at.desc()
).limit(5).all()
except:
stats = {
'online_agents': 0,
'active_sessions': 0,
'today_messages': 0,
'online_gateways': 0,
}
recent_sessions = []
return render_template('index.html', stats=stats, recent_sessions=recent_sessions)
@web_bp.route('/config/channels')
@jwt_required()
@jwt_required(optional=True)
def channels():
"""频道配置列表"""
from app.models.channel_config import ChannelConfig
configs = ChannelConfig.query.all()
try:
verify_jwt_in_request(optional=True)
user_id = get_jwt_identity()
if not user_id:
configs = []
else:
from app.models.channel_config import ChannelConfig
configs = ChannelConfig.query.all()
except:
configs = []
return render_template('config/channels.html', configs=configs)
@web_bp.route('/test')
@jwt_required()
@jwt_required(optional=True)
def test():
"""连接测试页面"""
from app.models.channel_config import ChannelConfig
configs = ChannelConfig.query.filter_by(enabled=True).all()
try:
verify_jwt_in_request(optional=True)
user_id = get_jwt_identity()
if not user_id:
configs = []
else:
from app.models.channel_config import ChannelConfig
configs = ChannelConfig.query.filter_by(enabled=True).all()
except:
configs = []
return render_template('test/index.html', configs=configs)
@web_bp.route('/monitor/sessions')
@jwt_required()
@jwt_required(optional=True)
def sessions():
"""会话监控"""
agents = Agent.query.all()
sessions = Session.query.order_by(Session.last_active_at.desc()).limit(50).all()
try:
verify_jwt_in_request(optional=True)
user_id = get_jwt_identity()
if not user_id:
agents = []
sessions = []
else:
agents = Agent.query.all()
sessions = Session.query.order_by(Session.last_active_at.desc()).limit(50).all()
except:
agents = []
sessions = []
return render_template('monitor/sessions.html', sessions=sessions, agents=agents)
@web_bp.route('/monitor/sessions/<session_id>')
@jwt_required()
@jwt_required(optional=True)
def session_detail(session_id):
"""会话详情页面"""
session = Session.query.get_or_404(session_id)
messages = Message.query.filter_by(session_id=session_id).order_by(
Message.created_at.asc()
).limit(200).all()
try:
verify_jwt_in_request(optional=True)
user_id = get_jwt_identity()
if not user_id:
session = None
messages = []
else:
session = Session.query.get_or_404(session_id)
messages = Message.query.filter_by(session_id=session_id).order_by(
Message.created_at.asc()
).limit(200).all()
except:
session = None
messages = []
if session is None:
return render_template('errors/401.html'), 401
return render_template('monitor/session_detail.html', session=session, messages=messages)
@web_bp.route('/config/channels/<channel_id>/edit')
@jwt_required()
@jwt_required(optional=True)
def channel_edit(channel_id):
"""编辑频道配置页面"""
from app.models.channel_config import ChannelConfig
config = ChannelConfig.query.get_or_404(channel_id)
try:
verify_jwt_in_request(optional=True)
user_id = get_jwt_identity()
if not user_id:
return render_template('errors/401.html'), 401
from app.models.channel_config import ChannelConfig
config = ChannelConfig.query.get_or_404(channel_id)
except:
return render_template('errors/401.html'), 401
return render_template('config/channel_edit.html', config=config)