feat: 页面顶部环境徽标(防混库第 4 层)
启动日志和横幅只在日志里;用户在界面上操作时也该一眼看出连的是哪个库。 生产显示红底 'PROD · <库名@主机/库>',开发显示灰底 'DEV',鼠标悬停给出完整目标。 环境信息由 core/db_config.env_badge() 提供(不含密码),web_server 注入 Jinja 全局。
This commit is contained in:
@@ -263,6 +263,25 @@ def print_banner():
|
||||
_log.info(text_block)
|
||||
|
||||
|
||||
def env_badge():
|
||||
"""给页面顶部徽标用的环境信息(不含密码)。
|
||||
|
||||
防混库的第 4 层:光靠启动日志不够——用户点开任何页面都该一眼看出
|
||||
自己正在操作哪个库。生产用红底。
|
||||
"""
|
||||
try:
|
||||
from core.models import db
|
||||
backend = "sqlite" if is_sqlite(db.engine.url) else "mysql"
|
||||
except Exception:
|
||||
backend = "?"
|
||||
try:
|
||||
target = describe_target()
|
||||
except Exception:
|
||||
target = DB_HOST or "(未配置)"
|
||||
return {"env": DEPLOY_ENV, "is_prod": DEPLOY_ENV == "prod",
|
||||
"backend": backend, "target": target}
|
||||
|
||||
|
||||
def _now():
|
||||
import time
|
||||
return time.strftime("%Y-%m-%d %H:%M:%S")
|
||||
|
||||
+3
-2
@@ -76,8 +76,9 @@ DB_NAME=auto_control
|
||||
**防混库**(`DEPLOY_ENV` 与库名/库标签双向校验,配置项说明见 [DEVELOPMENT.md](DEVELOPMENT.md) §4.2
|
||||
与 `.env.example` 的「数据库」段):启动时若
|
||||
「`.env` 声明的环境」与「库名」或「库中登记的 `app_meta.deployment_env`」不符,**直接拒绝启动**并打印
|
||||
两边分别是什么。每次启动还会打印一条横幅写明当前连的是哪个库(生产用 WARNING 级)。两个逃生阀
|
||||
`DB_ALLOW_ENV_MISMATCH` / `DB_ALLOW_SQLITE_FALLBACK` 默认关闭。
|
||||
两边分别是什么。每次启动还会打印一条横幅写明当前连的是哪个库(生产用 WARNING 级),
|
||||
页面顶部也常驻一个环境徽标(生产红底 `PROD · <库>`,开发灰底 `DEV`)。
|
||||
两个逃生阀 `DB_ALLOW_ENV_MISMATCH` / `DB_ALLOW_SQLITE_FALLBACK` 默认关闭。
|
||||
|
||||
其余配置项与默认值见 [DEVELOPMENT.md](DEVELOPMENT.md) §配置速查。
|
||||
|
||||
|
||||
@@ -46,6 +46,8 @@ body{background:var(--bg);font-family:var(--body);color:var(--text);font-size:14
|
||||
.brand-dot{width:9px;height:9px;border-radius:50%;background:var(--primary);box-shadow:0 0 0 0 rgba(15,118,110,.6);animation:brandPulse 2.4s infinite}
|
||||
@keyframes brandPulse{0%{box-shadow:0 0 0 0 rgba(15,118,110,.45)}70%{box-shadow:0 0 0 8px rgba(15,118,110,0)}100%{box-shadow:0 0 0 0 rgba(15,118,110,0)}}
|
||||
.navbar .tabs{display:flex;gap:2px;flex:1;align-self:stretch}
|
||||
.env-badge{margin-right:18px;padding:3px 10px;border-radius:10px;font-size:11px;font-weight:700;letter-spacing:.5px;background:#2b3038;color:#9aa3af;border:1px solid #3a4048;white-space:nowrap}
|
||||
.env-badge-prod{background:#4a1519;color:#ff9b9b;border-color:#8a2b31}
|
||||
.navbar .tab{position:relative;padding:0 18px;color:#9aa3af;font-size:14px;font-weight:500;cursor:pointer;border:none;background:none;transition:color .15s;letter-spacing:.2px}
|
||||
.navbar .tab::after{content:"";position:absolute;left:14px;right:14px;bottom:0;height:2px;border-radius:2px;background:var(--primary);transform:scaleX(0);transition:transform .18s}
|
||||
.navbar .tab:hover{color:#e5e7eb}
|
||||
@@ -392,6 +394,12 @@ body{background:var(--bg);font-family:var(--body);color:var(--text);font-size:14
|
||||
<!-- 顶部导航 -->
|
||||
<nav class="navbar">
|
||||
<div class="brand"><span class="brand-dot"></span>设备自动化后台</div>
|
||||
{% if db_env %}
|
||||
<span class="env-badge{% if db_env.is_prod %} env-badge-prod{% endif %}"
|
||||
title="数据库目标:{{ db_env.target }}({{ db_env.backend }})">
|
||||
{% if db_env.is_prod %}PROD · {{ db_env.target }}{% else %}{{ db_env.env|upper }}{% endif %}
|
||||
</span>
|
||||
{% endif %}
|
||||
<div class="tabs">
|
||||
<button class="tab active" data-tab="monitor" onclick="showTab('monitor')">监控</button>
|
||||
<button class="tab" data-tab="tasks" onclick="showTab('tasks')">任务</button>
|
||||
|
||||
@@ -74,6 +74,8 @@ try:
|
||||
with app.app_context(): # db.engine / app_meta 读取都需要 app context
|
||||
db_config.verify_deployment_label()
|
||||
db_config.print_banner()
|
||||
# 页面顶部环境徽标(防混库第 4 层:点开任何页面都能看出连的是哪个库)
|
||||
app.jinja_env.globals["db_env"] = db_config.env_badge()
|
||||
except db_config.DBConfigError as _e:
|
||||
_log.error("数据库环境校验失败,拒绝启动:\n%s", _e)
|
||||
raise SystemExit(2)
|
||||
|
||||
Reference in New Issue
Block a user