使用A I学习编程
前言
最近一直折腾电脑,主要是安装学习Linux系统,部署各种应用:安装配置软路由功能、迁移原来安装在群晖nas上的应用、docker-compose应用部署、以及程序员必备神器终端的使用等等。所有这些均是在AI帮助下完成的,很难想象没有AI帮助我能否做到这些。这两天忽然想要尝试用AI 编程,凡事总要试一试,对吧,于是就有了以下过程。
行动
在行动之前,我就想好了写一个简单的需求提交给 AI,让它给出解决方案和具体代码。需求如下:
目前对家中路由器hosts文件进行修改,以满足自己的应用需要。但是存在一个问题:当路由器重启后,修改过的配置就会丢失,需要手动再次输入,并使其生效。
因此,需要编程实现修改远程主机/etc/hosts文件功能,该应用应部署在服务端,可通过web页面访问,便于任何家中可上网设备进行修改。
初次使用可以手动建立连接配置并存为一个连接配置选项,方便后续使用。登录后可选择直接输入欲增加host内容单条目或上传文本文件格式的hosts列表,添加内容到hosts文件中,实现快捷更改远程服务器hosts的目的。
已有应用环境:有一台Debian 12 服务器一台,群晖服务器两台,均可支持软件部署。
将需求提交给 ChatGPT 并要求给出解决方案及具体代码,大约十多秒后 ChatGPT 给出了方案,非常完备(详见 附件1:ChatGPT 的方案及代码)。
调试
按照 ChatGPT 提供方案部署应用,首先在远端服务器新建一个项目目录 “edit-hosts”,然后在此目录下创建第一个文件 app.py ,并录入 ChatGPT 提供的代码。在此目录下创建子目录 “templates”,建立 index.html 文件并复制对应代码。
随后后安装、运行相关依赖。期间因为安装依赖产生各种问题,多次请教 ChatGPT ,ChatGPT 针对安装、调试遇到的问题一般会给出几种解决方案,并阐述相关理由和优缺点,因此安装依赖过程也是学习提高的过程。经过一个多小时安装、调试,所有运行环境均已准备好。激动人心时刻来临,这如同亲手做了一个玩具,一直不知道是否靠谱,检验AI成色的时刻来临。在命令终端输入指令:
Python app.py
屏幕上如期出现运行代码划过,最终停留在运行正常信息画面,并给出访问链接: http://192.168.50.20:2017 。兴冲冲打开浏览器输入以上网址,页面正常打开,接着输入路由器连接参数,在连接我的华硕路由器时,程序出错:"error": "sh: sudo: not found\n"。
虽然当时遇到了bug,所有编过程序的人都知道,这很正常,最害怕的完全跑不起来。编程过程就是需要不断地找到bug——》修正bug,自此我觉得这个 AI 编程是靠谱的。之所以出现上面这个错误,是因为华硕路由器 SSH 登录特点所致。虽然市面上大多数路由器均采用Linux系统,但是因为存储空间及功能需要,其系统非常精简,因此很多常用功能并不具备,比如 sudo指令登录root账号。事实上,华硕路由器通过ssh终端登录后直接进入root模式。AI显然不知道这种情况,按照正常逻辑登录后继续输入 sudo指令进入root权限,导致程序报错。我将此情况反馈给 ChatGPT,并让其修改。
反馈
以下我提出的反馈意见:
运行SSH终端连接华硕路由器后就直接进入 root 模式,无需再用sudo指令,且也不支持sudo指令,建议增加选项。错误信息如下:
"error": "sh: sudo: not found\n"
对于华硕路由器,不能使用重启生效指令,因为一旦重启,hosts文件自动恢复到初始状态,所有修改将丢失。但是可以执行一条指令让hosts修改生效并保持修改后的状态:killall -SIGHUP dnsmasq
如果要使应用更为通用,可以增加更多的选项和配置以及错误提示。或者在应用界面直接打开 /etc/hosts/ 文件编辑窗口,进行编辑、复制、粘贴操作,编辑完成后保存生效。
针对我的反馈意见,ChatGPT迅速给出答复如下:
这些改动会让应用更适配华硕路由器的环境。我会优化逻辑,增加以下功能:
SSH 执行命令时可选是否使用
sudo
(默认 root 用户不使用)针对华硕路由器的特定处理:修改 hosts 后提供
killall -SIGHUP dnsmasq
选项,而不是重启增强 UI 体验:提供一个 Web 界面直接编辑
/etc/hosts
,并提供错误提示
于是又按照改进方案进行调整程序,安装新的运行环境,期间多次与ChatGPT交换意见。结果就在调试过程中ChatGPT 不干了,提示我的免费额度已经用完,于是第一天AI编程只能按下暂停键。
换门
到了第二天,我考虑更换一个 AI 试试。从网上了解到 Claude 在编程方面非常厉害,我决定改用 Claude 。这一决定现在看来正确无比。当我把数百行 ChatGPT 的方案和代码以及我的反馈意见一并提交给 Claude 后,它立即就给出了完整解决方案,并优化了代码。
然后按照步骤一步步完成代码录入、运行环境调试,输入指令运行。打开浏览器输入 http://192.168.50.123:5000 , 如期进入程序web页面,输入路由器登录数据,正常登录,尝试修改 hosts ,单条目录入成功,撒花🎉🎉🎉。虽然在随后的测试出了错:“查看hosts页面”对hosts文件进行了修改,保存修改未能生效。但相信经过简单调试,一定可以解决这个bug,由此可见 Claude 编程能力之强悍。
接下来的一两个小时,我将运行的信息反馈给 Claude,它根据反馈信息和要求进行代码调整,如此反复,最终完善了所有我要的功能,完成了我人生中第一个真正意义的网络软件开发。尽管这个软件还非常的简单,界面简陋,实现功能也非常单一,但它是真正意义的网络环境运行的软件,可以部署在远程服务器上,网内所有客户端访问使用。如果愿意,也可通过反向代理轻易实现公网访问。
总结
必须承认,AI 的出现将会对我们生活方方面面产生巨大的影响,对于一个快60岁的中老年人,如果没有AI 帮助很难想象我能独立完成这么一个简陋的软件。其中涉及任何一门计算机语言和知识都需要大量时间去学习掌握,更不要说用这些知识直接编程。再退一步,就算这些都是我目前所掌握的,数百行代码一个字一个的敲入,也需要至数个工作日才能完成。而现在,我一个业余爱好者,相关知识勉强算是略知皮毛几乎是零基础的人,在 AI帮助下仅仅用了数小时就完成了相关开发,不由得佩服AI的强大。
但这是不是意味着,以后程序员就要失业了?我不这样认为。AI的出现恰恰是体现程序员的价值所在。虽然我自嘲自己零基础实现了AI编程,但是如果没有对计算机有一定的了解,对编程有一定的理解,就算AI喂饭给我,我也不知道如何部署,各个部件如何协调运行,如何搭建环境等等,更不用说让代码跑起来。这只是意味着对程序员要求更高,需要程序员站在使用者角度,能够提出精确的需求分析,能够熟知各个技术平台技术特点,针对性让AI完成代码部分,调试/协调、优化和组装仍需要人来干。这部分价值是Ai不能替代的,这也是软件开发最具价值的部分。
附件 1: ChatGPT 的方案及代码
远程 Hosts 管理系统方案
1.需求概述
需要通过 Web 应用管理远程路由器的
/etc/hosts
文件。确保修改后的配置在设备重启后仍然生效。
通过 Web 界面支持:
选择远程主机进行 SSH 连接。
直接输入 hosts 记录进行单条添加。
上传文本文件批量修改 hosts 记录。
可持久化存储远程主机的 SSH 连接信息,方便后续管理。
部署环境:Debian 12 服务器或群晖服务器。
2.技术方案
2.1 选用技术
后端:Flask(提供 Web API)、Paramiko(SSH 远程管理)、SQLite(存储连接信息)
前端:Flask + Bootstrap
部署方式:
直接运行 Flask 服务器
使用 Docker 容器化部署
3. 代码实现
3.1 安装依赖
pip install flask paramiko flask_sqlalchemy
3.2 app.py
- Flask 后端
from flask import Flask, request, render_template, redirect, url_for, flash, jsonify
import paramiko
import os
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///connections.db'
app.config['SECRET_KEY'] = 'your_secret_key'
db = SQLAlchemy(app)
class SSHConnection(db.Model):
id = db.Column(db.Integer, primary_key=True)
hostname = db.Column(db.String(100), nullable=False)
username = db.Column(db.String(50), nullable=False)
password = db.Column(db.String(100), nullable=False)
port = db.Column(db.Integer, default=22)
with app.app_context():
db.create_all()
def ssh_connect(hostname, username, password, port=22):
try:
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(hostname, port, username, password)
return client
except Exception as e:
return str(e)
@app.route('/')
def index():
connections = SSHConnection.query.all()
return render_template('index.html', connections=connections)
@app.route('/add_connection', methods=['POST'])
def add_connection():
hostname = request.form['hostname']
username = request.form['username']
password = request.form['password']
port = request.form.get('port', 22, type=int)
new_conn = SSHConnection(hostname=hostname, username=username, password=password, port=port)
db.session.add(new_conn)
db.session.commit()
flash("连接已保存", "success")
return redirect(url_for('index'))
@app.route('/update_hosts/<int:id>', methods=['POST'])
def update_hosts(id):
conn = SSHConnection.query.get(id)
if not conn:
return jsonify({'error': '无效的连接'}), 400
client = ssh_connect(conn.hostname, conn.username, conn.password, conn.port)
if isinstance(client, str):
return jsonify({'error': client}), 500
new_entry = request.form['new_entry']
if not new_entry.strip():
return jsonify({'error': '输入不能为空'}), 400
try:
command = f"echo '{new_entry}' | sudo tee -a /etc/hosts"
stdin, stdout, stderr = client.exec_command(command)
output = stdout.read().decode()
error = stderr.read().decode()
client.close()
if error:
return jsonify({'error': error}), 500
return jsonify({'success': output})
except Exception as e:
return jsonify({'error': str(e)}), 500
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)
3.3 templates/index.html
- Web 界面
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>远程 Hosts 管理</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
</head>
<body>
<div class="container">
<h2 class="mt-4">远程 Hosts 管理</h2>
<form action="/add_connection" method="POST" class="mb-3">
<input type="text" name="hostname" placeholder="主机地址" required class="form-control">
<input type="text" name="username" placeholder="用户名" required class="form-control">
<input type="password" name="password" placeholder="密码" required class="form-control">
<input type="number" name="port" placeholder="端口 (默认22)" class="form-control">
<button type="submit" class="btn btn-primary mt-2">添加连接</button>
</form>
<ul class="list-group">
{% for conn in connections %}
<li class="list-group-item">
{{ conn.hostname }} ({{ conn.username }})
<a href="/delete_connection/{{ conn.id }}" class="btn btn-danger btn-sm">删除</a>
<form action="/update_hosts/{{ conn.id }}" method="POST" class="d-inline">
<input type="text" name="new_entry" placeholder="输入新的 hosts 记录" required>
<button type="submit" class="btn btn-success btn-sm">添加</button>
</form>
</li>
{% endfor %}
</ul>
</div>
</body>
</html>
4. 部署方式
4.1 直接运行 Flask 服务器
export FLASK_APP=app.py
flask run --host=0.0.0.0 --port=5000
4.2 使用 Docker 运行
docker build -t hosts_manager .
docker run -d -p 5000:5000 hosts_manager
这样就可以通过 Web 界面轻松管理远程 /etc/hosts
了! 🚀
附件 2: Claude 方案及代码
我仔细阅读了文档,发现有几个关键点需要针对性修改:
Updated Remote Hosts Management application
from flask import Flask, request, render_template, redirect, url_for, flash, jsonify
import paramiko
import os
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///connections.db'
app.config['SECRET_KEY'] = 'your_secret_key'
db = SQLAlchemy(app)
class SSHConnection(db.Model):
id = db.Column(db.Integer, primary_key=True)
hostname = db.Column(db.String(100), nullable=False)
username = db.Column(db.String(50), nullable=False)
password = db.Column(db.String(100), nullable=False)
port = db.Column(db.Integer, default=22)
router_type = db.Column(db.String(50), nullable=True) # 新增路由器类型字段
with app.app_context():
db.create_all()
def ssh_connect(hostname, username, password, port=22):
try:
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(hostname, port, username, password)
return client
except Exception as e:
return str(e)
@app.route('/')
def index():
connections = SSHConnection.query.all()
return render_template('index.html', connections=connections)
@app.route('/add_connection', methods=['POST'])
def add_connection():
hostname = request.form['hostname']
username = request.form['username']
password = request.form['password']
port = request.form.get('port', 22, type=int)
router_type = request.form.get('router_type', '')
new_conn = SSHConnection(
hostname=hostname,
username=username,
password=password,
port=port,
router_type=router_type
)
db.session.add(new_conn)
db.session.commit()
flash("连接已保存", "success")
return redirect(url_for('index'))
@app.route('/update_hosts/<int:id>', methods=['POST'])
def update_hosts(id):
conn = SSHConnection.query.get(id)
if not conn:
return jsonify({'error': '无效的连接'}), 400
client = ssh_connect(conn.hostname, conn.username, conn.password, conn.port)
if isinstance(client, str):
return jsonify({'error': client}), 500
new_entry = request.form['new_entry']
if not new_entry.strip():
return jsonify({'error': '输入不能为空'}), 400
try:
# 针对不同路由器类型特殊处理
if conn.router_type == 'asus':
# 对于华硕路由器,直接写入并重启DNS服务
stdin, stdout, stderr = client.exec_command(f"echo '{new_entry}' >> /etc/hosts && killall -SIGHUP dnsmasq")
else:
# 对于其他路由器,尝试使用sudo或直接写入
stdin, stdout, stderr = client.exec_command(f"echo '{new_entry}' >> /etc/hosts")
output = stdout.read().decode()
error = stderr.read().decode()
client.close()
if error and 'sudo' in error:
# 如果不支持sudo,尝试直接写入
stdin, stdout, stderr = client.exec_command(f"echo '{new_entry}' >> /etc/hosts")
output = stdout.read().decode()
error = stderr.read().decode()
if error:
return jsonify({'error': error}), 500
return jsonify({'success': '主机文件更新成功!'})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/view_hosts/<int:id>', methods=['GET'])
def view_hosts(id):
conn = SSHConnection.query.get(id)
if not conn:
flash('无效的连接', 'error')
return redirect(url_for('index'))
try:
client = ssh_connect(conn.hostname, conn.username, conn.password, conn.port)
if isinstance(client, str):
flash(f'连接失败: {client}', 'error')
return redirect(url_for('index'))
stdin, stdout, stderr = client.exec_command('cat /etc/hosts')
hosts_content = stdout.read().decode('utf-8')
client.close()
return render_template('hosts_view.html', hosts_content=hosts_content, connection_id=id)
except Exception as e:
flash(f'获取主机文件失败: {str(e)}', 'error')
return redirect(url_for('index'))
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)
Hosts View Template
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>Hosts 文件查看</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
</head>
<body>
<div class="container mt-4">
<h2>Hosts 文件内容</h2>
<form action="/update_hosts_file/{{ connection_id }}" method="POST">
<div class="form-group">
<textarea name="hosts_content" class="form-control" rows="20">{{ hosts_content }}</textarea>
</div>
<button type="submit" class="btn btn-primary mt-2">保存修改</button>
<a href="/" class="btn btn-secondary mt-2">返回</a>
</form>
</div>
</body>
</html>
Updated Index Template
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>远程 Hosts 管理</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
</head>
<body>
<div class="container">
<h2 class="mt-4">远程 Hosts 管理</h2>
<form action="/add_connection" method="POST" class="mb-3">
<div class="row">
<div class="col-md-6">
<input type="text" name="hostname" placeholder="主机地址" required class="form-control mb-2">
<input type="text" name="username" placeholder="用户名" required class="form-control mb-2">
<input type="password" name="password" placeholder="密码" required class="form-control mb-2">
<input type="number" name="port" placeholder="端口 (默认22)" class="form-control mb-2">
<select name="router_type" class="form-control mb-2">
<option value="">选择路由器类型</option>
<option value="asus">华硕路由器</option>
<option value="other">其他路由器</option>
</select>
<button type="submit" class="btn btn-primary">添加连接</button>
</div>
</div>
</form>
<div class="card">
<div class="card-header">已保存的连接</div>
<ul class="list-group list-group-flush">
{% for conn in connections %}
<li class="list-group-item d-flex justify-content-between align-items-center">
<div>
{{ conn.hostname }} ({{ conn.username }})
<span class="badge bg-info">{{ conn.router_type or '未指定' }}</span>
</div>
<div>
<a href="/view_hosts/{{ conn.id }}" class="btn btn-info btn-sm">查看 Hosts</a>
<form action="/update_hosts/{{ conn.id }}" method="POST" class="d-inline">
<div class="input-group">
<input type="text" name="new_entry" placeholder="新的 hosts 记录" required class="form-control form-control-sm">
<button type="submit" class="btn btn-success btn-sm">添加</button>
</div>
</form>
<a href="/delete_connection/{{ conn.id }}" class="btn btn-danger btn-sm">删除</a>
</div>
</li>
{% endfor %}
</ul>
</div>
</div>
</body>
</html>
我对原代码进行了以下重要改进:
增加路由器类型选择
在连接模型中新增
router_type
字段在界面增加路由器类型下拉选择
对华硕路由器特殊处理,直接使用
killall -SIGHUP dnsmasq
重启DNS服务
增加 Hosts 文件查看和编辑功能
新增
/view_hosts/<id>
路由增加
hosts_view.html
模板可以直接查看和编辑整个 Hosts 文件
错误处理优化
增加对不同场景的错误处理
尝试绕过 sudo 限制
增加更友好的错误提示
界面优化
使用 Bootstrap 卡片布局
增加路由器类型标签
优化按钮和输入框布局
新增查看和编辑整个 Hosts 文件的功能
主要解决的问题:
针对华硕路由器的特殊处理
绕过 sudo 限制
提供更灵活的 Hosts 文件管理方式
建议:
部署时请注意安全性,如使用 SSH 密钥替代密码
可以考虑增加加密存储连接信息
后续可以增加备份、回滚等高级功能
附件 3: Claude 方案最终代码
app.py
from flask import Flask, request, render_template, redirect, url_for, flash, jsonify
import paramiko
import os
from flask_sqlalchemy import SQLAlchemy
import logging
logging.basicConfig(level=logging.INFO)
app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:////app/instance/connections.db'
app.config['SECRET_KEY'] = 'your_secret_key'
db = SQLAlchemy(app)
class SSHConnection(db.Model):
id = db.Column(db.Integer, primary_key=True)
hostname = db.Column(db.String(100), nullable=False)
username = db.Column(db.String(50), nullable=False)
password = db.Column(db.String(100), nullable=False)
port = db.Column(db.Integer, default=22)
router_type = db.Column(db.String(50), nullable=True) # 新增路由器类型字段
with app.app_context():
db.create_all()
def ssh_connect(hostname, username, password, port=22):
try:
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(hostname, port, username, password)
return client
except Exception as e:
return str(e)
@app.route('/')
def index():
connections = SSHConnection.query.all()
return render_template('index.html', connections=connections)
@app.route('/add_connection', methods=['POST'])
def add_connection():
hostname = request.form['hostname']
username = request.form['username']
password = request.form['password']
port = request.form.get('port', 22, type=int)
router_type = request.form.get('router_type', '')
new_conn = SSHConnection(
hostname=hostname,
username=username,
password=password,
port=port,
router_type=router_type
)
db.session.add(new_conn)
db.session.commit()
flash("连接已保存", "success")
return redirect(url_for('index'))
@app.route('/update_hosts/<int:id>', methods=['POST'])
def update_hosts(id):
conn = SSHConnection.query.get(id)
if not conn:
return jsonify({'error': '无效的连接'}), 400
client = ssh_connect(conn.hostname, conn.username, conn.password, conn.port)
if isinstance(client, str):
return jsonify({'error': client}), 500
new_entry = request.form['new_entry']
if not new_entry.strip():
return jsonify({'error': '输入不能为空'}), 400
try:
# 针对不同路由器类型特殊处理
if conn.router_type == 'asus':
# 对于华硕路由器,直接写入并重启DNS服务
stdin, stdout, stderr = client.exec_command(f"echo '{new_entry}' >> /etc/hosts && killall -SIGHUP dnsmasq")
else:
# 对于其他路由器,尝试使用sudo或直接写入
stdin, stdout, stderr = client.exec_command(f"echo '{new_entry}' >> /etc/hosts")
output = stdout.read().decode()
error = stderr.read().decode()
client.close()
if error and 'sudo' in error:
# 如果不支持sudo,尝试直接写入
stdin, stdout, stderr = client.exec_command(f"echo '{new_entry}' >> /etc/hosts")
output = stdout.read().decode()
error = stderr.read().decode()
if error:
return jsonify({'error': error}), 500
return jsonify({'success': '主机文件更新成功!'})
except Exception as e:
return jsonify({'error': str(e)}), 500
@app.route('/view_hosts/<int:id>', methods=['GET'])
def view_hosts(id):
conn = SSHConnection.query.get(id)
if not conn:
flash('无效的连接', 'error')
return redirect(url_for('index'))
try:
client = ssh_connect(conn.hostname, conn.username, conn.password, conn.port)
if isinstance(client, str):
flash(f'连接失败: {client}', 'error')
return redirect(url_for('index'))
stdin, stdout, stderr = client.exec_command('cat /etc/hosts')
hosts_content = stdout.read().decode('utf-8')
client.close()
return render_template('hosts_view.html', hosts_content=hosts_content, connection_id=id)
except Exception as e:
flash(f'获取主机文件失败: {str(e)}', 'error')
return redirect(url_for('index'))
@app.route('/update_hosts_file/<int:id>', methods=['POST'])
def update_hosts_file(id):
conn = SSHConnection.query.get(id)
if not conn:
flash('无效的连接', 'error')
return redirect(url_for('index'))
hosts_content = request.form['hosts_content']
try:
# 创建新的连接,避免复用可能已失效的连接
client = paramiko.SSHClient()
client.set_missing_host_key_policy(paramiko.AutoAddPolicy())
client.connect(
hostname=conn.hostname,
port=conn.port,
username=conn.username,
password=conn.password,
timeout=10 # 增加连接超时时间
)
try:
# 使用 echo 命令写入文件,避免 EOL 问题
command = f"echo '{hosts_content}' > /etc/hosts"
stdin, stdout, stderr = client.exec_command(command, get_pty=True)
# 读取命令执行结果
output = stdout.read().decode().strip()
error = stderr.read().decode().strip()
if error:
app.logger.error(f"写入文件时发生错误: {error}")
flash(f'写入失败: {error}', 'error')
return redirect(url_for('view_hosts', id=id))
# 验证文件内容
stdin, stdout, stderr = client.exec_command('cat /etc/hosts')
updated_content = stdout.read().decode().strip()
# 重启 DNS 服务(针对华硕路由器)
if conn.router_type == 'asus':
client.exec_command('killall -SIGHUP dnsmasq')
# 检查文件内容是否匹配
if updated_content.strip() != hosts_content.strip():
flash('文件写入失败:内容不匹配', 'error')
return redirect(url_for('view_hosts', id=id))
flash('Hosts 文件更新成功!', 'success')
return redirect(url_for('index'))
except Exception as e:
app.logger.error(f"更新 Hosts 文件时发生错误: {str(e)}")
flash(f'更新失败: {str(e)}', 'error')
return redirect(url_for('view_hosts', id=id))
finally:
# 确保关闭连接
client.close()
except Exception as e:
app.logger.error(f"连接时发生错误: {str(e)}")
flash(f'连接失败: {str(e)}', 'error')
return redirect(url_for('view_hosts', id=id))
if __name__ == '__main__':
app.run(host='0.0.0.0', port=5000, debug=True)
index.html
<!DOCTYPE html>
<html lang="zh">
<head>
<meta charset="UTF-8">
<title>远程 Hosts 管理</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css">
</head>
<body>
<div class="container">
<h2 class="mt-4">远程 Hosts 管理</h2>
<form action="/add_connection" method="POST" class="mb-3">
<div class="row">
<div class="col-md-6">
<input type="text" name="hostname" placeholder="主机地址" required class="form-control mb-2">
<input type="text" name="username" placeholder="用户名" required class="form-control mb-2">
<input type="password" name="password" placeholder="密码" required class="form-control mb-2">
<input type="number" name="port" placeholder="端口 (默认22)" class="form-control mb-2">
<select name="router_type" class="form-control mb-2">
<option value="">选择路由器类型</option>
<option value="asus">华硕路由器</option>
<option value="other">其他路由器</option>
</select>
<button type="submit" class="btn btn-primary">添加连接</button>
</div>
</div>
</form>
<div class="card">
<div class="card-header">已保存的连接</div>
<ul class="list-group list-group-flush" id="connection-list">
{% for conn in connections %}
<li class="list-group-item d-flex justify-content-between align-items-center">
<div>
{{ conn.hostname }} ({{ conn.username }})
<span class="badge bg-info">{{ conn.router_type or '未指定' }}</span>
</div>
<div>
<a href="/view_hosts/{{ conn.id }}" class="btn btn-info btn-sm">查看 Hosts</a>
<form action="/update_hosts/{{ conn.id }}" method="POST" class="d-inline hosts-update-form">
<div class="input-group">
<input type="text" name="new_entry" placeholder="新的 hosts 记录" required class="form-control form-control-sm">
<button type="submit" class="btn btn-success btn-sm">添加</button>
</div>
</form>
<a href="/delete_connection/{{ conn.id }}" class="btn btn-danger btn-sm">删除</a>
</div>
</li>
{% endfor %}
</ul>
</div>
</div>
<!-- 成功提示模态框 -->
<div class="modal fade" id="successModal" tabindex="-1">
<div class="modal-dialog">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title">操作成功</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body" id="successModalBody">
<!-- 动态插入成功消息 -->
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">关闭</button>
</div>
</div>
</div>
</div>
<!-- 引入必要的 JS 库 -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.bundle.min.js"></script>
<script>
document.addEventListener('DOMContentLoaded', function() {
// 为所有 hosts 更新表单添加异步提交事件
document.querySelectorAll('.hosts-update-form').forEach(form => {
form.addEventListener('submit', function(e) {
e.preventDefault();
fetch(this.action, {
method: 'POST',
body: new FormData(this)
})
.then(response => response.json())
.then(data => {
if (data.success) {
// 显示成功模态框
document.getElementById('successModalBody').textContent = data.success;
var successModal = new bootstrap.Modal(document.getElementById('successModal'));
successModal.show();
// 重置表单
this.querySelector('input[name="new_entry"]').value = '';
} else if (data.error) {
// 显示错误提示
alert(data.error);
}
})
.catch(error => {
console.error('Error:', error);
alert('发生错误');
});
});
});
});
</script>
</body>
</html>
项目目录结构:
edithosts/
│
├── app.py
├── Dockerfile
├── docker-compose.yml
├── requirements.txt
├── templates/
│ ├── index.html
│ └── hosts_view.html
└── .dockerignore