""" Dashboard login and startup window for the Cluster4NPU UI application. This module provides the main entry point window that allows users to create new pipelines or load existing ones. It serves as the application launcher and recent files manager. Main Components: - DashboardLogin: Main startup window with project management - Recent files management and display - New pipeline creation workflow - Application navigation and routing Usage: from cluster4npu_ui.ui.windows.login import DashboardLogin dashboard = DashboardLogin() dashboard.show() """ import os from pathlib import Path from PyQt5.QtWidgets import ( QWidget, QVBoxLayout, QHBoxLayout, QLabel, QPushButton, QListWidget, QListWidgetItem, QMessageBox, QFileDialog, QFrame, QSizePolicy, QSpacerItem ) from PyQt5.QtCore import Qt, pyqtSignal from PyQt5.QtGui import QFont, QPixmap, QIcon from cluster4npu_ui.config.settings import get_settings class DashboardLogin(QWidget): """ Main startup window for the Cluster4NPU application. Provides options to create new pipelines, load existing ones, and manage recent files. Serves as the application's main entry point. """ # Signals pipeline_requested = pyqtSignal(str) # Emitted when user wants to open/create pipeline def __init__(self): super().__init__() self.settings = get_settings() self.setup_ui() self.load_recent_files() # Connect to integrated dashboard (will be implemented) self.dashboard_window = None def setup_ui(self): """Initialize the user interface.""" self.setWindowTitle("Cluster4NPU - Pipeline Dashboard") self.setMinimumSize(800, 600) self.resize(1000, 700) # Main layout main_layout = QVBoxLayout(self) main_layout.setSpacing(20) main_layout.setContentsMargins(40, 40, 40, 40) # Header section self.create_header(main_layout) # Content section content_layout = QHBoxLayout() content_layout.setSpacing(30) # Left side - Actions self.create_actions_panel(content_layout) # Right side - Recent files self.create_recent_files_panel(content_layout) main_layout.addLayout(content_layout) # Footer self.create_footer(main_layout) def create_header(self, parent_layout): """Create the header section with title and description.""" header_frame = QFrame() header_frame.setStyleSheet(""" QFrame { background-color: #313244; border-radius: 12px; padding: 20px; } """) header_layout = QVBoxLayout(header_frame) # Title title_label = QLabel("Cluster4NPU Pipeline Designer") title_label.setFont(QFont("Arial", 24, QFont.Bold)) title_label.setStyleSheet("color: #89b4fa; margin-bottom: 10px;") title_label.setAlignment(Qt.AlignCenter) header_layout.addWidget(title_label) # Subtitle subtitle_label = QLabel("Design, configure, and deploy high-performance ML inference pipelines") subtitle_label.setFont(QFont("Arial", 14)) subtitle_label.setStyleSheet("color: #cdd6f4; margin-bottom: 5px;") subtitle_label.setAlignment(Qt.AlignCenter) header_layout.addWidget(subtitle_label) # Version info version_label = QLabel("Version 1.0.0 - Multi-stage NPU Pipeline System") version_label.setFont(QFont("Arial", 10)) version_label.setStyleSheet("color: #6c7086;") version_label.setAlignment(Qt.AlignCenter) header_layout.addWidget(version_label) parent_layout.addWidget(header_frame) def create_actions_panel(self, parent_layout): """Create the actions panel with main buttons.""" actions_frame = QFrame() actions_frame.setStyleSheet(""" QFrame { background-color: #313244; border-radius: 12px; padding: 20px; } """) actions_frame.setMaximumWidth(350) actions_layout = QVBoxLayout(actions_frame) # Panel title actions_title = QLabel("Get Started") actions_title.setFont(QFont("Arial", 16, QFont.Bold)) actions_title.setStyleSheet("color: #f9e2af; margin-bottom: 20px;") actions_layout.addWidget(actions_title) # Create new pipeline button self.new_pipeline_btn = QPushButton("Create New Pipeline") self.new_pipeline_btn.setFont(QFont("Arial", 12, QFont.Bold)) self.new_pipeline_btn.setStyleSheet(""" QPushButton { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #89b4fa, stop:1 #74c7ec); color: #1e1e2e; border: none; padding: 15px 20px; border-radius: 10px; margin-bottom: 10px; } QPushButton:hover { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #a6c8ff, stop:1 #89dceb); } """) self.new_pipeline_btn.clicked.connect(self.create_new_pipeline) actions_layout.addWidget(self.new_pipeline_btn) # Open existing pipeline button self.open_pipeline_btn = QPushButton("Open Existing Pipeline") self.open_pipeline_btn.setFont(QFont("Arial", 12)) self.open_pipeline_btn.setStyleSheet(""" QPushButton { background-color: #45475a; color: #cdd6f4; border: 2px solid #585b70; padding: 15px 20px; border-radius: 10px; margin-bottom: 10px; } QPushButton:hover { background-color: #585b70; border-color: #89b4fa; } """) self.open_pipeline_btn.clicked.connect(self.open_existing_pipeline) actions_layout.addWidget(self.open_pipeline_btn) # Import from template button # self.import_template_btn = QPushButton("Import from Template") # self.import_template_btn.setFont(QFont("Arial", 12)) # self.import_template_btn.setStyleSheet(""" # QPushButton { # background-color: #45475a; # color: #cdd6f4; # border: 2px solid #585b70; # padding: 15px 20px; # border-radius: 10px; # margin-bottom: 20px; # } # QPushButton:hover { # background-color: #585b70; # border-color: #a6e3a1; # } # """) # self.import_template_btn.clicked.connect(self.import_template) # actions_layout.addWidget(self.import_template_btn) # Additional info # info_label = QLabel("Start by creating a new pipeline or opening an existing .mflow file") # info_label.setFont(QFont("Arial", 10)) # info_label.setStyleSheet("color: #6c7086; padding: 10px; background-color: #45475a; border-radius: 8px;") # info_label.setWordWrap(True) # actions_layout.addWidget(info_label) # Spacer actions_layout.addItem(QSpacerItem(20, 40, QSizePolicy.Minimum, QSizePolicy.Expanding)) parent_layout.addWidget(actions_frame) def create_recent_files_panel(self, parent_layout): """Create the recent files panel.""" recent_frame = QFrame() recent_frame.setStyleSheet(""" QFrame { background-color: #313244; border-radius: 12px; padding: 20px; } """) recent_layout = QVBoxLayout(recent_frame) # Panel title with clear button title_layout = QHBoxLayout() recent_title = QLabel("Recent Pipelines") recent_title.setFont(QFont("Arial", 16, QFont.Bold)) recent_title.setStyleSheet("color: #f9e2af;") title_layout.addWidget(recent_title) title_layout.addItem(QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)) self.clear_recent_btn = QPushButton("Clear All") self.clear_recent_btn.setStyleSheet(""" QPushButton { background-color: #f38ba8; color: #1e1e2e; border: none; padding: 5px 10px; border-radius: 5px; font-size: 10px; } QPushButton:hover { background-color: #f2d5de; } """) self.clear_recent_btn.clicked.connect(self.clear_recent_files) title_layout.addWidget(self.clear_recent_btn) recent_layout.addLayout(title_layout) # Recent files list self.recent_files_list = QListWidget() self.recent_files_list.setStyleSheet(""" QListWidget { background-color: #1e1e2e; border: 2px solid #45475a; border-radius: 8px; padding: 5px; } QListWidget::item { padding: 10px; border-bottom: 1px solid #45475a; border-radius: 4px; margin: 2px; } QListWidget::item:hover { background-color: #383a59; } QListWidget::item:selected { background: qlineargradient(x1:0, y1:0, x2:1, y2:0, stop:0 #89b4fa, stop:1 #74c7ec); color: #1e1e2e; } """) self.recent_files_list.itemDoubleClicked.connect(self.open_recent_file) recent_layout.addWidget(self.recent_files_list) parent_layout.addWidget(recent_frame) def create_footer(self, parent_layout): """Create the footer with additional options.""" footer_layout = QHBoxLayout() # Documentation link docs_btn = QPushButton("Documentation") docs_btn.setStyleSheet(""" QPushButton { background-color: transparent; color: #89b4fa; border: none; text-decoration: underline; padding: 5px; } QPushButton:hover { color: #a6c8ff; } """) footer_layout.addWidget(docs_btn) footer_layout.addItem(QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)) # Examples link examples_btn = QPushButton("Examples") examples_btn.setStyleSheet(""" QPushButton { background-color: transparent; color: #a6e3a1; border: none; text-decoration: underline; padding: 5px; } QPushButton:hover { color: #b3f5c0; } """) footer_layout.addWidget(examples_btn) footer_layout.addItem(QSpacerItem(40, 20, QSizePolicy.Expanding, QSizePolicy.Minimum)) # Settings link settings_btn = QPushButton("Settings") settings_btn.setStyleSheet(""" QPushButton { background-color: transparent; color: #f9e2af; border: none; text-decoration: underline; padding: 5px; } QPushButton:hover { color: #fdeaa7; } """) footer_layout.addWidget(settings_btn) parent_layout.addLayout(footer_layout) def load_recent_files(self): """Load and display recent files.""" self.recent_files_list.clear() recent_files = self.settings.get_recent_files() if not recent_files: item = QListWidgetItem("No recent files") item.setFlags(Qt.NoItemFlags) # Make it non-selectable item.setData(Qt.UserRole, None) self.recent_files_list.addItem(item) return for file_path in recent_files: if os.path.exists(file_path): # Extract filename and directory file_name = os.path.basename(file_path) file_dir = os.path.dirname(file_path) # Create list item item_text = f"{file_name}\n{file_dir}" item = QListWidgetItem(item_text) item.setData(Qt.UserRole, file_path) item.setToolTip(file_path) self.recent_files_list.addItem(item) else: # Remove non-existent files self.settings.remove_recent_file(file_path) def create_new_pipeline(self): """Create a new pipeline.""" try: # Import here to avoid circular imports from cluster4npu_ui.ui.dialogs.create_pipeline import CreatePipelineDialog dialog = CreatePipelineDialog(self) if dialog.exec_() == dialog.Accepted: project_info = dialog.get_project_info() self.launch_pipeline_editor(project_info.get('name', 'Untitled')) except ImportError: # Fallback: directly launch editor self.launch_pipeline_editor("New Pipeline") def open_existing_pipeline(self): """Open an existing pipeline file.""" file_path, _ = QFileDialog.getOpenFileName( self, "Open Pipeline File", self.settings.get_default_project_location(), "Pipeline files (*.mflow);;All files (*)" ) if file_path: self.settings.add_recent_file(file_path) self.load_recent_files() self.launch_pipeline_editor(file_path) def open_recent_file(self, item: QListWidgetItem): """Open a recent file.""" file_path = item.data(Qt.UserRole) if file_path and os.path.exists(file_path): self.launch_pipeline_editor(file_path) elif file_path: QMessageBox.warning(self, "File Not Found", f"The file '{file_path}' could not be found.") self.settings.remove_recent_file(file_path) self.load_recent_files() def import_template(self): """Import a pipeline from template.""" QMessageBox.information( self, "Import Template", "Template import functionality will be available in a future version." ) def clear_recent_files(self): """Clear all recent files.""" reply = QMessageBox.question( self, "Clear Recent Files", "Are you sure you want to clear all recent files?", QMessageBox.Yes | QMessageBox.No, QMessageBox.No ) if reply == QMessageBox.Yes: self.settings.clear_recent_files() self.load_recent_files() def launch_pipeline_editor(self, project_info): """Launch the main pipeline editor.""" try: # Import here to avoid circular imports from cluster4npu_ui.ui.windows.dashboard import IntegratedPipelineDashboard self.dashboard_window = IntegratedPipelineDashboard() # Load project if it's a file path if isinstance(project_info, str) and os.path.exists(project_info): # Load the pipeline file try: self.dashboard_window.load_pipeline_file(project_info) except Exception as e: QMessageBox.warning( self, "File Load Warning", f"Could not load pipeline file: {e}\n\n" "Opening with empty pipeline instead." ) self.dashboard_window.show() self.hide() # Hide the login window except ImportError as e: QMessageBox.critical( self, "Error", f"Could not launch pipeline editor: {e}\n\n" "Please ensure all required modules are available." ) def closeEvent(self, event): """Handle window close event.""" # Save window geometry self.settings.set_window_geometry(self.saveGeometry()) event.accept()