398 lines
14 KiB
Python
398 lines
14 KiB
Python
# -*- coding: utf-8 -*-
|
|
"""主窗口"""
|
|
|
|
from PySide6.QtWidgets import (
|
|
QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
|
|
QStackedWidget, QListWidget, QListWidgetItem,
|
|
QStatusBar, QLabel, QMessageBox, QSplitter
|
|
)
|
|
from PySide6.QtCore import Qt, QSize, Signal
|
|
from PySide6.QtGui import QIcon, QAction
|
|
|
|
from .widgets.task_panel import TaskPanel
|
|
from .widgets.task_manager import TaskManager
|
|
from .widgets.env_editor import EnvEditor
|
|
from .widgets.log_viewer import LogViewer
|
|
from .widgets.db_viewer import DBViewer
|
|
from .widgets.status_panel import StatusPanel
|
|
from .resources import load_stylesheet
|
|
|
|
|
|
class MainWindow(QMainWindow):
|
|
"""ETL GUI 主窗口"""
|
|
|
|
# 信号
|
|
status_message = Signal(str, int) # message, timeout_ms
|
|
|
|
def __init__(self):
|
|
super().__init__()
|
|
self.setWindowTitle("飞球 ETL 管理系统")
|
|
self.setMinimumSize(1200, 800)
|
|
self.resize(1400, 900)
|
|
|
|
# 应用样式
|
|
self.setStyleSheet(load_stylesheet())
|
|
|
|
# 初始化 UI
|
|
self._init_ui()
|
|
self._init_menu()
|
|
self._init_status_bar()
|
|
self._connect_signals()
|
|
|
|
# 默认选中第一项
|
|
self.nav_list.setCurrentRow(0)
|
|
|
|
# 首次显示标记
|
|
self._first_show = True
|
|
|
|
def showEvent(self, event):
|
|
"""窗口显示事件"""
|
|
super().showEvent(event)
|
|
if self._first_show:
|
|
self._first_show = False
|
|
# 延迟检查配置,让窗口先显示
|
|
from PySide6.QtCore import QTimer
|
|
QTimer.singleShot(100, self._check_config_on_startup)
|
|
|
|
def _init_ui(self):
|
|
"""初始化界面"""
|
|
# 中央部件
|
|
central_widget = QWidget()
|
|
self.setCentralWidget(central_widget)
|
|
|
|
# 主布局
|
|
main_layout = QHBoxLayout(central_widget)
|
|
main_layout.setContentsMargins(0, 0, 0, 0)
|
|
main_layout.setSpacing(0)
|
|
|
|
# 创建分割器
|
|
splitter = QSplitter(Qt.Horizontal)
|
|
main_layout.addWidget(splitter)
|
|
|
|
# 左侧导航栏
|
|
nav_widget = self._create_nav_widget()
|
|
splitter.addWidget(nav_widget)
|
|
|
|
# 右侧内容区
|
|
self.content_stack = QStackedWidget()
|
|
splitter.addWidget(self.content_stack)
|
|
|
|
# 设置分割比例
|
|
splitter.setSizes([200, 1200])
|
|
splitter.setStretchFactor(0, 0)
|
|
splitter.setStretchFactor(1, 1)
|
|
|
|
# 创建各个面板
|
|
self._create_panels()
|
|
|
|
def _create_nav_widget(self) -> QWidget:
|
|
"""创建导航侧边栏"""
|
|
nav_widget = QWidget()
|
|
nav_widget.setMaximumWidth(220)
|
|
nav_widget.setMinimumWidth(180)
|
|
|
|
layout = QVBoxLayout(nav_widget)
|
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
layout.setSpacing(0)
|
|
|
|
# 标题
|
|
title_label = QLabel(" ETL 控制台")
|
|
title_label.setProperty("heading", True)
|
|
title_label.setFixedHeight(60)
|
|
title_label.setAlignment(Qt.AlignVCenter)
|
|
layout.addWidget(title_label)
|
|
|
|
# 导航列表
|
|
self.nav_list = QListWidget()
|
|
self.nav_list.setObjectName("navList")
|
|
layout.addWidget(self.nav_list)
|
|
|
|
# 添加导航项
|
|
nav_items = [
|
|
("任务配置", "配置并执行 ETL 任务"),
|
|
("任务管理", "管理任务队列和历史记录"),
|
|
("环境配置", "编辑 .env 配置文件"),
|
|
("数据库", "查看数据库和执行查询"),
|
|
("ETL 状态", "查看 ETL 运行状态"),
|
|
("日志", "查看执行日志"),
|
|
]
|
|
|
|
for name, tooltip in nav_items:
|
|
item = QListWidgetItem(name)
|
|
item.setToolTip(tooltip)
|
|
item.setSizeHint(QSize(0, 44))
|
|
self.nav_list.addItem(item)
|
|
|
|
return nav_widget
|
|
|
|
def _create_panels(self):
|
|
"""创建各个功能面板"""
|
|
# 任务配置面板
|
|
self.task_panel = TaskPanel()
|
|
self.content_stack.addWidget(self.task_panel)
|
|
|
|
# 任务管理面板
|
|
self.task_manager = TaskManager()
|
|
self.content_stack.addWidget(self.task_manager)
|
|
|
|
# 环境配置面板
|
|
self.env_editor = EnvEditor()
|
|
self.content_stack.addWidget(self.env_editor)
|
|
|
|
# 数据库查看器
|
|
self.db_viewer = DBViewer()
|
|
self.content_stack.addWidget(self.db_viewer)
|
|
|
|
# ETL 状态面板
|
|
self.status_panel = StatusPanel()
|
|
self.content_stack.addWidget(self.status_panel)
|
|
|
|
# 日志面板
|
|
self.log_viewer = LogViewer()
|
|
self.content_stack.addWidget(self.log_viewer)
|
|
|
|
def _init_menu(self):
|
|
"""初始化菜单栏"""
|
|
menubar = self.menuBar()
|
|
|
|
# 文件菜单
|
|
file_menu = menubar.addMenu("文件(&F)")
|
|
|
|
refresh_action = QAction("刷新配置(&R)", self)
|
|
refresh_action.setShortcut("Ctrl+R")
|
|
refresh_action.triggered.connect(self._refresh_config)
|
|
file_menu.addAction(refresh_action)
|
|
|
|
settings_action = QAction("设置(&S)...", self)
|
|
settings_action.setShortcut("Ctrl+,")
|
|
settings_action.triggered.connect(self._show_settings)
|
|
file_menu.addAction(settings_action)
|
|
|
|
file_menu.addSeparator()
|
|
|
|
exit_action = QAction("退出(&X)", self)
|
|
exit_action.setShortcut("Ctrl+Q")
|
|
exit_action.triggered.connect(self.close)
|
|
file_menu.addAction(exit_action)
|
|
|
|
# 视图菜单
|
|
view_menu = menubar.addMenu("视图(&V)")
|
|
|
|
task_config_action = QAction("任务配置(&T)", self)
|
|
task_config_action.setShortcut("Ctrl+1")
|
|
task_config_action.triggered.connect(lambda: self._switch_panel(0))
|
|
view_menu.addAction(task_config_action)
|
|
|
|
task_manager_action = QAction("任务管理(&M)", self)
|
|
task_manager_action.setShortcut("Ctrl+2")
|
|
task_manager_action.triggered.connect(lambda: self._switch_panel(1))
|
|
view_menu.addAction(task_manager_action)
|
|
|
|
env_action = QAction("环境配置(&E)", self)
|
|
env_action.setShortcut("Ctrl+3")
|
|
env_action.triggered.connect(lambda: self._switch_panel(2))
|
|
view_menu.addAction(env_action)
|
|
|
|
db_action = QAction("数据库(&D)", self)
|
|
db_action.setShortcut("Ctrl+4")
|
|
db_action.triggered.connect(lambda: self._switch_panel(3))
|
|
view_menu.addAction(db_action)
|
|
|
|
status_action = QAction("ETL 状态(&S)", self)
|
|
status_action.setShortcut("Ctrl+5")
|
|
status_action.triggered.connect(lambda: self._switch_panel(4))
|
|
view_menu.addAction(status_action)
|
|
|
|
log_action = QAction("日志(&L)", self)
|
|
log_action.setShortcut("Ctrl+6")
|
|
log_action.triggered.connect(lambda: self._switch_panel(5))
|
|
view_menu.addAction(log_action)
|
|
|
|
# 帮助菜单
|
|
help_menu = menubar.addMenu("帮助(&H)")
|
|
|
|
about_action = QAction("关于(&A)", self)
|
|
about_action.triggered.connect(self._show_about)
|
|
help_menu.addAction(about_action)
|
|
|
|
def _init_status_bar(self):
|
|
"""初始化状态栏"""
|
|
self.status_bar = QStatusBar()
|
|
self.setStatusBar(self.status_bar)
|
|
|
|
# 连接状态
|
|
self.conn_status_label = QLabel("数据库: 未连接")
|
|
self.conn_status_label.setProperty("status", "warning")
|
|
self.status_bar.addPermanentWidget(self.conn_status_label)
|
|
|
|
# 任务状态
|
|
self.task_status_label = QLabel("任务: 空闲")
|
|
self.status_bar.addPermanentWidget(self.task_status_label)
|
|
|
|
# 默认消息
|
|
self.status_bar.showMessage("就绪", 3000)
|
|
|
|
def _connect_signals(self):
|
|
"""连接信号"""
|
|
# 导航切换
|
|
self.nav_list.currentRowChanged.connect(self._on_nav_changed)
|
|
|
|
# 任务面板信号
|
|
self.task_panel.task_started.connect(self._on_task_started)
|
|
self.task_panel.task_finished.connect(self._on_task_finished)
|
|
self.task_panel.log_message.connect(self.log_viewer.append_log)
|
|
self.task_panel.add_to_queue.connect(self._on_add_to_queue)
|
|
self.task_panel.create_schedule.connect(self._on_create_schedule)
|
|
|
|
# 任务管理器信号
|
|
self.task_manager.task_started.connect(self._on_task_started)
|
|
self.task_manager.task_finished.connect(self._on_task_finished)
|
|
self.task_manager.log_message.connect(self.log_viewer.append_log)
|
|
|
|
# 数据库连接状态
|
|
self.db_viewer.connection_changed.connect(self._on_db_connection_changed)
|
|
|
|
# 状态消息
|
|
self.status_message.connect(self._show_status_message)
|
|
|
|
def _on_nav_changed(self, index: int):
|
|
"""导航项切换"""
|
|
self.content_stack.setCurrentIndex(index)
|
|
|
|
def _switch_panel(self, index: int):
|
|
"""切换到指定面板"""
|
|
self.nav_list.setCurrentRow(index)
|
|
|
|
def _refresh_config(self):
|
|
"""刷新配置"""
|
|
self.env_editor.load_config()
|
|
self.task_panel.refresh_tasks()
|
|
self.status_bar.showMessage("配置已刷新", 3000)
|
|
|
|
def _on_task_started(self, task_info: str):
|
|
"""任务开始时"""
|
|
self.task_status_label.setText(f"任务: 执行中 - {task_info}")
|
|
self.task_status_label.setProperty("status", "info")
|
|
self.task_status_label.style().unpolish(self.task_status_label)
|
|
self.task_status_label.style().polish(self.task_status_label)
|
|
|
|
def _on_task_finished(self, success: bool, message: str):
|
|
"""任务完成时"""
|
|
if success:
|
|
self.task_status_label.setText("任务: 完成")
|
|
self.task_status_label.setProperty("status", "success")
|
|
else:
|
|
self.task_status_label.setText("任务: 失败")
|
|
self.task_status_label.setProperty("status", "error")
|
|
self.task_status_label.style().unpolish(self.task_status_label)
|
|
self.task_status_label.style().polish(self.task_status_label)
|
|
self.status_bar.showMessage(message, 5000)
|
|
|
|
def _on_db_connection_changed(self, connected: bool, message: str):
|
|
"""数据库连接状态变化"""
|
|
if connected:
|
|
self.conn_status_label.setText("数据库: 已连接")
|
|
self.conn_status_label.setProperty("status", "success")
|
|
else:
|
|
self.conn_status_label.setText("数据库: 未连接")
|
|
self.conn_status_label.setProperty("status", "warning")
|
|
self.conn_status_label.style().unpolish(self.conn_status_label)
|
|
self.conn_status_label.style().polish(self.conn_status_label)
|
|
if message:
|
|
self.status_bar.showMessage(message, 3000)
|
|
|
|
def _show_status_message(self, message: str, timeout: int):
|
|
"""显示状态栏消息"""
|
|
self.status_bar.showMessage(message, timeout)
|
|
|
|
def _on_add_to_queue(self, config):
|
|
"""添加任务到队列"""
|
|
task_id = self.task_manager.add_task(config)
|
|
self.status_bar.showMessage(f"任务已添加到队列 (ID: {task_id})", 3000)
|
|
|
|
def _on_create_schedule(self, name: str, task_codes: list, task_config: dict):
|
|
"""创建调度任务"""
|
|
# 打开调度编辑对话框
|
|
from .widgets.task_manager import ScheduleEditDialog
|
|
from .models.schedule_model import ScheduledTask, ScheduleConfig
|
|
import uuid
|
|
|
|
# 创建一个预填充的调度任务
|
|
task = ScheduledTask(
|
|
id=str(uuid.uuid4())[:8],
|
|
name=name,
|
|
task_codes=task_codes,
|
|
schedule=ScheduleConfig(),
|
|
task_config=task_config,
|
|
)
|
|
|
|
# 打开编辑对话框
|
|
dialog = ScheduleEditDialog(task=task, parent=self)
|
|
if dialog.exec():
|
|
updated_task = dialog.get_task()
|
|
if updated_task:
|
|
self.task_manager.schedule_store.add_task(updated_task)
|
|
self.task_manager._refresh_schedule_table()
|
|
self.status_bar.showMessage(f"调度任务已创建: {updated_task.name}", 3000)
|
|
# 切换到任务管理面板的调度选项卡
|
|
self._switch_panel(1)
|
|
|
|
def _show_settings(self):
|
|
"""显示设置对话框"""
|
|
from .widgets.settings_dialog import SettingsDialog
|
|
dialog = SettingsDialog(self)
|
|
if dialog.exec():
|
|
# 重新加载配置
|
|
self._refresh_config()
|
|
self.status_bar.showMessage("设置已保存", 3000)
|
|
|
|
def _check_config_on_startup(self):
|
|
"""启动时检查配置"""
|
|
from .utils.app_settings import app_settings
|
|
if not app_settings.is_configured():
|
|
QMessageBox.information(
|
|
self,
|
|
"首次配置",
|
|
"欢迎使用 ETL 管理系统!\n\n"
|
|
"请先配置 ETL 项目路径,否则无法执行任务。\n\n"
|
|
"点击 文件 → 设置 进行配置。"
|
|
)
|
|
|
|
def _show_about(self):
|
|
"""显示关于对话框"""
|
|
QMessageBox.about(
|
|
self,
|
|
"关于 飞球 ETL 管理系统",
|
|
"<h3>飞球 ETL 管理系统</h3>"
|
|
"<p>版本: 1.0.0</p>"
|
|
"<p>一个用于管理台球场门店数据 ETL 的图形化工具。</p>"
|
|
"<p>功能包括:</p>"
|
|
"<ul>"
|
|
"<li>任务配置与执行</li>"
|
|
"<li>环境变量管理</li>"
|
|
"<li>数据库查询</li>"
|
|
"<li>ETL 状态监控</li>"
|
|
"</ul>"
|
|
)
|
|
|
|
def closeEvent(self, event):
|
|
"""关闭事件"""
|
|
# 检查是否有正在运行的任务
|
|
if hasattr(self, 'task_panel') and self.task_panel.is_running():
|
|
reply = QMessageBox.question(
|
|
self,
|
|
"确认退出",
|
|
"当前有任务正在执行,确定要退出吗?",
|
|
QMessageBox.Yes | QMessageBox.No,
|
|
QMessageBox.No
|
|
)
|
|
if reply == QMessageBox.No:
|
|
event.ignore()
|
|
return
|
|
|
|
# 关闭数据库连接
|
|
if hasattr(self, 'db_viewer'):
|
|
self.db_viewer.close_connection()
|
|
|
|
event.accept()
|