diff --git a/app.py b/app.py index ceb0766..ffd3512 100644 --- a/app.py +++ b/app.py @@ -118,6 +118,7 @@ def create_app() -> Flask: with app.app_context(): db.create_all() + _migrate_schema() _ensure_default_user() _ensure_admin_from_env() @@ -254,6 +255,7 @@ def create_app() -> Flask: path = (form.path.data or "").strip() or "/" s = Service( name=form.name.data.strip(), + scheme=form.scheme.data, host=form.host.data.strip(), port=form.port.data, path=path, @@ -288,6 +290,7 @@ def create_app() -> Flask: ).all() if form.validate_on_submit(): s.name = form.name.data.strip() + s.scheme = form.scheme.data s.host = form.host.data.strip() s.port = form.port.data s.path = (form.path.data or "").strip() or "/" @@ -331,6 +334,28 @@ def create_app() -> Flask: return app +def _migrate_schema() -> None: + """SQLite 已有库补列(db.create_all 不会 ALTER 已有表)。""" + from sqlalchemy import inspect, text + + try: + insp = inspect(db.engine) + if "services" not in insp.get_table_names(): + return + cols = {c["name"] for c in insp.get_columns("services")} + if "scheme" not in cols: + with db.engine.begin() as conn: + conn.execute( + text( + "ALTER TABLE services ADD COLUMN scheme VARCHAR(8) " + "NOT NULL DEFAULT 'http'" + ) + ) + print("[nav] 已为 services 表添加 scheme 列(默认 http)。", flush=True) + except Exception as exc: + print(f"[nav] 数据库结构迁移跳过或失败: {exc}", flush=True) + + def _first_group_id() -> Optional[int]: g = ServiceGroup.query.order_by(ServiceGroup.sort_order, ServiceGroup.id).first() return g.id if g else None diff --git a/forms.py b/forms.py index d025548..3f6bd57 100644 --- a/forms.py +++ b/forms.py @@ -21,7 +21,16 @@ class GroupForm(FlaskForm): class ServiceForm(FlaskForm): name = StringField("服务名称", validators=[DataRequired(message="请输入服务名称")]) - host = StringField("内网 IP 或主机名", validators=[DataRequired(message="请输入主机")]) + scheme = SelectField( + "协议", + choices=[("http", "HTTP"), ("https", "HTTPS")], + default="http", + validators=[DataRequired(message="请选择协议")], + ) + host = StringField( + "主机或域名", + validators=[DataRequired(message="请输入主机或域名")], + ) port = IntegerField( "端口", validators=[ diff --git a/models.py b/models.py index 19bd418..0ab57c4 100644 --- a/models.py +++ b/models.py @@ -39,6 +39,7 @@ class Service(db.Model): id = db.Column(db.Integer, primary_key=True) name = db.Column(db.String(120), nullable=False) + scheme = db.Column(db.String(8), nullable=False, default="http") host = db.Column(db.String(255), nullable=False) port = db.Column(db.Integer, nullable=False) path = db.Column(db.String(512), nullable=False, default="/") @@ -51,4 +52,7 @@ class Service(db.Model): p = (self.path or "/").strip() if not p.startswith("/"): p = "/" + p - return f"http://{self.host}:{self.port}{p}" + proto = (self.scheme or "http").strip().lower() + if proto not in ("http", "https"): + proto = "http" + return f"{proto}://{self.host}:{self.port}{p}" diff --git a/templates/admin_service_form.html b/templates/admin_service_form.html index 9f65507..0317fea 100644 --- a/templates/admin_service_form.html +++ b/templates/admin_service_form.html @@ -28,9 +28,16 @@
{{ form.name.errors[0] }}
{% endif %} +
+ {{ form.scheme.label }} + {{ form.scheme() }} + {% if form.scheme.errors %} +
{{ form.scheme.errors[0] }}
+ {% endif %} +
{{ form.host.label }} - {{ form.host(placeholder="例如 192.168.1.10 或 主机名") }} + {{ form.host(placeholder="例如 192.168.1.10 或 api.example.com") }} {% if form.host.errors %}
{{ form.host.errors[0] }}
{% endif %} diff --git a/部署与使用说明.md b/部署与使用说明.md index 7acc84f..61d76ad 100644 --- a/部署与使用说明.md +++ b/部署与使用说明.md @@ -29,7 +29,7 @@ | 数据库 | SQLite,默认文件名为 `nav_local.db`(与运行当前工作目录有关)。 | | 网络监听 | 默认绑定 `0.0.0.0`,便于同局域网手机、电脑访问。 | -**说明**:服务访问地址由程序拼接为 `http://{host}:{port}{path}`,当前版本固定为 **HTTP**(内网场景)。若目标服务为 HTTPS,需在后续版本中扩展字段;嵌入 iframe 时浏览器仍按 HTTPS 规则校验混合内容等。 +**说明**:服务访问地址由程序拼接为 `{scheme}://{host}:{port}{path}`,可在「服务管理」中选择 **HTTP** 或 **HTTPS**。嵌入 iframe 时浏览器仍按混合内容等安全策略校验(例如导航站为 HTTPS、内嵌目标为 HTTP 时可能被浏览器拦截)。 --- @@ -214,12 +214,24 @@ http://<本机局域网IP>:5070 - 字段含义简要说明: - **服务名称**:左侧显示名称。 - - **内网 IP 或主机名**:如 `192.168.1.10` 或可解析的主机名。 + - **协议**:**HTTP** 或 **HTTPS**;HTTPS 站点选 HTTPS,端口一般填 `443`(或其它 TLS 端口)。 + - **主机或域名**:如 `192.168.1.10`、`api.example.com` 等。 - **端口**:1–65535。 - **路径**:可选,须以 `/` 开头;留空则按 `/` 处理。 - **分组**:必选。 - **排序**:同分组内数字越小越靠前。 +**HTTPS 服务示例:** + +| 字段 | 示例值 | +|------|--------| +| 协议 | HTTPS | +| 主机或域名 | `panel.example.com` | +| 端口 | `443` | +| 路径 | `/` | + +生成地址:`https://panel.example.com:443/` + ### 8.5 关于 iframe 打不开的说明 部分网站(尤其银行、部分管理面板)通过 **`X-Frame-Options`** 或 **`Content-Security-Policy`** 禁止被嵌入 iframe,此时右侧区域可能为空白或浏览器控制台报错。这属于 **目标站点安全策略**,与本导航站实现无关。若必须统一入口,只能由目标服务侧放开嵌入策略,或改为新窗口打开(需改代码,非当前默认行为)。