diff --git a/mods/silo b/mods/silo index 3a67d2082b..17a10ab1b6 160000 --- a/mods/silo +++ b/mods/silo @@ -1 +1 @@ -Subproject commit 3a67d2082b93173195e073ffc974c22e3db6a370 +Subproject commit 17a10ab1b68d13227d2b90e92163353f68e7fdc1 diff --git a/src/Mod/Create/InitGui.py b/src/Mod/Create/InitGui.py index 0caf77f0ab..c9d0016c6b 100644 --- a/src/Mod/Create/InitGui.py +++ b/src/Mod/Create/InitGui.py @@ -95,278 +95,33 @@ def _setup_silo_menu(): FreeCAD.Console.PrintLog(f"Create: Silo menu setup skipped: {e}\n") -class SiloActivityPanel: - """Interactive database browser with search, item table, and details pane.""" +def _setup_silo_auth_panel(): + """Dock the Silo authentication panel in the right-hand side panel.""" + try: + from PySide import QtCore, QtWidgets - def __init__(self): - from PySide import QtCore, QtGui, QtWidgets + mw = FreeCADGui.getMainWindow() + if mw is None: + return - self._QtCore = QtCore - self._QtWidgets = QtWidgets + # Don't create duplicate panels + if mw.findChild(QtWidgets.QDockWidget, "SiloDatabaseAuth"): + return import silo_commands - self._client = silo_commands._client - self._SiloSync = silo_commands.SiloSync + auth = silo_commands.SiloAuthDockWidget() - self.widget = QtWidgets.QWidget() - self._items_data = [] - self._debounce_timer = QtCore.QTimer() - self._debounce_timer.setSingleShot(True) - self._debounce_timer.setInterval(300) - self._debounce_timer.timeout.connect(self._do_refresh) - - self._build_ui() - self._do_refresh() - - def _build_ui(self): - QtWidgets = self._QtWidgets - QtCore = self._QtCore - - layout = QtWidgets.QVBoxLayout(self.widget) - layout.setContentsMargins(4, 4, 4, 4) - layout.setSpacing(4) - - # --- Search / filter toolbar --- - toolbar = QtWidgets.QHBoxLayout() - toolbar.setSpacing(4) - - self._search = QtWidgets.QLineEdit() - self._search.setPlaceholderText("Search parts...") - self._search.setClearButtonEnabled(True) - self._search.textChanged.connect(self._on_search_changed) - toolbar.addWidget(self._search, 1) - - self._type_filter = QtWidgets.QComboBox() - self._type_filter.addItem("All", "") - self._type_filter.addItem("Part", "part") - self._type_filter.addItem("Assembly", "assembly") - self._type_filter.setFixedWidth(90) - self._type_filter.currentIndexChanged.connect(self._on_search_changed) - toolbar.addWidget(self._type_filter) - - refresh_btn = QtWidgets.QPushButton("Refresh") - refresh_btn.setFixedWidth(60) - refresh_btn.clicked.connect(self._do_refresh) - toolbar.addWidget(refresh_btn) - - layout.addLayout(toolbar) - - # --- Item table --- - self._table = QtWidgets.QTableWidget() - self._table.setColumnCount(4) - self._table.setHorizontalHeaderLabels( - ["Part Number", "Description", "Type", "Updated"] - ) - header = self._table.horizontalHeader() - header.setSectionResizeMode(0, QtWidgets.QHeaderView.ResizeToContents) - header.setSectionResizeMode(1, QtWidgets.QHeaderView.Stretch) - header.setSectionResizeMode(2, QtWidgets.QHeaderView.ResizeToContents) - header.setSectionResizeMode(3, QtWidgets.QHeaderView.ResizeToContents) - self._table.verticalHeader().setVisible(False) - self._table.setSelectionBehavior(QtWidgets.QAbstractItemView.SelectRows) - self._table.setSelectionMode(QtWidgets.QAbstractItemView.SingleSelection) - self._table.setEditTriggers(QtWidgets.QAbstractItemView.NoEditTriggers) - self._table.itemSelectionChanged.connect(self._on_selection_changed) - self._table.doubleClicked.connect(self._on_open) - layout.addWidget(self._table, 1) - - # --- Details pane --- - self._details_group = QtWidgets.QGroupBox("Part Details") - details_layout = QtWidgets.QVBoxLayout(self._details_group) - details_layout.setSpacing(2) - - form = QtWidgets.QFormLayout() - form.setHorizontalSpacing(12) - self._detail_pn = QtWidgets.QLabel("-") - self._detail_pn.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse) - self._detail_desc = QtWidgets.QLabel("-") - self._detail_desc.setWordWrap(True) - self._detail_desc.setTextInteractionFlags(QtCore.Qt.TextSelectableByMouse) - self._detail_type = QtWidgets.QLabel("-") - self._detail_rev = QtWidgets.QLabel("-") - self._detail_updated = QtWidgets.QLabel("-") - self._detail_projects = QtWidgets.QLabel("-") - self._detail_projects.setWordWrap(True) - form.addRow("Part Number:", self._detail_pn) - form.addRow("Description:", self._detail_desc) - form.addRow("Type:", self._detail_type) - form.addRow("Revision:", self._detail_rev) - form.addRow("Updated:", self._detail_updated) - form.addRow("Projects:", self._detail_projects) - details_layout.addLayout(form) - - btn_row = QtWidgets.QHBoxLayout() - self._open_btn = QtWidgets.QPushButton("Open") - self._open_btn.clicked.connect(self._on_open) - self._info_btn = QtWidgets.QPushButton("Info...") - self._info_btn.clicked.connect(self._on_info) - btn_row.addWidget(self._open_btn) - btn_row.addStretch() - btn_row.addWidget(self._info_btn) - details_layout.addLayout(btn_row) - - self._details_group.setVisible(False) - layout.addWidget(self._details_group) - - # --- Actions --- - - def _on_search_changed(self): - self._debounce_timer.start() - - def _do_refresh(self): - search = self._search.text().strip() - item_type = self._type_filter.currentData() or "" - try: - items = self._client.list_items(search=search, item_type=item_type) - if not isinstance(items, list): - items = [] - except Exception: - items = None - - self._items_data = items if items is not None else [] - self._populate_table(items) - - def _populate_table(self, items): - QtWidgets = self._QtWidgets - self._table.setRowCount(0) - self._details_group.setVisible(False) - - if items is None: - self._table.setRowCount(1) - self._table.setSpan(0, 0, 1, 4) - msg = QtWidgets.QTableWidgetItem("(Unable to connect to Silo database)") - msg.setForeground( - self._table.palette().color( - self._table.palette().Disabled, self._table.palette().Text - ) - ) - self._table.setItem(0, 0, msg) - return - - if not items: - self._table.setRowCount(1) - self._table.setSpan(0, 0, 1, 4) - msg = QtWidgets.QTableWidgetItem("(No items found)") - msg.setForeground( - self._table.palette().color( - self._table.palette().Disabled, self._table.palette().Text - ) - ) - self._table.setItem(0, 0, msg) - return - - self._table.setRowCount(len(items)) - for i, item in enumerate(items): - pn = item.get("part_number", "") - desc = item.get("description", "") - itype = item.get("item_type", "") - updated = item.get("updated_at", "")[:10] if item.get("updated_at") else "" - - self._table.setItem(i, 0, QtWidgets.QTableWidgetItem(pn)) - self._table.setItem(i, 1, QtWidgets.QTableWidgetItem(desc)) - self._table.setItem(i, 2, QtWidgets.QTableWidgetItem(itype)) - self._table.setItem(i, 3, QtWidgets.QTableWidgetItem(updated)) - - def _selected_item(self): - rows = self._table.selectionModel().selectedRows() - if not rows: - return None - row = rows[0].row() - if row < len(self._items_data): - return self._items_data[row] - return None - - def _on_selection_changed(self): - item = self._selected_item() - if not item: - self._details_group.setVisible(False) - return - - self._detail_pn.setText(item.get("part_number", "-")) - self._detail_desc.setText(item.get("description", "-") or "-") - self._detail_type.setText(item.get("item_type", "-")) - self._detail_rev.setText(str(item.get("current_revision", "-"))) - updated = item.get("updated_at", "") - self._detail_updated.setText(updated[:10] if updated else "-") - - # Fetch projects in background-safe way (quick API call) - pn = item.get("part_number", "") - try: - projects = self._client.get_item_projects(pn) - codes = [p.get("code", "") for p in projects if p.get("code")] - self._detail_projects.setText(", ".join(codes) if codes else "-") - except Exception: - self._detail_projects.setText("-") - - self._details_group.setVisible(True) - - def _on_open(self): - item = self._selected_item() - if not item: - return - pn = item.get("part_number", "") - if not pn: - return - try: - sync = self._SiloSync(self._client) - sync.open_item(pn) - except Exception as e: - FreeCAD.Console.PrintError(f"Failed to open {pn}: {e}\n") - - def _on_info(self): - item = self._selected_item() - if not item: - return - pn = item.get("part_number", "") - if not pn: - return - try: - from PySide import QtGui - - item_data = self._client.get_item(pn) - revisions = self._client.get_revisions(pn) - - try: - projects = self._client.get_item_projects(pn) - project_codes = [p.get("code", "") for p in projects if p.get("code")] - except Exception: - project_codes = [] - - msg = f"
Type: {item_data.get('item_type', '-')}
" - msg += f"Description: {item_data.get('description', '-')}
" - msg += f"Projects: {', '.join(project_codes) if project_codes else 'None'}
" - msg += f"Current Revision: {item_data.get('current_revision', 1)}
" - - has_file, _ = self._client.has_file(pn) - msg += f"File in MinIO: {'Yes' if has_file else 'No'}
" - - if revisions: - current_status = revisions[0].get("status", "draft") - msg += f"Current Status: {current_status}
" - - msg += "| Rev | Status | Date | File | Comment |
|---|---|---|---|---|
| {rev['revision_number']} | {status} | {date} | {file_icon} | {comment} |