Compare commits

..

2 Commits

Author SHA1 Message Date
wzj
72d03723cd Merge branch 'master' of https://gitea.liuyan.wang/wzj/squid_ui 2025-07-29 11:12:01 +08:00
wzj
056d91b7ad 增加复制代理按钮 2025-07-29 11:05:22 +08:00
4 changed files with 125 additions and 20 deletions

73
app.py
View File

@ -13,6 +13,7 @@ app.secret_key = str(uuid.uuid4())
# 配置文件路径
CONFIG_FILE = 'config/config.json'
SQUID_PASSWD_FILE = 'config/squid_passwd'
USERS_FILE = 'config/users.json' # 新增用户信息存储文件
# 加载配置
@ -42,7 +43,7 @@ def save_config(config):
class User:
def __init__(self, name, password, is_active=True):
self.name = name
self.password = password
self.password = password # 这里存储明文密码
self.is_active = is_active
@ -74,7 +75,10 @@ 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]), parts[1])
users.append(User(parts[0], plain_password, is_active))
except FileNotFoundError:
pass
return users
@ -87,12 +91,33 @@ def write_squid_file(users):
f.write(line)
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):
users = read_squid_file()
if any(u.name == username for u in users):
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:
@ -109,7 +134,7 @@ def index():
proxy_address=config['proxy_address'],
proxy_port=config['proxy_port'])
# 在 clients 路由中增加分页逻辑
@app.route('/clients')
@basic_auth_required
def clients():
@ -123,13 +148,14 @@ def clients():
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)
# 其他代码保持不变...
total_pages=total_pages,
proxy_address=config['proxy_address'],
proxy_port=config['proxy_port'])
@app.route('/settings')
@ -138,6 +164,7 @@ def settings():
config = load_config()
return render_template('settings.html', config=config)
@app.route('/update_settings', methods=['POST'])
@basic_auth_required
def update_settings():
@ -154,7 +181,7 @@ def update_settings():
config['proxy_port'] = proxy_port
save_config(config)
flash('设置已成功保存!', 'success') # 添加成功提示
flash('设置已成功保存!', 'success')
return redirect(url_for('settings'))
@ -167,6 +194,7 @@ def toggle_user(username):
user.is_active = not user.is_active
break
write_squid_file(users)
save_users_to_json(users) # 更新users.json
return '', 200
@ -175,6 +203,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
@ -189,6 +218,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:
@ -211,6 +249,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"'})
@ -220,4 +275,4 @@ if __name__ == '__main__':
if not os.path.exists('config'):
os.makedirs('config')
load_config() # 初始化配置文件
app.run(host='0.0.0.0', port=8080, debug=True)
app.run(host='0.0.0.0', port=8080, debug=True)

View File

@ -1 +1,2 @@
Flask==2.3.2
Flask==2.3.2
pyperclip

View File

@ -483,4 +483,4 @@ input:checked + .slider:before {
flex-direction: column;
gap: 5px;
}
}
}

View File

@ -27,6 +27,9 @@
<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>
@ -181,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('确定要退出系统吗?')) {
@ -190,13 +213,39 @@
}
});
});
// 增加导航按钮事件
document.getElementById('logoutBtn').addEventListener('click', function() {
if(confirm('确定要退出系统吗?')) {
fetch('/logout').then(() => {
window.location.href = '/';
});
}
});
</script>
{% endblock %}
{% 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 %}