合并
This commit is contained in:
318
etl_billiards/gui/widgets/env_editor.py
Normal file
318
etl_billiards/gui/widgets/env_editor.py
Normal file
@@ -0,0 +1,318 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
"""环境变量编辑器"""
|
||||
|
||||
from PySide6.QtWidgets import (
|
||||
QWidget, QVBoxLayout, QHBoxLayout, QGridLayout,
|
||||
QGroupBox, QLabel, QLineEdit, QPushButton, QScrollArea,
|
||||
QFrame, QMessageBox, QFileDialog, QCheckBox
|
||||
)
|
||||
from PySide6.QtCore import Qt, Signal
|
||||
|
||||
from ..utils.config_helper import ConfigHelper, ENV_GROUPS
|
||||
|
||||
|
||||
class EnvEditor(QWidget):
|
||||
"""环境变量编辑器"""
|
||||
|
||||
# 信号
|
||||
config_saved = Signal() # 配置保存成功
|
||||
|
||||
def __init__(self, parent=None):
|
||||
super().__init__(parent)
|
||||
self.config_helper = ConfigHelper()
|
||||
self.field_widgets = {} # 存储字段控件
|
||||
self.show_sensitive = False
|
||||
|
||||
self._init_ui()
|
||||
self.load_config()
|
||||
|
||||
def _init_ui(self):
|
||||
"""初始化界面"""
|
||||
layout = QVBoxLayout(self)
|
||||
layout.setContentsMargins(16, 16, 16, 16)
|
||||
layout.setSpacing(16)
|
||||
|
||||
# 标题和按钮
|
||||
header_layout = QHBoxLayout()
|
||||
|
||||
title = QLabel("环境配置")
|
||||
title.setProperty("heading", True)
|
||||
header_layout.addWidget(title)
|
||||
|
||||
header_layout.addStretch()
|
||||
|
||||
self.show_sensitive_check = QCheckBox("显示敏感信息")
|
||||
self.show_sensitive_check.stateChanged.connect(self._toggle_sensitive)
|
||||
header_layout.addWidget(self.show_sensitive_check)
|
||||
|
||||
self.import_btn = QPushButton("导入")
|
||||
self.import_btn.setProperty("secondary", True)
|
||||
self.import_btn.clicked.connect(self._import_config)
|
||||
header_layout.addWidget(self.import_btn)
|
||||
|
||||
self.export_btn = QPushButton("导出")
|
||||
self.export_btn.setProperty("secondary", True)
|
||||
self.export_btn.clicked.connect(self._export_config)
|
||||
header_layout.addWidget(self.export_btn)
|
||||
|
||||
self.reload_btn = QPushButton("重新加载")
|
||||
self.reload_btn.setProperty("secondary", True)
|
||||
self.reload_btn.clicked.connect(self.load_config)
|
||||
header_layout.addWidget(self.reload_btn)
|
||||
|
||||
self.save_btn = QPushButton("保存")
|
||||
self.save_btn.clicked.connect(self._save_config)
|
||||
header_layout.addWidget(self.save_btn)
|
||||
|
||||
layout.addLayout(header_layout)
|
||||
|
||||
# 配置文件路径
|
||||
path_layout = QHBoxLayout()
|
||||
path_layout.addWidget(QLabel("配置文件:"))
|
||||
self.path_label = QLabel(str(self.config_helper.env_path))
|
||||
self.path_label.setProperty("subheading", True)
|
||||
path_layout.addWidget(self.path_label, 1)
|
||||
layout.addLayout(path_layout)
|
||||
|
||||
# 滚动区域
|
||||
scroll_area = QScrollArea()
|
||||
scroll_area.setWidgetResizable(True)
|
||||
scroll_area.setFrameShape(QFrame.NoFrame)
|
||||
layout.addWidget(scroll_area, 1)
|
||||
|
||||
# 配置组容器
|
||||
config_widget = QWidget()
|
||||
self.config_layout = QVBoxLayout(config_widget)
|
||||
self.config_layout.setSpacing(16)
|
||||
|
||||
# 创建各配置组
|
||||
self._create_config_groups()
|
||||
|
||||
# 弹性空间
|
||||
self.config_layout.addStretch()
|
||||
|
||||
scroll_area.setWidget(config_widget)
|
||||
|
||||
# 验证结果
|
||||
self.validation_label = QLabel()
|
||||
self.validation_label.setWordWrap(True)
|
||||
layout.addWidget(self.validation_label)
|
||||
|
||||
def _create_config_groups(self):
|
||||
"""创建配置分组"""
|
||||
for group_id, group_info in ENV_GROUPS.items():
|
||||
group = QGroupBox(group_info["title"])
|
||||
grid_layout = QGridLayout(group)
|
||||
|
||||
for row, key in enumerate(group_info["keys"]):
|
||||
# 标签
|
||||
label = QLabel(f"{key}:")
|
||||
label.setMinimumWidth(180)
|
||||
grid_layout.addWidget(label, row, 0)
|
||||
|
||||
# 输入框
|
||||
edit = QLineEdit()
|
||||
edit.setPlaceholderText(self._get_placeholder(key))
|
||||
|
||||
# 敏感字段处理
|
||||
if key in group_info.get("sensitive", []):
|
||||
edit.setEchoMode(QLineEdit.Password)
|
||||
edit.setProperty("sensitive", True)
|
||||
|
||||
edit.textChanged.connect(self._on_value_changed)
|
||||
grid_layout.addWidget(edit, row, 1)
|
||||
|
||||
# 存储控件引用
|
||||
self.field_widgets[key] = edit
|
||||
|
||||
self.config_layout.addWidget(group)
|
||||
|
||||
# 其他配置组(动态添加)
|
||||
self.other_group = QGroupBox("其他配置")
|
||||
self.other_layout = QGridLayout(self.other_group)
|
||||
self.other_group.setVisible(False)
|
||||
self.config_layout.addWidget(self.other_group)
|
||||
|
||||
def load_config(self):
|
||||
"""加载配置"""
|
||||
env_vars = self.config_helper.load_env()
|
||||
|
||||
# 更新已知字段
|
||||
for key, edit in self.field_widgets.items():
|
||||
value = env_vars.get(key, "")
|
||||
edit.blockSignals(True)
|
||||
edit.setText(value)
|
||||
edit.blockSignals(False)
|
||||
|
||||
# 处理其他字段
|
||||
known_keys = set(self.field_widgets.keys())
|
||||
other_keys = [k for k in env_vars.keys() if k not in known_keys]
|
||||
|
||||
# 清除旧的其他字段
|
||||
while self.other_layout.count():
|
||||
item = self.other_layout.takeAt(0)
|
||||
if item.widget():
|
||||
item.widget().deleteLater()
|
||||
|
||||
# 添加其他字段
|
||||
if other_keys:
|
||||
self.other_group.setVisible(True)
|
||||
for row, key in enumerate(sorted(other_keys)):
|
||||
label = QLabel(f"{key}:")
|
||||
self.other_layout.addWidget(label, row, 0)
|
||||
|
||||
edit = QLineEdit(env_vars[key])
|
||||
edit.textChanged.connect(self._on_value_changed)
|
||||
self.other_layout.addWidget(edit, row, 1)
|
||||
|
||||
self.field_widgets[key] = edit
|
||||
else:
|
||||
self.other_group.setVisible(False)
|
||||
|
||||
self._validate()
|
||||
|
||||
def _save_config(self):
|
||||
"""保存配置"""
|
||||
# 收集所有值
|
||||
env_vars = {}
|
||||
for key, edit in self.field_widgets.items():
|
||||
value = edit.text().strip()
|
||||
if value:
|
||||
env_vars[key] = value
|
||||
|
||||
# 验证
|
||||
errors = self.config_helper.validate_env(env_vars)
|
||||
if errors:
|
||||
reply = QMessageBox.question(
|
||||
self,
|
||||
"验证警告",
|
||||
"配置存在以下问题:\n\n" + "\n".join(f"• {e}" for e in errors) + "\n\n是否仍要保存?",
|
||||
QMessageBox.Yes | QMessageBox.No,
|
||||
QMessageBox.No
|
||||
)
|
||||
if reply == QMessageBox.No:
|
||||
return
|
||||
|
||||
# 保存
|
||||
if self.config_helper.save_env(env_vars):
|
||||
QMessageBox.information(self, "成功", "配置已保存")
|
||||
self.config_saved.emit()
|
||||
else:
|
||||
QMessageBox.critical(self, "错误", "保存配置失败")
|
||||
|
||||
def _import_config(self):
|
||||
"""导入配置"""
|
||||
file_path, _ = QFileDialog.getOpenFileName(
|
||||
self,
|
||||
"导入配置文件",
|
||||
"",
|
||||
"环境文件 (*.env);;所有文件 (*.*)"
|
||||
)
|
||||
if not file_path:
|
||||
return
|
||||
|
||||
try:
|
||||
from pathlib import Path
|
||||
temp_helper = ConfigHelper(Path(file_path))
|
||||
env_vars = temp_helper.load_env()
|
||||
|
||||
# 更新字段
|
||||
for key, value in env_vars.items():
|
||||
if key in self.field_widgets:
|
||||
self.field_widgets[key].setText(value)
|
||||
|
||||
QMessageBox.information(self, "成功", f"已导入 {len(env_vars)} 个配置项")
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "错误", f"导入失败: {e}")
|
||||
|
||||
def _export_config(self):
|
||||
"""导出配置"""
|
||||
file_path, _ = QFileDialog.getSaveFileName(
|
||||
self,
|
||||
"导出配置文件",
|
||||
".env.backup",
|
||||
"环境文件 (*.env);;所有文件 (*.*)"
|
||||
)
|
||||
if not file_path:
|
||||
return
|
||||
|
||||
try:
|
||||
from pathlib import Path
|
||||
|
||||
# 收集当前值
|
||||
env_vars = {}
|
||||
for key, edit in self.field_widgets.items():
|
||||
value = edit.text().strip()
|
||||
if value:
|
||||
env_vars[key] = value
|
||||
|
||||
# 保存到指定路径
|
||||
temp_helper = ConfigHelper(Path(file_path))
|
||||
if temp_helper.save_env(env_vars):
|
||||
QMessageBox.information(self, "成功", f"配置已导出到:\n{file_path}")
|
||||
else:
|
||||
QMessageBox.critical(self, "错误", "导出失败")
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "错误", f"导出失败: {e}")
|
||||
|
||||
def _toggle_sensitive(self, state: int):
|
||||
"""切换敏感信息显示"""
|
||||
self.show_sensitive = state == Qt.Checked
|
||||
|
||||
for key, edit in self.field_widgets.items():
|
||||
if edit.property("sensitive"):
|
||||
edit.setEchoMode(
|
||||
QLineEdit.Normal if self.show_sensitive else QLineEdit.Password
|
||||
)
|
||||
|
||||
def _on_value_changed(self):
|
||||
"""值变化时验证"""
|
||||
self._validate()
|
||||
|
||||
def _validate(self):
|
||||
"""验证配置"""
|
||||
env_vars = {}
|
||||
for key, edit in self.field_widgets.items():
|
||||
value = edit.text().strip()
|
||||
if value:
|
||||
env_vars[key] = value
|
||||
|
||||
errors = self.config_helper.validate_env(env_vars)
|
||||
|
||||
if errors:
|
||||
self.validation_label.setText("⚠ " + "; ".join(errors))
|
||||
self.validation_label.setProperty("status", "warning")
|
||||
else:
|
||||
self.validation_label.setText("✓ 配置验证通过")
|
||||
self.validation_label.setProperty("status", "success")
|
||||
|
||||
self.validation_label.style().unpolish(self.validation_label)
|
||||
self.validation_label.style().polish(self.validation_label)
|
||||
|
||||
@staticmethod
|
||||
def _get_placeholder(key: str) -> str:
|
||||
"""获取占位符提示"""
|
||||
placeholders = {
|
||||
"PG_DSN": "postgresql://user:password@host:5432/dbname",
|
||||
"PG_HOST": "localhost",
|
||||
"PG_PORT": "5432",
|
||||
"PG_NAME": "billiards",
|
||||
"PG_USER": "postgres",
|
||||
"PG_PASSWORD": "密码",
|
||||
"API_BASE": "https://pc.ficoo.vip/apiprod/admin/v1",
|
||||
"API_TOKEN": "Bearer token",
|
||||
"API_TIMEOUT": "20",
|
||||
"API_PAGE_SIZE": "200",
|
||||
"STORE_ID": "门店ID (数字)",
|
||||
"TIMEZONE": "Asia/Taipei",
|
||||
"EXPORT_ROOT": "export/JSON",
|
||||
"LOG_ROOT": "export/LOG",
|
||||
"FETCH_ROOT": "JSON 抓取输出目录",
|
||||
"INGEST_SOURCE_DIR": "本地 JSON 输入目录",
|
||||
"PIPELINE_FLOW": "FULL / FETCH_ONLY / INGEST_ONLY",
|
||||
"RUN_TASKS": "任务列表,逗号分隔",
|
||||
"OVERLAP_SECONDS": "3600",
|
||||
"WINDOW_START": "2025-07-01 00:00:00",
|
||||
"WINDOW_END": "2025-08-01 00:00:00",
|
||||
}
|
||||
return placeholders.get(key, "")
|
||||
Reference in New Issue
Block a user