Merge remote-tracking branch 'origin/main' into feat/55-ux-polish
CI / Lint (ruff) (pull_request) Successful in 7s
CI / Tests (py3.10 / ubuntu-latest) (pull_request) Successful in 9s
CI / Tests (py3.12 / windows-latest) (pull_request) Successful in 20s
CI / Tests (py3.12 / ubuntu-latest) (pull_request) Successful in 9s
CI / Tests (py3.13 / ubuntu-latest) (pull_request) Successful in 9s
CI / Lint (ruff) (pull_request) Successful in 7s
CI / Tests (py3.10 / ubuntu-latest) (pull_request) Successful in 9s
CI / Tests (py3.12 / windows-latest) (pull_request) Successful in 20s
CI / Tests (py3.12 / ubuntu-latest) (pull_request) Successful in 9s
CI / Tests (py3.13 / ubuntu-latest) (pull_request) Successful in 9s
This commit is contained in:
@@ -39,6 +39,7 @@ from PySide6.QtWidgets import (
|
||||
QGridLayout,
|
||||
QHBoxLayout,
|
||||
QHeaderView,
|
||||
QInputDialog,
|
||||
QLabel,
|
||||
QLineEdit,
|
||||
QListWidget,
|
||||
@@ -1703,6 +1704,30 @@ class MainWindow(QMainWindow):
|
||||
search_row.addWidget(self.disable_all_btn)
|
||||
v.addLayout(search_row)
|
||||
|
||||
# Named server sets (issue #52): apply a saved Active/Disabled split
|
||||
# in one click. Sets live in the config file under _bccServerSets.
|
||||
sets_row = QHBoxLayout()
|
||||
sets_lbl = QLabel("Set")
|
||||
sets_lbl.setObjectName("muted")
|
||||
sets_row.addWidget(sets_lbl)
|
||||
self.sets_combo = QComboBox()
|
||||
self.sets_combo.setMinimumWidth(120)
|
||||
sets_row.addWidget(self.sets_combo, 1)
|
||||
self.apply_set_btn = QPushButton("Apply")
|
||||
self.apply_set_btn.setToolTip("Enable exactly this set's servers; disable the rest")
|
||||
self.apply_set_btn.clicked.connect(self._apply_selected_set)
|
||||
sets_row.addWidget(self.apply_set_btn)
|
||||
self.save_set_btn = QPushButton("Save set…")
|
||||
self.save_set_btn.setToolTip("Save the current Active/Disabled split as a named set")
|
||||
self.save_set_btn.clicked.connect(self._save_set)
|
||||
sets_row.addWidget(self.save_set_btn)
|
||||
self.del_set_btn = QPushButton("−")
|
||||
self.del_set_btn.setToolTip("Delete the selected set")
|
||||
self.del_set_btn.setMaximumWidth(32)
|
||||
self.del_set_btn.clicked.connect(self._delete_set)
|
||||
sets_row.addWidget(self.del_set_btn)
|
||||
v.addLayout(sets_row)
|
||||
|
||||
# Active and Disabled sections live in a vertical splitter so the user
|
||||
# can drag the divider instead of being stuck with a fixed-height
|
||||
# disabled list.
|
||||
@@ -1922,6 +1947,7 @@ class MainWindow(QMainWindow):
|
||||
self._undo_stack.clear()
|
||||
self.undo_btn.setEnabled(False)
|
||||
self._health.clear() # health results are per-profile; a fresh load invalidates them
|
||||
self._refresh_sets_combo() # sets are per-config; repopulate from the loaded file
|
||||
self._refresh_tables(select_index=0 if self.servers else -1)
|
||||
self._update_status(saved=False)
|
||||
if repaired:
|
||||
@@ -2021,6 +2047,73 @@ class MainWindow(QMainWindow):
|
||||
cur = self._current_index()
|
||||
self._refresh_tables(select_index=cur if cur >= 0 else None)
|
||||
|
||||
# --- named server sets (issue #52) ------------------------------------ #
|
||||
def _refresh_sets_combo(self, select: str | None = None):
|
||||
sets = core.list_server_sets(self.full_config)
|
||||
self.sets_combo.blockSignals(True)
|
||||
self.sets_combo.clear()
|
||||
for name in sorted(sets):
|
||||
self.sets_combo.addItem(name)
|
||||
if select is not None:
|
||||
idx = self.sets_combo.findText(select)
|
||||
if idx >= 0:
|
||||
self.sets_combo.setCurrentIndex(idx)
|
||||
self.sets_combo.blockSignals(False)
|
||||
has_sets = bool(sets)
|
||||
self.apply_set_btn.setEnabled(has_sets)
|
||||
self.del_set_btn.setEnabled(has_sets)
|
||||
|
||||
def _apply_selected_set(self):
|
||||
name = self.sets_combo.currentText()
|
||||
sets = core.list_server_sets(self.full_config)
|
||||
if name not in sets:
|
||||
return
|
||||
self._push_undo()
|
||||
missing = core.apply_server_set(self.servers, sets[name])
|
||||
self._refresh_tables(select_index=self._current_index() if self.servers else None)
|
||||
self._mark_dirty()
|
||||
on = sum(1 for s in self.servers if s.enabled)
|
||||
msg = f"Applied set “{name}” · {on} enabled. Review and Save."
|
||||
if missing:
|
||||
msg += f" ⚠ no longer in this config: {', '.join(missing)}"
|
||||
self.status.setText(msg)
|
||||
|
||||
def _save_set(self):
|
||||
name, ok = QInputDialog.getText(
|
||||
self,
|
||||
"Save server set",
|
||||
"Set name (saves which servers are currently Active):",
|
||||
text=self.sets_combo.currentText(),
|
||||
)
|
||||
name = name.strip()
|
||||
if not ok or not name:
|
||||
return
|
||||
if name in core.list_server_sets(self.full_config) and (
|
||||
QMessageBox.question(self, "Set exists", f"Replace set “{name}”?")
|
||||
!= QMessageBox.StandardButton.Yes
|
||||
):
|
||||
return
|
||||
members = core.save_server_set(self.full_config, name, self.servers)
|
||||
self._refresh_sets_combo(select=name)
|
||||
self._mark_dirty() # the set is written on the next Save
|
||||
self.status.setText(
|
||||
f"Set “{name}” saved ({len(members)} server(s)). Press Save to write it."
|
||||
)
|
||||
|
||||
def _delete_set(self):
|
||||
name = self.sets_combo.currentText()
|
||||
if not name:
|
||||
return
|
||||
if (
|
||||
QMessageBox.question(self, "Delete set", f"Delete set “{name}”?")
|
||||
!= QMessageBox.StandardButton.Yes
|
||||
):
|
||||
return
|
||||
if core.delete_server_set(self.full_config, name):
|
||||
self._refresh_sets_combo()
|
||||
self._mark_dirty()
|
||||
self.status.setText(f"Set “{name}” deleted. Press Save to write the change.")
|
||||
|
||||
# --- test all (spawn-test every enabled local server) ---------------- #
|
||||
def _test_all_servers(self):
|
||||
targets = [s for s in self.servers if s.enabled and s.kind == "stdio"]
|
||||
@@ -2339,8 +2432,13 @@ class MainWindow(QMainWindow):
|
||||
self.validation_lbl.setStyleSheet(f"color: {WARN};")
|
||||
self.save_btn.setEnabled(False)
|
||||
return False
|
||||
self.validation_lbl.setText("✓ valid")
|
||||
self.validation_lbl.setStyleSheet(f"color: {GOOD};")
|
||||
lint_warnings = core.lint_servers(self.servers)
|
||||
if lint_warnings:
|
||||
self.validation_lbl.setText(f"⚠ {lint_warnings[0]}")
|
||||
self.validation_lbl.setStyleSheet(f"color: {WARN};")
|
||||
else:
|
||||
self.validation_lbl.setText("✓ valid")
|
||||
self.validation_lbl.setStyleSheet(f"color: {GOOD};")
|
||||
self.save_btn.setEnabled(self.dirty)
|
||||
return True
|
||||
|
||||
|
||||
Reference in New Issue
Block a user