增加新功能

This commit is contained in:
wzj 2025-06-24 11:06:02 +08:00
parent 048d4e20cf
commit 4b5c89786e
6 changed files with 736 additions and 270 deletions

351
app.py
View File

@ -1,139 +1,278 @@
from flask import Flask, render_template, request, jsonify, abort, redirect, url_for
from flask import Flask, render_template, request, jsonify, abort, redirect, url_for, session, flash
from functools import wraps
import os
import subprocess
import base64
import sqlite3
from werkzeug.security import generate_password_hash, check_password_hash
app = Flask(__name__)
app.secret_key = os.urandom(24)
# 配置
# 数据库配置
DATABASE = 'config/squid_manager.db'
SQUID_PASSWD_FILE = 'config/squid_passwd'
ADMIN_PASSWORD = os.getenv('SQUID_PASSWORD', 'admin123')
class User:
def __init__(self, name, password, is_active=True):
self.name = name
self.password = password
self.is_active = is_active
# 初始化数据库
def init_db():
with sqlite3.connect(DATABASE) as conn:
cursor = conn.cursor()
cursor.execute('''
CREATE TABLE IF NOT EXISTS admin_users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password TEXT NOT NULL
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS squid_users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
username TEXT UNIQUE NOT NULL,
password TEXT NOT NULL,
is_active INTEGER DEFAULT 1
)
''')
cursor.execute('''
CREATE TABLE IF NOT EXISTS settings (
id INTEGER PRIMARY KEY DEFAULT 1,
proxy_address TEXT DEFAULT 'proxy.example.com',
proxy_port TEXT DEFAULT '3128',
CONSTRAINT singleton CHECK (id = 1)
)
''')
# 检查是否有管理员用户
cursor.execute("SELECT COUNT(*) FROM admin_users")
if cursor.fetchone()[0] == 0:
cursor.execute(
"INSERT INTO admin_users (username, password) VALUES (?, ?)",
('admin', generate_password_hash('admin123'))
# 检查是否有设置
cursor.execute("SELECT COUNT(*) FROM settings")
if cursor.fetchone()[0] == 0:
cursor.execute(
"INSERT INTO settings (proxy_address, proxy_port) VALUES (?, ?)",
('proxy.example.com', '3128'))
conn.commit()
# 数据库连接
def basic_auth_required(f):
def get_db():
db = sqlite3.connect(DATABASE)
db.row_factory = sqlite3.Row
return db
# 登录装饰器
def login_required(f):
@wraps(f)
def decorated(*args, **kwargs):
auth = request.authorization
if not auth or not (auth.username == 'admin' and auth.password == ADMIN_PASSWORD):
return ('Unauthorized', 401,
{'WWW-Authenticate': 'Basic realm="Authorization Required"'})
def decorated_function(*args, **kwargs):
if 'logged_in' not in session:
return redirect(url_for('login'))
return f(*args, **kwargs)
return decorated
return decorated_function
def read_squid_file():
users = []
try:
with open(SQUID_PASSWD_FILE, 'r') as f:
for line in f:
line = line.strip()
if not line:
continue
is_active = not line.startswith('#')
if not is_active:
line = line[1:]
parts = line.split(':', 1)
if len(parts) == 2:
users.append(User(parts[0], parts[1], is_active))
except FileNotFoundError:
pass
return users
def write_squid_file(users):
with open(SQUID_PASSWD_FILE, 'w') as f:
for user in users:
line = f"{'#' if not user.is_active else ''}{user.name}:{user.password}\n"
f.write(line)
def create_user(username, password):
users = read_squid_file()
if any(u.name == username for u in users):
return False
try:
subprocess.run(['htpasswd', '-b', SQUID_PASSWD_FILE, username, password], check=True)
return True
except subprocess.CalledProcessError:
return False
# 初始化应用
init_db()
@app.route('/')
@basic_auth_required
@login_required
def index():
users = read_squid_file()
return render_template('clients.html', users=users)
db = get_db()
squid_users = db.execute("SELECT * FROM squid_users").fetchall()
settings = db.execute("SELECT * FROM settings WHERE id = 1").fetchone()
db.close()
return render_template('index.html',
user_count=len(squid_users),
settings=settings)
@app.route('/toggle/<username>', methods=['POST'])
@basic_auth_required
def toggle_user(username):
users = read_squid_file()
for user in users:
if user.name == username:
user.is_active = not user.is_active
break
@app.route('/login', methods=['GET', 'POST'])
def login():
if request.method == 'POST':
username = request.form['username']
password = request.form['password']
write_squid_file(users)
return '', 200
db = get_db()
user = db.execute(
"SELECT * FROM admin_users WHERE username = ?", (username,)
).fetchone()
db.close()
@app.route('/delete/<username>', methods=['POST'])
@basic_auth_required
def delete_user(username):
users = [u for u in read_squid_file() if u.name != username]
write_squid_file(users)
return '', 200
@app.route('/save_user', methods=['POST'])
@basic_auth_required
def save_user():
data = request.get_json()
username = data.get('username')
password = data.get('password')
if not username or not password:
return jsonify({'error': 'Username and password required'}), 400
try:
subprocess.run(['htpasswd', '-b', SQUID_PASSWD_FILE, username, password], check=True)
return '', 200
except subprocess.CalledProcessError:
return jsonify({'error': 'Failed to update password'}), 500
@app.route('/create_user', methods=['POST'])
@basic_auth_required
def handle_create_user():
data = request.get_json()
username = data.get('username')
password = data.get('password')
if not username or not password:
return jsonify({'error': 'Username and password required'}), 400
if create_user(username, password):
return '', 200
if user and check_password_hash(user['password'], password):
session['logged_in'] = True
session['username'] = username
return redirect(url_for('index'))
else:
return jsonify({'error': 'User already exists'}), 409
flash('用户名或密码错误', 'error')
return render_template('login.html')
@app.route('/logout')
def logout():
return ('', 401, {'WWW-Authenticate': 'Basic realm="Authorization Required"'})
session.clear()
return redirect(url_for('login'))
@app.route('/clients')
@login_required
def clients():
db = get_db()
users = db.execute("SELECT * FROM squid_users").fetchall()
db.close()
return render_template('clients.html', users=users)
@app.route('/settings', methods=['GET', 'POST'])
@login_required
def settings():
db = get_db()
if request.method == 'POST':
action = request.form.get('action')
if action == 'change_password':
current_password = request.form['current_password']
new_password = request.form['new_password']
confirm_password = request.form['confirm_password']
user = db.execute(
"SELECT * FROM admin_users WHERE username = ?", (session['username'],)
).fetchone()
if not check_password_hash(user['password'], current_password):
flash('当前密码不正确', 'error')
elif new_password != confirm_password:
flash('新密码不匹配', 'error')
else:
db.execute(
"UPDATE admin_users SET password = ? WHERE username = ?",
(generate_password_hash(new_password), session['username'])
)
db.commit()
flash('密码修改成功', 'success')
elif action == 'update_proxy':
proxy_address = request.form['proxy_address']
proxy_port = request.form['proxy_port']
db.execute(
"UPDATE settings SET proxy_address = ?, proxy_port = ? WHERE id = 1",
(proxy_address, proxy_port)
)
db.commit()
flash('代理设置更新成功', 'success')
settings = db.execute("SELECT * FROM settings WHERE id = 1").fetchone()
db.close()
return render_template('settings.html', settings=settings)
# Squid用户管理API
@app.route('/api/toggle_user/<int:user_id>', methods=['POST'])
@login_required
def toggle_user(user_id):
db = get_db()
user = db.execute("SELECT is_active FROM squid_users WHERE id = ?", (user_id,)).fetchone()
if user:
new_status = 0 if user['is_active'] else 1
db.execute(
"UPDATE squid_users SET is_active = ? WHERE id = ?",
(new_status, user_id)
)
db.commit()
# 更新squid_passwd文件
update_squid_passwd()
db.close()
return jsonify({'success': True})
@app.route('/api/delete_user/<int:user_id>', methods=['POST'])
@login_required
def delete_user(user_id):
db = get_db()
db.execute("DELETE FROM squid_users WHERE id = ?", (user_id,))
db.commit()
# 更新squid_passwd文件
update_squid_passwd()
db.close()
return jsonify({'success': True})
@app.route('/api/create_user', methods=['POST'])
@login_required
def create_user():
username = request.json.get('username')
password = request.json.get('password')
if not username or not password:
return jsonify({'success': False, 'error': '用户名和密码不能为空'}), 400
db = get_db()
try:
db.execute(
"INSERT INTO squid_users (username, password) VALUES (?, ?)",
(username, password)
)
db.commit()
# 更新squid_passwd文件
update_squid_passwd()
return jsonify({'success': True})
except sqlite3.IntegrityError:
return jsonify({'success': False, 'error': '用户名已存在'}), 400
finally:
db.close()
@app.route('/api/update_user_password', methods=['POST'])
@login_required
def update_user_password():
user_id = request.json.get('user_id')
password = request.json.get('password')
if not user_id or not password:
return jsonify({'success': False, 'error': '参数不完整'}), 400
db = get_db()
db.execute(
"UPDATE squid_users SET password = ? WHERE id = ?",
(password, user_id)
)
db.commit()
# 更新squid_passwd文件
update_squid_passwd()
db.close()
return jsonify({'success': True})
def update_squid_passwd():
db = get_db()
users = db.execute("SELECT * FROM squid_users").fetchall()
db.close()
with open(SQUID_PASSWD_FILE, 'w') as f:
for user in users:
line = f"{'#' if not user['is_active'] else ''}{user['username']}:{user['password']}\n"
f.write(line)
if __name__ == '__main__':

View File

@ -1,138 +1,50 @@
/* 基础样式 */
body {
font-family: 'Microsoft YaHei', 'PingFang SC', sans-serif;
max-width: 900px;
margin: 0 auto;
padding: 30px;
background-color: #f8f9fa;
margin: 0;
padding: 0;
background-color: #f5f5f5;
color: #333;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background-color: white;
border-radius: 10px;
padding: 30px;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.08);
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
}
h1 {
text-align: center;
color: #2c3e50;
margin-bottom: 30px;
font-weight: 600;
font-size: 28px;
}
table {
width: 100%;
border-collapse: collapse;
margin: 25px 0;
background-color: white;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
border-radius: 8px;
overflow: hidden;
}
th, td {
padding: 15px 20px;
text-align: left;
border-bottom: 1px solid #e9ecef;
}
th {
background-color: #f8f9fa;
font-weight: 600;
color: #495057;
}
tr:hover {
background-color: #f8f9fa;
}
.actions {
.login-container {
display: flex;
gap: 10px;
align-items: center;
}
button {
padding: 8px 16px;
border: none;
border-radius: 6px;
cursor: pointer;
transition: all 0.3s;
font-size: 14px;
font-weight: 500;
}
.btn-primary {
background-color: #4285f4;
color: white;
}
.btn-primary:hover {
background-color: #3367d6;
}
.btn-danger {
background-color: #ea4335;
color: white;
}
.btn-danger:hover {
background-color: #d33426;
}
.btn-success {
background-color: #34a853;
color: white;
}
.btn-success:hover {
background-color: #2d9248;
}
.header-actions {
display: flex;
justify-content: space-between;
margin-bottom: 20px;
}
footer {
text-align: center;
margin-top: 40px;
color: #6c757d;
font-size: 14px;
}
/* 模态框样式 */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1000;
justify-content: center;
align-items: center;
height: 100vh;
background-color: #f5f5f5;
}
.modal-content {
.login-box {
background-color: white;
padding: 30px;
border-radius: 8px;
box-shadow: 0 0 15px rgba(0, 0, 0, 0.1);
width: 400px;
padding: 25px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
}
.modal-title {
font-size: 20px;
font-weight: 600;
margin-bottom: 20px;
.login-box h1 {
text-align: center;
margin-bottom: 30px;
color: #2c3e50;
}
.login-footer {
margin-top: 20px;
text-align: center;
font-size: 14px;
color: #666;
}
/* 表单样式 */
.form-group {
margin-bottom: 20px;
}
@ -141,22 +53,92 @@ footer {
display: block;
margin-bottom: 8px;
font-weight: 500;
color: #495057;
}
.form-group input {
width: 94%;
width: 100%;
padding: 10px;
border: 1px solid #ced4da;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 14px;
font-size: 16px;
}
.modal-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 20px;
/* 按钮样式 */
button, .btn {
padding: 10px 15px;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 14px;
font-weight: 500;
transition: background-color 0.3s;
text-decoration: none;
display: inline-block;
}
.btn-primary {
background-color: #3498db;
color: white;
}
.btn-primary:hover {
background-color: #2980b9;
}
.btn-danger {
background-color: #e74c3c;
color: white;
}
.btn-danger:hover {
background-color: #c0392b;
}
.btn-success {
background-color: #2ecc71;
color: white;
}
.btn-success:hover {
background-color: #27ae60;
}
/* 表格样式 */
table {
width: 100%;
border-collapse: collapse;
margin: 20px 0;
}
th, td {
padding: 12px 15px;
text-align: left;
border-bottom: 1px solid #ddd;
}
th {
background-color: #f8f9fa;
font-weight: 600;
}
tr:hover {
background-color: #f5f5f5;
}
.password-cell {
position: relative;
}
.password-placeholder {
letter-spacing: 2px;
}
.btn-show-password {
background: none;
border: none;
cursor: pointer;
padding: 0 5px;
font-size: 16px;
}
/* 开关样式 */
@ -198,9 +180,145 @@ footer {
}
input:checked + .slider {
background-color: #34a853;
background-color: #2ecc71;
}
input:checked + .slider:before {
transform: translateX(24px);
}
/* 模态框样式 */
.modal {
display: none;
position: fixed;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-color: rgba(0, 0, 0, 0.5);
z-index: 1000;
justify-content: center;
align-items: center;
}
.modal-content {
background-color: white;
border-radius: 8px;
width: 400px;
padding: 25px;
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.15);
}
.modal-title {
font-size: 20px;
font-weight: 600;
margin-bottom: 20px;
color: #2c3e50;
}
.modal-actions {
display: flex;
justify-content: flex-end;
gap: 10px;
margin-top: 20px;
}
/* 仪表盘样式 */
.dashboard {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
margin: 30px 0;
}
.dashboard-card {
background-color: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
text-align: center;
}
.dashboard-card h3 {
margin-top: 0;
color: #555;
}
.stat {
font-size: 36px;
font-weight: bold;
margin: 10px 0;
color: #2c3e50;
}
/* 代理使用示例 */
.proxy-usage {
margin-top: 40px;
}
.proxy-usage h2 {
border-bottom: 1px solid #eee;
padding-bottom: 10px;
}
.usage-example {
background-color: #f8f9fa;
border-radius: 6px;
padding: 15px;
margin: 15px 0;
}
.usage-example h3 {
margin-top: 0;
}
code {
background-color: #eee;
padding: 2px 4px;
border-radius: 3px;
font-family: monospace;
}
/* 头部操作按钮 */
.header-actions {
display: flex;
justify-content: space-between;
margin-bottom: 20px;
flex-wrap: wrap;
gap: 10px;
}
/* 设置部分 */
.settings-section {
margin-bottom: 40px;
padding-bottom: 20px;
border-bottom: 1px solid #eee;
}
.settings-section h2 {
margin-top: 0;
}
/* 响应式设计 */
@media (max-width: 768px) {
.container {
padding: 15px;
}
.login-box {
width: 90%;
padding: 20px;
}
table {
font-size: 14px;
}
th, td {
padding: 8px 10px;
}
.header-actions {
flex-direction: column;
}
}

View File

@ -2,34 +2,48 @@
<html>
<head>
<meta charset="UTF-8">
<title>Squid代理用户管理系统</title>
<title>用户管理 - Squid代理管理系统</title>
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
</head>
<body>
<div class="container">
<h1>Squid代理用户管理系统</h1>
<h1>用户管理</h1>
<div class="header-actions">
<button id="createUserBtn" class="btn-primary">+ 添加新用户</button>
<button id="logoutBtn" class="btn-danger">退出系统</button>
<a href="{{ url_for('index') }}" class="btn-primary">返回首页</a>
<a href="{{ url_for('settings') }}" class="btn-primary">系统设置</a>
<button id="createUserBtn" class="btn-success">+ 添加新用户</button>
<a href="{{ url_for('logout') }}" class="btn-danger">退出登录</a>
</div>
<table>
<tr>
<th>用户名</th>
<th>密码</th>
<th>状态</th>
<th>操作</th>
</tr>
{% for user in users %}
<tr>
<td>{{ user.name }}</td>
<td>{{ user['username'] }}</td>
<td class="password-cell">
<span class="password-placeholder">••••••••</span>
<span class="password-value" style="display:none">{{ user['password'] }}</span>
<button class="btn-show-password" data-user-id="{{ user['id'] }}">
<i class="eye-icon">👁️</i>
</button>
</td>
<td>
<div class="actions">
<label class="switch">
<input type="checkbox" {% if user.is_active %}checked{% endif %} data-username="{{ user.name }}">
<input type="checkbox" {% if user['is_active'] %}checked{% endif %}
data-user-id="{{ user['id'] }}">
<span class="slider"></span>
</label>
<button class="btn-primary edit-btn">编辑</button>
<button class="btn-danger delete-btn">删除</button>
</td>
<td>
<div class="actions">
<button class="btn-primary edit-btn" data-user-id="{{ user['id'] }}">编辑</button>
<button class="btn-danger delete-btn" data-user-id="{{ user['id'] }}">删除</button>
</div>
</td>
</tr>
@ -37,7 +51,7 @@
</table>
<footer>
由 Flask 重构 - 基于原项目 <a href="https://github.com/ckazi" target="_blank">ckazi</a>
Squid代理管理系统 &copy; 2023
</footer>
</div>
@ -84,8 +98,14 @@
// 切换用户状态
document.querySelectorAll('input[type="checkbox"]').forEach(sw => {
sw.addEventListener('change', function() {
const username = this.dataset.username;
fetch(`/toggle/${username}`, { method: 'POST' });
const userId = this.dataset.userId;
fetch(`/api/toggle_user/${userId}`, { method: 'POST' })
.then(response => response.json())
.then(data => {
if (!data.success) {
this.checked = !this.checked;
}
});
});
});
@ -93,9 +113,31 @@
document.querySelectorAll('.delete-btn').forEach(btn => {
btn.addEventListener('click', function() {
if(confirm('确定要删除这个用户吗?此操作不可撤销!')) {
const username = this.closest('tr').querySelector('td').textContent;
fetch(`/delete/${username}`, { method: 'POST' })
.then(() => location.reload());
const userId = this.dataset.userId;
fetch(`/api/delete_user/${userId}`, { method: 'POST' })
.then(response => response.json())
.then(data => {
if (data.success) {
location.reload();
}
});
}
});
});
// 显示/隐藏密码
document.querySelectorAll('.btn-show-password').forEach(btn => {
btn.addEventListener('click', function() {
const row = this.closest('tr');
const placeholder = row.querySelector('.password-placeholder');
const password = row.querySelector('.password-value');
if (placeholder.style.display === 'none') {
placeholder.style.display = 'inline';
password.style.display = 'none';
} else {
placeholder.style.display = 'none';
password.style.display = 'inline';
}
});
});
@ -103,16 +145,20 @@
// 编辑用户
document.querySelectorAll('.edit-btn').forEach(btn => {
btn.addEventListener('click', function() {
const username = this.closest('tr').querySelector('td').textContent;
const row = this.closest('tr');
const username = row.querySelector('td').textContent;
const userId = this.dataset.userId;
document.getElementById('editUsername').value = username;
document.getElementById('editPassword').value = '';
document.getElementById('editModal').dataset.userId = userId;
document.getElementById('editModal').style.display = 'flex';
});
});
// 保存编辑
document.getElementById('saveBtn').addEventListener('click', function() {
const username = document.getElementById('editUsername').value;
const userId = document.getElementById('editModal').dataset.userId;
const password = document.getElementById('editPassword').value;
if(!password) {
@ -120,12 +166,17 @@
return;
}
fetch('/save_user', {
fetch('/api/update_user_password', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
}).then(response => {
if(response.ok) location.reload();
body: JSON.stringify({ user_id: userId, password })
}).then(response => response.json())
.then(data => {
if(data.success) {
location.reload();
} else {
alert(data.error || '更新密码失败');
}
});
});
@ -144,15 +195,16 @@
return;
}
fetch('/create_user', {
fetch('/api/create_user', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ username, password })
}).then(response => {
if(response.ok) {
}).then(response => response.json())
.then(data => {
if(data.success) {
location.reload();
} else {
response.json().then(data => alert(data.error || '创建用户失败'));
alert(data.error || '创建用户失败');
}
});
});
@ -166,15 +218,6 @@
document.getElementById('createCloseBtn').addEventListener('click', function() {
document.getElementById('createModal').style.display = 'none';
});
// 退出系统
document.getElementById('logoutBtn').addEventListener('click', function() {
if(confirm('确定要退出系统吗?')) {
fetch('/logout').then(() => {
window.location.href = '/';
});
}
});
});
</script>
</body>

60
templates/index.html Normal file
View File

@ -0,0 +1,60 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Squid代理管理系统 - 首页</title>
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
</head>
<body>
<div class="container">
<h1>Squid代理管理系统</h1>
<div class="header-actions">
<a href="{{ url_for('clients') }}" class="btn-primary">用户管理</a>
<a href="{{ url_for('settings') }}" class="btn-primary">系统设置</a>
<a href="{{ url_for('logout') }}" class="btn-danger">退出登录</a>
</div>
<div class="dashboard">
<div class="dashboard-card">
<h3>用户数量</h3>
<p class="stat">{{ user_count }}</p>
</div>
<div class="dashboard-card">
<h3>代理地址</h3>
<p class="stat">{{ settings['proxy_address'] }}:{{ settings['proxy_port'] }}</p>
</div>
</div>
<div class="proxy-usage">
<h2>代理使用示例</h2>
<div class="usage-example">
<h3>Linux/Mac终端使用代理</h3>
<code>
export http_proxy="http://{{ settings['proxy_address'] }}:{{ settings['proxy_port'] }}"<br>
export https_proxy="http://{{ settings['proxy_address'] }}:{{ settings['proxy_port'] }}"
</code>
</div>
<div class="usage-example">
<h3>Windows CMD使用代理</h3>
<code>
set http_proxy=http://{{ settings['proxy_address'] }}:{{ settings['proxy_port'] }}<br>
set https_proxy=http://{{ settings['proxy_address'] }}:{{ settings['proxy_port'] }}
</code>
</div>
<div class="usage-example">
<h3>浏览器配置代理</h3>
<p>地址: {{ settings['proxy_address'] }}</p>
<p>端口: {{ settings['proxy_port'] }}</p>
</div>
</div>
<footer>
Squid代理管理系统 &copy; 2023
</footer>
</div>
</body>
</html>

42
templates/login.html Normal file
View File

@ -0,0 +1,42 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>登录 - Squid代理管理系统</title>
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
</head>
<body>
<div class="login-container">
<div class="login-box">
<h1>Squid代理管理系统</h1>
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% for category, message in messages %}
<div class="alert alert-{{ category }}">{{ message }}</div>
{% endfor %}
{% endif %}
{% endwith %}
<form method="POST" action="{{ url_for('login') }}">
<div class="form-group">
<label for="username">用户名</label>
<input type="text" id="username" name="username" required>
</div>
<div class="form-group">
<label for="password">密码</label>
<input type="password" id="password" name="password" required>
</div>
<button type="submit" class="btn-primary">登录</button>
</form>
<div class="login-footer">
<p>初始用户名: admin</p>
<p>初始密码: admin123</p>
</div>
</div>
</div>
</body>
</html>

64
templates/settings.html Normal file
View File

@ -0,0 +1,64 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>系统设置 - Squid代理管理系统</title>
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
</head>
<body>
<div class="container">
<h1>系统设置</h1>
<div class="header-actions">
<a href="{{ url_for('index') }}" class="btn-primary">返回首页</a>
<a href="{{ url_for('clients') }}" class="btn-primary">用户管理</a>
<a href="{{ url_for('logout') }}" class="btn-danger">退出登录</a>
</div>
<div class="settings-section">
<h2>修改管理员密码</h2>
<form method="POST" action="{{ url_for('settings') }}">
<input type="hidden" name="action" value="change_password">
<div class="form-group">
<label for="current_password">当前密码</label>
<input type="password" id="current_password" name="current_password" required>
</div>
<div class="form-group">
<label for="new_password">新密码</label>
<input type="password" id="new_password" name="new_password" required>
</div>
<div class="form-group">
<label for="confirm_password">确认新密码</label>
<input type="password" id="confirm_password" name="confirm_password" required>
</div>
<button type="submit" class="btn-success">修改密码</button>
</form>
</div>
<div class="settings-section">
<h2>代理服务器设置</h2>
<form method="POST" action="{{ url_for('settings') }}">
<input type="hidden" name="action" value="update_proxy">
<div class="form-group">
<label for="proxy_address">代理地址</label>
<input type="text" id="proxy_address" name="proxy_address"
value="{{ settings['proxy_address'] }}" required>
</div>
<div class="form-group">
<label for="proxy_port">代理端口</label>
<input type="text" id="proxy_port" name="proxy_port"
value="{{ settings['proxy_port'] }}" required>
</div>
<button type="submit" class="btn-success">保存设置</button>
</form>
</div>
</div>
</body>
</html>