Compare commits
28 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| e4c884e79a | |||
| 7261b2d2a5 | |||
| 72d03723cd | |||
| 056d91b7ad | |||
| f7310541e1 | |||
| dba95410b4 | |||
| 42ca12ef11 | |||
| 49116c6d58 | |||
| cdb2289a64 | |||
| 53d912d632 | |||
| 73b0d11396 | |||
| 49fa5cdcf1 | |||
| 6017e93264 | |||
| 4022dcce1a | |||
| a167c77652 | |||
| 391aadf5eb | |||
| 68efaea7fb | |||
| ea5499af7e | |||
| 83fb4e312e | |||
| 880c8d79a6 | |||
| 1a6438bd55 | |||
| ca79d4488e | |||
| 8c2a0e12a3 | |||
| 3707bcaf34 | |||
| cd7add2377 | |||
| 8e8b25e0ad | |||
| 8300d10074 | |||
| 84e57bc8db |
182
README.md
Normal file
182
README.md
Normal file
@ -0,0 +1,182 @@
|
||||
# Squid 代理管理系统
|
||||

|
||||

|
||||

|
||||

|
||||
|
||||
基于 Flask 的 Squid 代理用户管理界面,提供 Web 界面管理 Squid 代理用户和配置。
|
||||
|
||||
## 功能特性
|
||||
|
||||
- 🛡️ 基于 Basic Auth 的管理员认证
|
||||
- 👥 代理用户管理(增删改查、启用/禁用)
|
||||
- ⚙️ 代理服务器配置管理
|
||||
- 📊 用户统计仪表盘
|
||||
- 🐳 Docker 容器化部署
|
||||
- 📝 配置文件持久化存储
|
||||
|
||||
## 快速部署
|
||||
|
||||
### 前提条件
|
||||
|
||||
- Docker 20.10+
|
||||
- Docker Compose 2.0+
|
||||
- 开放端口 51822 (Squid) 和 51823 (Web UI)
|
||||
|
||||
### 部署步骤
|
||||
|
||||
1. **准备持久化目录**
|
||||
在项目根目录执行:
|
||||
```bash
|
||||
mkdir -p config log
|
||||
chown 31:31 log # Squid 默认使用 squid 用户(UID 31)
|
||||
```
|
||||
|
||||
2. **构建镜像**
|
||||
分别构建两个服务的镜像:
|
||||
```bash
|
||||
# 构建 Web UI 镜像
|
||||
docker build -t squid-ui:latest .
|
||||
|
||||
# 构建 Squid 镜像
|
||||
cd squid && docker build -t squid:latest .
|
||||
cd ..
|
||||
```
|
||||
|
||||
3. **初始化配置文件**
|
||||
首次运行前需要准备基础配置:
|
||||
```bash
|
||||
touch config/squid_passwd
|
||||
cat > config/config.json <<EOF
|
||||
{
|
||||
"admin_password": "admin123",
|
||||
"proxy_address": "0.0.0.0",
|
||||
"proxy_port": "3128"
|
||||
}
|
||||
EOF
|
||||
```
|
||||
|
||||
4. **启动服务**
|
||||
通过 compose 启动服务:
|
||||
```bash
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
5. **验证服务状态**
|
||||
```bash
|
||||
docker-compose ps
|
||||
```
|
||||
应该看到两个服务状态均为 `running`
|
||||
|
||||
### 访问管理界面
|
||||
|
||||
```
|
||||
http://your-server-ip:51823
|
||||
```
|
||||
默认管理员凭证:
|
||||
- 用户名: `admin`
|
||||
- 密码: `admin123`
|
||||
|
||||
### 配置代理客户端
|
||||
|
||||
```
|
||||
代理地址: your-server-ip
|
||||
端口: 51822
|
||||
认证方式: 用户名/密码
|
||||
```
|
||||
|
||||
## 持久化存储说明
|
||||
|
||||
| 目录/文件 | 用途 | 权限要求 |
|
||||
|-----------|------|----------|
|
||||
| `./config` | 存储所有配置文件 | 默认 |
|
||||
| `./config/squid_passwd` | 用户认证文件 | 容器内可读写 |
|
||||
| `./log` | Squid 日志目录 | 必须设置为 31:31 (squid用户) |
|
||||
|
||||
## 常见问题
|
||||
|
||||
### 权限问题排查
|
||||
|
||||
如果 Squid 容器启动失败,检查日志目录权限:
|
||||
```bash
|
||||
ls -ld log
|
||||
```
|
||||
正确输出应该类似:
|
||||
```
|
||||
drwxr-xr-x 2 31 31 4096 Jan 1 00:00 log
|
||||
```
|
||||
|
||||
修复命令:
|
||||
```bash
|
||||
chown -R 31:31 log
|
||||
```
|
||||
|
||||
### 重新构建镜像
|
||||
|
||||
当代码更新后需要重新构建:
|
||||
```bash
|
||||
docker-compose down
|
||||
docker build -t squid-ui:latest .
|
||||
cd squid && docker build -t squid:latest .
|
||||
cd ..
|
||||
docker-compose up -d
|
||||
```
|
||||
|
||||
## 升级指南
|
||||
|
||||
1. 停止服务:
|
||||
```bash
|
||||
docker-compose down
|
||||
```
|
||||
|
||||
2. 备份配置:
|
||||
```bash
|
||||
cp -r config config_backup_$(date +%Y%m%d)
|
||||
```
|
||||
|
||||
3. 按上述步骤重新构建和启动
|
||||
|
||||
## 管理指南
|
||||
|
||||
### 添加新用户
|
||||
|
||||
1. 登录管理界面
|
||||
2. 导航到 "客户端管理"
|
||||
3. 点击 "添加用户"
|
||||
4. 输入用户名和密码
|
||||
5. 点击保存
|
||||
|
||||
### 修改代理设置
|
||||
|
||||
1. 登录管理界面
|
||||
2. 导航到 "系统设置"
|
||||
3. 修改以下参数:
|
||||
- 管理员密码
|
||||
- 代理服务器地址
|
||||
- 代理服务器端口
|
||||
4. 点击 "保存设置"
|
||||
|
||||
### 查看日志
|
||||
|
||||
Squid 日志位于主机上的 `./log` 目录:
|
||||
|
||||
```bash
|
||||
tail -f ./log/access.log
|
||||
```
|
||||
|
||||
## 常见问题
|
||||
|
||||
**Q: 为什么我的代理连接被拒绝?**
|
||||
A: 检查:
|
||||
- Squid 容器是否正常运行 (`docker ps`)
|
||||
- 用户是否已激活
|
||||
- 防火墙是否允许 51822 端口
|
||||
|
||||
**Q: 如何重置管理员密码?**
|
||||
A: 编辑 `config/config.json` 文件中的 `admin_password` 字段
|
||||
|
||||
## 许可证
|
||||
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 Jeazw
|
||||
197
app.py
197
app.py
@ -1,28 +1,58 @@
|
||||
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
|
||||
from functools import wraps
|
||||
import os
|
||||
import subprocess
|
||||
import base64
|
||||
import json
|
||||
import uuid
|
||||
from math import ceil
|
||||
from flask import flash
|
||||
|
||||
app = Flask(__name__)
|
||||
app.secret_key = str(uuid.uuid4())
|
||||
|
||||
# 配置
|
||||
# 配置文件路径
|
||||
CONFIG_FILE = 'config/config.json'
|
||||
SQUID_PASSWD_FILE = 'config/squid_passwd'
|
||||
ADMIN_PASSWORD = os.getenv('SQUID_PASSWORD', 'admin123')
|
||||
USERS_FILE = 'config/users.json' # 新增用户信息存储文件
|
||||
|
||||
|
||||
# 加载配置
|
||||
def load_config():
|
||||
try:
|
||||
with open(CONFIG_FILE, 'r') as f:
|
||||
return json.load(f)
|
||||
except (FileNotFoundError, json.JSONDecodeError):
|
||||
# 默认配置
|
||||
default_config = {
|
||||
"admin_password": "admin123",
|
||||
"proxy_address": "127.0.0.1",
|
||||
"proxy_port": "3128"
|
||||
}
|
||||
os.makedirs('config', exist_ok=True)
|
||||
with open(CONFIG_FILE, 'w') as f:
|
||||
json.dump(default_config, f, indent=4)
|
||||
return default_config
|
||||
|
||||
|
||||
# 保存配置
|
||||
def save_config(config):
|
||||
with open(CONFIG_FILE, 'w') as f:
|
||||
json.dump(config, f, indent=4)
|
||||
|
||||
|
||||
class User:
|
||||
def __init__(self, name, password, is_active=True):
|
||||
self.name = name
|
||||
self.password = password
|
||||
self.password = password # 这里存储明文密码
|
||||
self.is_active = is_active
|
||||
|
||||
|
||||
def basic_auth_required(f):
|
||||
@wraps(f)
|
||||
def decorated(*args, **kwargs):
|
||||
config = load_config()
|
||||
auth = request.authorization
|
||||
if not auth or not (auth.username == 'admin' and auth.password == ADMIN_PASSWORD):
|
||||
if not auth or not (auth.username == 'admin' and auth.password == config['admin_password']):
|
||||
return ('Unauthorized', 401,
|
||||
{'WWW-Authenticate': 'Basic realm="Authorization Required"'})
|
||||
return f(*args, **kwargs)
|
||||
@ -45,17 +75,69 @@ def read_squid_file():
|
||||
|
||||
parts = line.split(':', 1)
|
||||
if len(parts) == 2:
|
||||
users.append(User(parts[0], parts[1], is_active))
|
||||
# 从users.json中获取明文密码
|
||||
users_data = load_users_data()
|
||||
plain_password = next((u['password'] for u in users_data if u['username'] == parts[0]), None)
|
||||
users.append(User(parts[0], plain_password, 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)
|
||||
"""只更新squid_passwd文件的状态(是否注释),不修改密码内容"""
|
||||
try:
|
||||
# 读取现有加密密码
|
||||
with open(SQUID_PASSWD_FILE, 'r') as f:
|
||||
existing_lines = f.readlines()
|
||||
|
||||
# 创建用户名到密码的映射
|
||||
existing_passwords = {}
|
||||
for line in existing_lines:
|
||||
line = line.strip()
|
||||
if not line:
|
||||
continue
|
||||
active = not line.startswith('#')
|
||||
if not active:
|
||||
line = line[1:]
|
||||
parts = line.split(':', 1)
|
||||
if len(parts) == 2:
|
||||
existing_passwords[parts[0]] = parts[1]
|
||||
|
||||
# 写入新文件,保持加密密码不变
|
||||
with open(SQUID_PASSWD_FILE, 'w') as f:
|
||||
for user in users:
|
||||
encrypted_password = existing_passwords.get(user.name, user.password)
|
||||
line = f"{'#' if not user.is_active else ''}{user.name}:{encrypted_password}\n"
|
||||
f.write(line)
|
||||
except FileNotFoundError:
|
||||
# 如果文件不存在,创建新文件
|
||||
with open(SQUID_PASSWD_FILE, 'w') as f:
|
||||
for user in users:
|
||||
# 首次创建时需要使用htpasswd命令生成加密密码
|
||||
subprocess.run(['htpasswd', '-b', SQUID_PASSWD_FILE, user.name, user.password], check=True)
|
||||
# 如果不是活跃用户,需要添加注释
|
||||
if not user.is_active:
|
||||
with open(SQUID_PASSWD_FILE, 'r') as f_read:
|
||||
content = f_read.read()
|
||||
with open(SQUID_PASSWD_FILE, 'w') as f_write:
|
||||
f_write.write(f"#{content}")
|
||||
|
||||
|
||||
def load_users_data():
|
||||
"""加载用户明文数据"""
|
||||
try:
|
||||
with open(USERS_FILE, 'r') as f:
|
||||
return json.load(f)
|
||||
except (FileNotFoundError, json.JSONDecodeError):
|
||||
return []
|
||||
|
||||
|
||||
def save_users_to_json(users):
|
||||
"""保存用户明文信息到JSON文件"""
|
||||
users_data = [{'username': u.name, 'password': u.password, 'active': u.is_active} for u in users]
|
||||
with open(USERS_FILE, 'w') as f:
|
||||
json.dump(users_data, f, indent=4)
|
||||
|
||||
|
||||
def create_user(username, password):
|
||||
@ -64,6 +146,11 @@ def create_user(username, password):
|
||||
return False
|
||||
|
||||
try:
|
||||
# 先保存明文密码到users.json
|
||||
users.append(User(username, password, True))
|
||||
save_users_to_json(users)
|
||||
|
||||
# 然后创建加密密码
|
||||
subprocess.run(['htpasswd', '-b', SQUID_PASSWD_FILE, username, password], check=True)
|
||||
return True
|
||||
except subprocess.CalledProcessError:
|
||||
@ -73,8 +160,62 @@ def create_user(username, password):
|
||||
@app.route('/')
|
||||
@basic_auth_required
|
||||
def index():
|
||||
config = load_config()
|
||||
users = read_squid_file()
|
||||
return render_template('clients.html', users=users)
|
||||
return render_template('index.html',
|
||||
user_count=len(users),
|
||||
proxy_address=config['proxy_address'],
|
||||
proxy_port=config['proxy_port'])
|
||||
|
||||
|
||||
@app.route('/clients')
|
||||
@basic_auth_required
|
||||
def clients():
|
||||
page = request.args.get('page', 1, type=int)
|
||||
per_page = 5
|
||||
|
||||
users = read_squid_file()
|
||||
total_pages = ceil(len(users) / per_page)
|
||||
|
||||
start = (page - 1) * per_page
|
||||
end = start + per_page
|
||||
paginated_users = users[start:end]
|
||||
|
||||
config = load_config()
|
||||
|
||||
return render_template('clients.html',
|
||||
users=paginated_users,
|
||||
page=page,
|
||||
total_pages=total_pages,
|
||||
proxy_address=config['proxy_address'],
|
||||
proxy_port=config['proxy_port'])
|
||||
|
||||
|
||||
@app.route('/settings')
|
||||
@basic_auth_required
|
||||
def settings():
|
||||
config = load_config()
|
||||
return render_template('settings.html', config=config)
|
||||
|
||||
|
||||
@app.route('/update_settings', methods=['POST'])
|
||||
@basic_auth_required
|
||||
def update_settings():
|
||||
config = load_config()
|
||||
new_password = request.form.get('admin_password')
|
||||
proxy_address = request.form.get('proxy_address')
|
||||
proxy_port = request.form.get('proxy_port')
|
||||
|
||||
if new_password:
|
||||
config['admin_password'] = new_password
|
||||
if proxy_address:
|
||||
config['proxy_address'] = proxy_address
|
||||
if proxy_port:
|
||||
config['proxy_port'] = proxy_port
|
||||
|
||||
save_config(config)
|
||||
flash('设置已成功保存!', 'success')
|
||||
return redirect(url_for('settings'))
|
||||
|
||||
|
||||
@app.route('/toggle/<username>', methods=['POST'])
|
||||
@ -85,8 +226,8 @@ def toggle_user(username):
|
||||
if user.name == username:
|
||||
user.is_active = not user.is_active
|
||||
break
|
||||
|
||||
write_squid_file(users)
|
||||
save_users_to_json(users) # 更新users.json
|
||||
return '', 200
|
||||
|
||||
|
||||
@ -95,6 +236,7 @@ def toggle_user(username):
|
||||
def delete_user(username):
|
||||
users = [u for u in read_squid_file() if u.name != username]
|
||||
write_squid_file(users)
|
||||
save_users_to_json(users) # 更新users.json
|
||||
return '', 200
|
||||
|
||||
|
||||
@ -109,6 +251,15 @@ def save_user():
|
||||
return jsonify({'error': 'Username and password required'}), 400
|
||||
|
||||
try:
|
||||
# 更新明文密码
|
||||
users = read_squid_file()
|
||||
for user in users:
|
||||
if user.name == username:
|
||||
user.password = password
|
||||
break
|
||||
save_users_to_json(users)
|
||||
|
||||
# 更新加密密码
|
||||
subprocess.run(['htpasswd', '-b', SQUID_PASSWD_FILE, username, password], check=True)
|
||||
return '', 200
|
||||
except subprocess.CalledProcessError:
|
||||
@ -131,6 +282,23 @@ def handle_create_user():
|
||||
return jsonify({'error': 'User already exists'}), 409
|
||||
|
||||
|
||||
@app.route('/copy_proxy/<username>', methods=['POST'])
|
||||
@basic_auth_required
|
||||
def copy_proxy(username):
|
||||
config = load_config()
|
||||
users = read_squid_file()
|
||||
user = next((u for u in users if u.name == username), None)
|
||||
|
||||
if not user:
|
||||
return jsonify({'error': 'User not found'}), 404
|
||||
|
||||
proxy_url = f"http://{user.name}:{user.password}@{config['proxy_address']}:{config['proxy_port']}"
|
||||
return jsonify({
|
||||
'message': 'Proxy URL copied to clipboard (simulated)',
|
||||
'proxy_url': proxy_url
|
||||
})
|
||||
|
||||
|
||||
@app.route('/logout')
|
||||
def logout():
|
||||
return ('', 401, {'WWW-Authenticate': 'Basic realm="Authorization Required"'})
|
||||
@ -139,4 +307,5 @@ def logout():
|
||||
if __name__ == '__main__':
|
||||
if not os.path.exists('config'):
|
||||
os.makedirs('config')
|
||||
app.run(host='0.0.0.0', port=8080, debug=True)
|
||||
load_config() # 初始化配置文件
|
||||
app.run(host='0.0.0.0', port=8080, debug=True)
|
||||
|
||||
5
config/config.json
Normal file
5
config/config.json
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"admin_password": "admin123",
|
||||
"proxy_address": "127.0.0.1",
|
||||
"proxy_port": "3128"
|
||||
}
|
||||
23
config/squid.conf
Normal file → Executable file
23
config/squid.conf
Normal file → Executable file
@ -5,7 +5,24 @@ auth_param basic realm Squid proxy-caching web server
|
||||
auth_param basic credentialsttl 2 hours
|
||||
acl auth_users proxy_auth REQUIRED
|
||||
http_access allow auth_users
|
||||
http_access deny all
|
||||
|
||||
# 缓存配置
|
||||
cache_dir ufs /var/cache/squid 100 16 256
|
||||
cache_mem 256 MB
|
||||
maximum_object_size 50 MB
|
||||
|
||||
# 日志与PID
|
||||
pid_filename /tmp/squid.pid
|
||||
cache_log /dev/null
|
||||
access_log none
|
||||
cache_store_log none
|
||||
cache_log /var/log/squid/cache.log
|
||||
cache_store_log /var/log/squid/store.log
|
||||
|
||||
# 日志轮转设置
|
||||
logfile_rotate 10
|
||||
|
||||
# 自定义日志格式(年月日时分秒)
|
||||
logformat custom_format %{%Y/%m/%d %H:%M:%S}tl.%03tu %6tr %>a %Ss/%03Hs %<st %rm %ru %[un] %Sh/%<A %mt
|
||||
access_log /var/log/squid/access.log custom_format
|
||||
|
||||
# 强制使用英语错误页面(可选)
|
||||
error_directory /usr/share/squid/errors/en
|
||||
|
||||
2
config/squid_passwd
Normal file → Executable file
2
config/squid_passwd
Normal file → Executable file
@ -1 +1 @@
|
||||
user:$apr1$KhRI8wrZ$SafuEp09ZGnmWYXm4dQvW.
|
||||
user:$apr1$4TQJji47$70jLwdaewHbiP0SRWYBE.1
|
||||
|
||||
7
config/users.json
Normal file
7
config/users.json
Normal file
@ -0,0 +1,7 @@
|
||||
[
|
||||
{
|
||||
"username": "user",
|
||||
"password": "112233",
|
||||
"active": true
|
||||
}
|
||||
]
|
||||
@ -6,22 +6,19 @@ services:
|
||||
container_name: squid-ui
|
||||
ports:
|
||||
- "51823:8080"
|
||||
environment:
|
||||
- SQUID_PASSWORD=Sqd123 # 建议通过环境变量文件或Docker secrets管理密码
|
||||
volumes:
|
||||
- ./config/squid_passwd:/app/config/squid_passwd:rw
|
||||
# 如果模板文件需要动态修改,可以挂载模板目录
|
||||
# - ./templates:/app/templates
|
||||
- ./config:/app/config:rw
|
||||
restart: always
|
||||
|
||||
squid:
|
||||
image: ckazi/squid
|
||||
image: squid
|
||||
container_name: squid
|
||||
ports:
|
||||
- "51822:3128"
|
||||
volumes:
|
||||
- ./config/squid.conf:/etc/squid/squid.conf:ro
|
||||
- ./config/squid_passwd:/etc/squid/squid_passwd:ro
|
||||
- ./log:/var/log/squid:rw
|
||||
depends_on:
|
||||
- squid-ui
|
||||
restart: always
|
||||
restart: always
|
||||
|
||||
@ -1 +1,2 @@
|
||||
Flask==2.3.2
|
||||
Flask==2.3.2
|
||||
pyperclip
|
||||
|
||||
24
squid/Dockerfile
Normal file
24
squid/Dockerfile
Normal file
@ -0,0 +1,24 @@
|
||||
FROM alpine:latest
|
||||
|
||||
# 安装 Squid
|
||||
RUN apk add --no-cache squid
|
||||
|
||||
# 创建必要的目录并设置权限
|
||||
RUN mkdir -p \
|
||||
/var/cache/squid \
|
||||
/var/log/squid \
|
||||
/var/run \
|
||||
&& chown -R squid:squid /var/cache/squid \
|
||||
&& chown -R squid:squid /var/log/squid
|
||||
|
||||
# 复制配置文件
|
||||
COPY squid.conf /etc/squid/squid.conf
|
||||
COPY start-squid.sh /usr/local/bin/start-squid.sh
|
||||
|
||||
RUN chmod +x /usr/local/bin/start-squid.sh
|
||||
|
||||
USER squid
|
||||
|
||||
EXPOSE 3128
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/start-squid.sh"]
|
||||
28
squid/squid.conf
Executable file
28
squid/squid.conf
Executable file
@ -0,0 +1,28 @@
|
||||
http_port 3128
|
||||
auth_param basic program /usr/lib/squid/basic_ncsa_auth /etc/squid/squid_passwd
|
||||
auth_param basic children 5
|
||||
auth_param basic realm Squid proxy-caching web server
|
||||
auth_param basic credentialsttl 2 hours
|
||||
acl auth_users proxy_auth REQUIRED
|
||||
http_access allow auth_users
|
||||
http_access deny all
|
||||
|
||||
# 缓存配置
|
||||
cache_dir ufs /var/cache/squid 100 16 256
|
||||
cache_mem 256 MB
|
||||
maximum_object_size 50 MB
|
||||
|
||||
# 日志与PID
|
||||
pid_filename /tmp/squid.pid
|
||||
cache_log /var/log/squid/cache.log
|
||||
cache_store_log /var/log/squid/store.log
|
||||
|
||||
# 日志轮转设置
|
||||
logfile_rotate 10
|
||||
|
||||
# 自定义日志格式(年月日时分秒)
|
||||
logformat custom_format %{%Y/%m/%d %H:%M:%S}tl.%03tu %6tr %>a %Ss/%03Hs %<st %rm %ru %[un] %Sh/%<A %mt
|
||||
access_log /var/log/squid/access.log custom_format
|
||||
|
||||
# 强制使用英语错误页面(可选)
|
||||
error_directory /usr/share/squid/errors/en
|
||||
21
squid/start-squid.sh
Normal file
21
squid/start-squid.sh
Normal file
@ -0,0 +1,21 @@
|
||||
#!/bin/sh
|
||||
|
||||
set -e
|
||||
|
||||
SQUID=$(/usr/bin/which squid)
|
||||
|
||||
initialize_cache() {
|
||||
echo "Initializing cache..."
|
||||
if [ ! -d "/var/cache/squid/00" ]; then
|
||||
"$SQUID" -z
|
||||
sleep 5
|
||||
fi
|
||||
}
|
||||
|
||||
run() {
|
||||
echo "Starting squid..."
|
||||
initialize_cache
|
||||
exec "$SQUID" -NYCd 1 -f /etc/squid/squid.conf
|
||||
}
|
||||
|
||||
run
|
||||
@ -1,3 +1,40 @@
|
||||
/*s tyle.css */
|
||||
/* 导航栏样式 */
|
||||
.main-nav {
|
||||
display: flex;
|
||||
background-color: #2c3e50;
|
||||
border-radius: 8px;
|
||||
padding: 0 20px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.nav-link {
|
||||
color: white;
|
||||
text-decoration: none;
|
||||
padding: 15px 20px;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.nav-link:hover {
|
||||
background-color: #34495e;
|
||||
}
|
||||
|
||||
.nav-link.active {
|
||||
background-color: #4285f4;
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
.nav-logout {
|
||||
margin-left: auto;
|
||||
align-self: center;
|
||||
padding: 8px 16px;
|
||||
}
|
||||
|
||||
/* 内容区域 */
|
||||
.content {
|
||||
min-height: 60vh;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: 'Microsoft YaHei', 'PingFang SC', sans-serif;
|
||||
max-width: 900px;
|
||||
@ -203,4 +240,247 @@ input:checked + .slider {
|
||||
|
||||
input:checked + .slider:before {
|
||||
transform: translateX(24px);
|
||||
}
|
||||
}
|
||||
|
||||
/* 在现有CSS文件末尾添加以下内容 */
|
||||
|
||||
/* 首页专用样式 */
|
||||
.dashboard {
|
||||
display: flex;
|
||||
justify-content: space-around;
|
||||
gap: 20px;
|
||||
margin: 20px 0;
|
||||
}
|
||||
.home-header h1 {
|
||||
font-size: 24px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
.dashboard-card {
|
||||
flex: 1;
|
||||
background: linear-gradient(135deg, #4285f4, #34a853);
|
||||
color: white;
|
||||
border-radius: 10px;
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
text-align: center;
|
||||
transition: transform 0.3s, box-shadow 0.3s;
|
||||
padding: 5px;
|
||||
}
|
||||
|
||||
.dashboard-card:hover {
|
||||
transform: translateY(-5px);
|
||||
box-shadow: 0 6px 16px rgba(0, 0, 0, 0.15);
|
||||
}
|
||||
|
||||
.dashboard-card h3 {
|
||||
margin-top: 0;
|
||||
font-size: 18px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.stat {
|
||||
font-size: 24px;
|
||||
margin: 5px 0 0;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.proxy-info {
|
||||
background-color: #f8f9fa;
|
||||
border-radius: 10px;
|
||||
padding: 10px;
|
||||
margin: 20px 0;
|
||||
border-left: 4px solid #4285f4;
|
||||
}
|
||||
|
||||
.proxy-info h2 {
|
||||
margin-top: 0;
|
||||
color: #2c3e50;
|
||||
font-size: 18px;
|
||||
}
|
||||
|
||||
.proxy-info p {
|
||||
margin: 15px 0;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.proxy-info code {
|
||||
background-color: #e9ecef;
|
||||
padding: 2px 6px;
|
||||
border-radius: 4px;
|
||||
font-family: monospace;
|
||||
color: #d63384;
|
||||
}
|
||||
|
||||
/* 响应式设计 */
|
||||
@media (max-width: 768px) {
|
||||
.dashboard {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.dashboard-card {
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
}
|
||||
|
||||
/* 添加以下新样式 */
|
||||
|
||||
.home-header {
|
||||
text-align: center;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
|
||||
.home-header .subtitle {
|
||||
color: #6c757d;
|
||||
font-size: 18px;
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.card-icon {
|
||||
font-size: 40px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.card-desc {
|
||||
font-size: 14px;
|
||||
opacity: 0.9;
|
||||
margin-top: 5px;
|
||||
}
|
||||
|
||||
.info-steps {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
|
||||
.step {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
gap: 15px;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
|
||||
.step-number {
|
||||
background-color: #4285f4;
|
||||
color: white;
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
font-size: 14px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.quick-actions {
|
||||
display: flex;
|
||||
gap: 20px;
|
||||
margin-top: 30px;
|
||||
}
|
||||
|
||||
.action-card {
|
||||
flex: 1;
|
||||
background-color: white;
|
||||
border-radius: 10px;
|
||||
padding: 25px;
|
||||
text-align: center;
|
||||
text-decoration: none;
|
||||
color: #333;
|
||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.05);
|
||||
transition: transform 0.3s, box-shadow 0.3s;
|
||||
}
|
||||
|
||||
.action-card:hover {
|
||||
transform: translateY(-3px);
|
||||
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
|
||||
}
|
||||
|
||||
.action-icon {
|
||||
font-size: 36px;
|
||||
margin-bottom: 15px;
|
||||
}
|
||||
|
||||
.action-card h3 {
|
||||
margin: 0 0 10px;
|
||||
color: #2c3e50;
|
||||
}
|
||||
|
||||
.action-card p {
|
||||
margin: 0;
|
||||
color: #6c757d;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
/* 设置页按钮样式调整 */
|
||||
.settings-form .btn-danger {
|
||||
padding: 8px 16px;
|
||||
border: none;
|
||||
border-radius: 6px;
|
||||
cursor: pointer;
|
||||
transition: all 0.3s;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.settings-form .btn-danger:hover {
|
||||
background-color: #d33426;
|
||||
}
|
||||
|
||||
/* 添加消息提示样式 */
|
||||
.alert {
|
||||
padding: 12px 20px;
|
||||
border-radius: 6px;
|
||||
margin-bottom: 20px;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.alert-success {
|
||||
background-color: #d4edda;
|
||||
color: #155724;
|
||||
border: 1px solid #c3e6cb;
|
||||
}
|
||||
|
||||
/* 分页样式 */
|
||||
.pagination {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
margin: 20px 0;
|
||||
gap: 5px;
|
||||
position: relative; /* 改为相对定位 */
|
||||
bottom: auto;
|
||||
}
|
||||
|
||||
/* 固定分页区域位置 */
|
||||
.table-container {
|
||||
min-height: calc(100vh - 400px); /* 动态计算最小高度 */
|
||||
margin-bottom: 60px; /* 为分页留出空间 */
|
||||
}
|
||||
|
||||
.pagination a, .pagination span {
|
||||
padding: 8px 12px;
|
||||
border: 1px solid #ddd;
|
||||
text-decoration: none;
|
||||
color: #4285f4;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.pagination a:hover {
|
||||
background-color: #f1f1f1;
|
||||
}
|
||||
|
||||
.pagination .current {
|
||||
background-color: #4285f4;
|
||||
color: white;
|
||||
border-color: #4285f4;
|
||||
}
|
||||
|
||||
/* 响应式调整 */
|
||||
@media (max-width: 768px) {
|
||||
.quick-actions {
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.step {
|
||||
flex-direction: column;
|
||||
gap: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
46
templates/base.html
Normal file
46
templates/base.html
Normal file
@ -0,0 +1,46 @@
|
||||
<!-- templates/base.html -->
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>{% block title %}Squid代理用户管理系统{% endblock %}</title>
|
||||
<link rel="icon" href="data:image/svg+xml,<svg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'><path fill='black' d='M12.008 0A822.933 822.933 0 0 0 1.59 6.043V18c3.578 2.087 7.238 4.274 10.418 6 3.928-2.267 6.71-3.868 10.402-6v-3.043l-1.045.6v1.8l-1.545.9-1.56-.9v-1.8l1.56-.885v-.002l.268.156a8.15 8.15 0 0 0 .404-1.754v-.002a8.72 8.72 0 0 0 .072-1.072 9.885 9.885 0 0 0-.072-1.127 8.873 8.873 0 0 0-.515-1.97v-.003a8.137 8.137 0 0 0-1.301-2.242 7.113 7.113 0 0 0-.615-.699 10.271 10.271 0 0 0-.846-.728 7.91 7.91 0 0 0-1.902-1.116 4.776 4.776 0 0 0-.586-.213v-.957c.41.118.812.265 1.2.442a9.2 9.2 0 0 1 1.618.943 9.4 9.4 0 0 1 1.158.986c.273.277.53.568.774.872.532.686.97 1.44 1.302 2.244h-.002a9.45 9.45 0 0 1 .645 2.613c.04.317.06.637.056.957 0 .314-.014.614-.043.914-.082.838-.37 1.786-.542 2.373l.472.27 1.045-.602V8.986l-1.303-.742v-.002l1.303.744V6c-3.56-2.057-7.212-4.154-10.402-6Zm8.08 14.826c-.02.052.003.002.004.002zM12.035 1.213l1.56.9v1.801l-1.56.885-1.545-.885h-.002v-.328a8.458 8.458 0 0 0-1.744.516 8.178 8.178 0 0 0-1.889 1.07l-.001.002a6.77 6.77 0 0 0-.9.783 9.171 9.171 0 0 0-.616.672 8.84 8.84 0 0 0-1.3 2.228l.228.127 1.287-.742 1.203.686c1.929-1.112 3.397-1.961 5.252-3.014l.027.014c1.926 1.114 3.398 1.955 5.238 3.029.028 1.997.014 4.064.014 6.086-1.874 1.084-3.753 2.16-5.28 3.043a859.719 859.719 0 0 1-5.294-3.043V8.957l.043-.027-1.203-.688-1.287.744-.229-.129h-.002a8.376 8.376 0 0 0-.53 2.057c-.044.36-.068.723-.07 1.086.002.344.026.687.07 1.027v.002c.015.215.06.429.102.643l-.83.484a7.017 7.017 0 0 1-.2-1.199A7.065 7.065 0 0 1 2.52 12c0-.329.028-.672.056-1a9.77 9.77 0 0 1 .658-2.6 9.438 9.438 0 0 1 1.303-2.244c.243-.3.5-.57.758-.842.37-.372.773-.71 1.203-1.013-1.215-.084-1.215-.084 0-.002a9.394 9.394 0 0 1 1.645-.942 9.866 9.866 0 0 1 2.347-.7l-.002-.542-1.043-.601 1.045.6zm0 .773-.887.514v1.027l.887.516.887-.516V2.5Zm-.03 6.928c-.935.532-1.888 1.084-2.689 1.543v3.086c.933.535 1.892 1.095 2.692 1.557.926-.565 1.865-1.093 2.676-1.557v-3.086c-.945-.542-1.857-1.074-2.678-1.543Zm-7.74 5.758 1.546.885v1.8l-.329.186c.146.177.303.344.471.5.277.288.58.55.902.785a8.07 8.07 0 0 0 1.83 1.059h.002a8.14 8.14 0 0 0 2.061.57c.417.058.837.087 1.258.086a8.37 8.37 0 0 0 1.332-.1 8.64 8.64 0 0 0 2.017-.572 8.076 8.076 0 0 0 1.86-1.1c.172-.114.315-.242.472-.37l.83.47a9.79 9.79 0 0 1-.945.787l.946.541 1.302-.756-1.302.758-.946-.543c-.516.37-1.067.69-1.644.955l-.002.002a9.502 9.502 0 0 1-2.588.756c-.441.057-.885.086-1.33.086a12.048 12.048 0 0 1-1.26-.072v.002a9.38 9.38 0 0 1-2.605-.744 9.044 9.044 0 0 1-1.688-.971 9.625 9.625 0 0 1-1.775-1.658h-.002l-.412.244-1.56-.9v-1.801zm0 .756-.886.515v1.028l.887.515.886-.515v-1.028zm15.555 0-.902.515v1.028l.902.515.887-.515v-1.028z'/></svg>">
|
||||
<link rel="stylesheet" href="{{ url_for('static', filename='styles.css') }}">
|
||||
</head>
|
||||
<body>
|
||||
<div class="container">
|
||||
<!-- 统一的导航栏 -->
|
||||
<nav class="main-nav">
|
||||
<a href="{{ url_for('index') }}" class="nav-link {% if request.endpoint == 'index' %}active{% endif %}">首页</a>
|
||||
<a href="{{ url_for('clients') }}" class="nav-link {% if request.endpoint == 'clients' %}active{% endif %}">用户管理</a>
|
||||
<a href="{{ url_for('settings') }}" class="nav-link {% if request.endpoint == 'settings' %}active{% endif %}">系统设置</a>
|
||||
<button id="logoutBtn" class="btn-danger nav-logout">退出系统</button>
|
||||
</nav>
|
||||
|
||||
<!-- 内容区块 -->
|
||||
<div class="content">
|
||||
{% block content %}{% endblock %}
|
||||
</div>
|
||||
|
||||
</div>
|
||||
|
||||
<!-- 统一的页脚 -->
|
||||
<footer>
|
||||
由 Flask 重构 - 基于原项目 <a href="https://github.com/ckazi" target="_blank">ckazi</a>
|
||||
</footer>
|
||||
<!-- 统一的JavaScript -->
|
||||
<script>
|
||||
// 退出系统功能
|
||||
document.getElementById('logoutBtn')?.addEventListener('click', function() {
|
||||
if(confirm('确定要退出系统吗?')) {
|
||||
fetch('/logout').then(() => {
|
||||
window.location.href = '/';
|
||||
});
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<!-- 子模板可以添加自己的JavaScript -->
|
||||
{% block scripts %}{% endblock %}
|
||||
</body>
|
||||
</html>
|
||||
@ -1,44 +1,59 @@
|
||||
<!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>
|
||||
<!-- templates/clients.html -->
|
||||
{% extends "base.html" %}
|
||||
|
||||
<div class="header-actions">
|
||||
<button id="createUserBtn" class="btn-primary">+ 添加新用户</button>
|
||||
<button id="logoutBtn" class="btn-danger">退出系统</button>
|
||||
</div>
|
||||
{% block title %}用户管理 - Squid代理用户管理系统{% endblock %}
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>用户名</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
{% for user in users %}
|
||||
<tr>
|
||||
<td>{{ user.name }}</td>
|
||||
<td>
|
||||
<div class="actions">
|
||||
<label class="switch">
|
||||
<input type="checkbox" {% if user.is_active %}checked{% endif %} data-username="{{ user.name }}">
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
<button class="btn-primary edit-btn">编辑</button>
|
||||
<button class="btn-danger delete-btn">删除</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% block content %}
|
||||
<h1>用户管理</h1>
|
||||
|
||||
<footer>
|
||||
由 Flask 重构 - 基于原项目 <a href="https://github.com/ckazi" target="_blank">ckazi</a>
|
||||
</footer>
|
||||
<div class="header-actions">
|
||||
<button id="createUserBtn" class="btn-primary">+ 添加新用户</button>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<tr>
|
||||
<th>用户名</th>
|
||||
<th>状态</th>
|
||||
<th>操作</th>
|
||||
</tr>
|
||||
{% for user in users %}
|
||||
<tr>
|
||||
<td>{{ user.name }}</td>
|
||||
<td>{{ "启用" if user.is_active else "禁用" }}</td>
|
||||
<td>
|
||||
<div class="actions">
|
||||
<label class="switch">
|
||||
<input type="checkbox" {% if user.is_active %}checked{% endif %} data-username="{{ user.name }}">
|
||||
<span class="slider"></span>
|
||||
</label>
|
||||
<button class="btn-primary edit-btn">编辑</button>
|
||||
<button class="btn-success copy-btn" data-username="{{ user.name }}">
|
||||
<i class="icon-copy"></i> 复制代理
|
||||
</button>
|
||||
<button class="btn-danger delete-btn">删除</button>
|
||||
</div>
|
||||
</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
|
||||
<!-- 分页导航 -->
|
||||
<div class="pagination">
|
||||
{% if page > 1 %}
|
||||
<a href="{{ url_for('clients', page=page-1) }}">« 上一页</a>
|
||||
{% endif %}
|
||||
|
||||
{% for p in range(1, total_pages + 1) %}
|
||||
{% if p == page %}
|
||||
<span class="current">{{ p }}</span>
|
||||
{% else %}
|
||||
<a href="{{ url_for('clients', page=p) }}">{{ p }}</a>
|
||||
{% endif %}
|
||||
{% endfor %}
|
||||
|
||||
{% if page < total_pages %}
|
||||
<a href="{{ url_for('clients', page=page+1) }}">下一页 »</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
<!-- 编辑用户模态框 -->
|
||||
@ -78,7 +93,9 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
|
||||
{% block scripts %}
|
||||
<script>
|
||||
document.addEventListener('DOMContentLoaded', function() {
|
||||
// 切换用户状态
|
||||
@ -167,6 +184,26 @@
|
||||
document.getElementById('createModal').style.display = 'none';
|
||||
});
|
||||
|
||||
// 复制代理地址
|
||||
document.querySelectorAll('.copy-btn').forEach(btn => {
|
||||
btn.addEventListener('click', function() {
|
||||
const username = this.dataset.username;
|
||||
fetch(`/copy_proxy/${username}`, { method: 'POST' })
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
// 创建一个临时的textarea元素来复制文本
|
||||
const textarea = document.createElement('textarea');
|
||||
textarea.value = data.proxy_url;
|
||||
document.body.appendChild(textarea);
|
||||
textarea.select();
|
||||
document.execCommand('copy');
|
||||
document.body.removeChild(textarea);
|
||||
|
||||
alert('代理地址已复制到剪贴板: ' + data.proxy_url);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// 退出系统
|
||||
document.getElementById('logoutBtn').addEventListener('click', function() {
|
||||
if(confirm('确定要退出系统吗?')) {
|
||||
@ -177,5 +214,38 @@
|
||||
});
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
{% endblock %}
|
||||
|
||||
{% block styles %}
|
||||
<style>
|
||||
/* 添加复制按钮的绿色样式 */
|
||||
.btn-success {
|
||||
background-color: #28a745;
|
||||
color: white;
|
||||
border: none;
|
||||
padding: 5px 10px;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.3s;
|
||||
}
|
||||
|
||||
.btn-success:hover {
|
||||
background-color: #218838;
|
||||
}
|
||||
|
||||
.btn-success:active {
|
||||
background-color: #1e7e34;
|
||||
}
|
||||
|
||||
.icon-copy {
|
||||
display: inline-block;
|
||||
width: 14px;
|
||||
height: 14px;
|
||||
background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/20000/svg' viewBox='0 0 24 24' fill='white'%3E%3Cpath d='M16 1H4c-1.1 0-2 .9-2 2v14h2V3h12V1zm3 4H8c-1.1 0-2 .9-2 2v14c0 1.1.9 2 2 2h11c1.1 0 2-.9 2-2V7c0-1.1-.9-2-2-2zm0 16H8V7h11v14z'/%3E%3C/svg%3E");
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
margin-right: 5px;
|
||||
vertical-align: middle;
|
||||
}
|
||||
</style>
|
||||
{% endblock %}
|
||||
|
||||
47
templates/index.html
Normal file
47
templates/index.html
Normal file
@ -0,0 +1,47 @@
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}Squid代理用户管理系统{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="home-header">
|
||||
<h1>Squid代理用户管理系统</h1>
|
||||
</div>
|
||||
|
||||
<div class="dashboard">
|
||||
<div class="dashboard-card">
|
||||
<div class="card-icon">👥</div>
|
||||
<h3>用户数量</h3>
|
||||
<p class="stat">{{ user_count }}</p>
|
||||
<p class="card-desc">当前活跃用户总数</p>
|
||||
</div>
|
||||
|
||||
<div class="dashboard-card">
|
||||
<div class="card-icon">🔌</div>
|
||||
<h3>代理地址</h3>
|
||||
<p class="stat">{{ proxy_address }}:{{ proxy_port }}</p>
|
||||
<p class="card-desc">服务器连接信息</p>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="proxy-info">
|
||||
<h2><i class="icon">📋</i> 代理使用说明</h2>
|
||||
<div class="info-steps">
|
||||
<div class="step">
|
||||
<span class="step-number">1</span>
|
||||
<p>在浏览器或系统设置中配置代理服务器</p>
|
||||
</div>
|
||||
<div class="step">
|
||||
<span class="step-number">2</span>
|
||||
<p>地址: <code>{{ proxy_address }}</code> 端口: <code>{{ proxy_port }}</code></p>
|
||||
</div>
|
||||
<div class="step">
|
||||
<span class="step-number">3</span>
|
||||
<p>使用格式: <code>http://用户名:密码@{{ proxy_address }}:{{ proxy_port }}</code></p>
|
||||
</div>
|
||||
<div class="step">
|
||||
<span class="step-number">4</span>
|
||||
<p>或者在PAC文件中配置: <code>PROXY {{ proxy_address }}:{{ proxy_port }}</code></p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% endblock %}
|
||||
42
templates/settings.html
Normal file
42
templates/settings.html
Normal file
@ -0,0 +1,42 @@
|
||||
<!-- templates/settings.html -->
|
||||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}系统设置 - Squid代理用户管理系统{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h1>系统设置</h1>
|
||||
|
||||
<!-- 添加消息显示区域 -->
|
||||
{% with messages = get_flashed_messages(with_categories=true) %}
|
||||
{% if messages %}
|
||||
<div class="alert alert-{{ messages[0][0] }}">
|
||||
{{ messages[0][1] }}
|
||||
</div>
|
||||
{% endif %}
|
||||
{% endwith %}
|
||||
|
||||
<form method="post" action="{{ url_for('update_settings') }}" class="settings-form">
|
||||
<div class="form-group">
|
||||
<label for="admin_password">管理员密码</label>
|
||||
<input type="password" id="admin_password" name="admin_password"
|
||||
placeholder="留空则不修改" value="{{ config.admin_password }}">
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="proxy_address">代理服务器地址</label>
|
||||
<input type="text" id="proxy_address" name="proxy_address"
|
||||
value="{{ config.proxy_address }}" required>
|
||||
</div>
|
||||
|
||||
<div class="form-group">
|
||||
<label for="proxy_port">代理服务器端口</label>
|
||||
<input type="text" id="proxy_port" name="proxy_port"
|
||||
value="{{ config.proxy_port }}" required>
|
||||
</div>
|
||||
|
||||
<div class="form-actions">
|
||||
<button type="submit" class="btn-success">保存设置</button>
|
||||
<a href="{{ url_for('index') }}" class="btn-danger">取消</a>
|
||||
</div>
|
||||
</form>
|
||||
{% endblock %}
|
||||
Loading…
x
Reference in New Issue
Block a user