bcc.py: unify paste/drop collision handling via shared _import_server() prompt
This commit is contained in:
@@ -374,6 +374,9 @@ class ServerEditor(QFrame):
|
||||
self.spawn_btn.setToolTip("Spawn the server for 3 s and report whether it starts cleanly")
|
||||
self.spawn_btn.clicked.connect(self._test_spawn)
|
||||
self.spawn_btn.setVisible(False)
|
||||
self.logs_btn = QPushButton("View logs")
|
||||
self.logs_btn.setToolTip("Open this server's MCP log in a read-only, auto-tailing viewer")
|
||||
self.logs_btn.clicked.connect(self._view_logs)
|
||||
self.details_btn = QPushButton("Details ▸")
|
||||
self.details_btn.setCheckable(True)
|
||||
self.details_btn.toggled.connect(self._toggle_diag)
|
||||
@@ -384,6 +387,7 @@ class ServerEditor(QFrame):
|
||||
dep.addWidget(self.fix_btn)
|
||||
dep.addWidget(self.test_btn)
|
||||
dep.addWidget(self.spawn_btn)
|
||||
dep.addWidget(self.logs_btn)
|
||||
dep.addWidget(self.details_btn)
|
||||
dep.addWidget(recheck)
|
||||
outer.addLayout(dep)
|
||||
@@ -728,6 +732,13 @@ class ServerEditor(QFrame):
|
||||
elif self.diag_card.isVisible():
|
||||
self.diag_text.setPlainText(self._full_diag_text())
|
||||
|
||||
def _view_logs(self):
|
||||
name = self.current_name()
|
||||
if not name:
|
||||
return
|
||||
dlg = LogViewerDialog(self.window(), name)
|
||||
dlg.exec()
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------- #
|
||||
# Arguments editor: one line = one argument, with a numbered gutter so that
|
||||
@@ -1104,7 +1115,7 @@ class MainWindow(QMainWindow):
|
||||
self.full_config: dict = {}
|
||||
self.servers: list[core.ServerEntry] = []
|
||||
self.current_profile: core.Profile | None = None
|
||||
self._loaded_mtime: float | None = None
|
||||
self._loaded_stat: core.ConfigStat | None = None
|
||||
self.dirty = False
|
||||
self._suppress_table = False
|
||||
self._suppress_sel = False
|
||||
@@ -1380,7 +1391,7 @@ class MainWindow(QMainWindow):
|
||||
return
|
||||
self.full_config = cfg
|
||||
repaired = True
|
||||
self._loaded_mtime = core.config_mtime(profile.path)
|
||||
self._loaded_stat = core.config_fingerprint(profile.path)
|
||||
self.current_profile = profile
|
||||
self.servers = core.extract_servers(self.full_config)
|
||||
self.dirty = False
|
||||
@@ -1620,34 +1631,38 @@ class MainWindow(QMainWindow):
|
||||
self._refresh_tables(select_index=min(idx, len(self.servers) - 1))
|
||||
self._mark_dirty()
|
||||
|
||||
def _import_server(self, name: str, data: dict) -> tuple[bool, bool]:
|
||||
"""
|
||||
Add a pasted/dropped `name`/`data` server to self.servers, resolving
|
||||
a name collision with an explicit prompt (never a silent overwrite).
|
||||
|
||||
Returns (added, replaced).
|
||||
"""
|
||||
existing = {s.name: i for i, s in enumerate(self.servers)}
|
||||
if name in existing:
|
||||
ans = QMessageBox.question(
|
||||
self,
|
||||
"Server exists",
|
||||
f"“{name}” already exists. Replace it?\n\nYes = replace · No = keep both (renamed)",
|
||||
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
||||
)
|
||||
if ans == QMessageBox.StandardButton.Yes:
|
||||
self.servers[existing[name]].data = data
|
||||
return False, True
|
||||
name = core.resolve_name_collision(name, {s.name for s in self.servers})
|
||||
self.servers.append(core.ServerEntry(name, data, True))
|
||||
return True, False
|
||||
|
||||
def paste_json(self):
|
||||
dlg = PasteDialog(self)
|
||||
if dlg.exec() != QDialog.DialogCode.Accepted or not dlg.result_servers:
|
||||
return
|
||||
self._push_undo()
|
||||
added, replaced = 0, 0
|
||||
existing = {s.name: i for i, s in enumerate(self.servers)}
|
||||
for name, data in dlg.result_servers.items():
|
||||
if name in existing:
|
||||
ans = QMessageBox.question(
|
||||
self,
|
||||
"Server exists",
|
||||
f"“{name}” already exists. Replace it?\n\nYes = replace · No = keep both (renamed)",
|
||||
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
||||
)
|
||||
if ans == QMessageBox.StandardButton.Yes:
|
||||
self.servers[existing[name]].data = data
|
||||
replaced += 1
|
||||
continue
|
||||
new = f"{name}-2"
|
||||
k = 3
|
||||
names = {s.name for s in self.servers}
|
||||
while new in names:
|
||||
new = f"{name}-{k}"
|
||||
k += 1
|
||||
name = new
|
||||
self.servers.append(core.ServerEntry(name, data, True))
|
||||
added += 1
|
||||
a, r = self._import_server(name, data)
|
||||
added += int(a)
|
||||
replaced += int(r)
|
||||
self._refresh_tables(select_index=len(self.servers) - 1)
|
||||
self._mark_dirty()
|
||||
self.status.setText(f"Imported {added} added, {replaced} replaced. Review and Save.")
|
||||
@@ -1719,11 +1734,14 @@ class MainWindow(QMainWindow):
|
||||
return
|
||||
|
||||
# Stale-file check: if the file changed on disk since we loaded it, prompt.
|
||||
disk_mtime = core.config_mtime(self.current_profile.path)
|
||||
# Compare mtime AND size (not mtime alone) so a concurrent external write
|
||||
# that lands within the mtime resolution window, or that restores the
|
||||
# original mtime, still gets caught.
|
||||
disk_stat = core.config_fingerprint(self.current_profile.path)
|
||||
if (
|
||||
disk_mtime is not None
|
||||
and self._loaded_mtime is not None
|
||||
and disk_mtime != self._loaded_mtime
|
||||
disk_stat is not None
|
||||
and self._loaded_stat is not None
|
||||
and disk_stat != self._loaded_stat
|
||||
):
|
||||
changed_keys, server_diff = core.external_change_summary(
|
||||
self.full_config, self.current_profile.path
|
||||
@@ -1744,7 +1762,7 @@ class MainWindow(QMainWindow):
|
||||
QMessageBox.critical(self, "Save failed", str(e))
|
||||
return
|
||||
self.full_config = fresh
|
||||
self._loaded_mtime = core.config_mtime(self.current_profile.path)
|
||||
self._loaded_stat = core.config_fingerprint(self.current_profile.path)
|
||||
self.current_profile.config_exists = True
|
||||
self.dirty = False
|
||||
self.save_btn.setEnabled(False)
|
||||
@@ -1762,7 +1780,7 @@ class MainWindow(QMainWindow):
|
||||
except Exception as e:
|
||||
QMessageBox.critical(self, "Save failed", str(e))
|
||||
return
|
||||
self._loaded_mtime = core.config_mtime(self.current_profile.path)
|
||||
self._loaded_stat = core.config_fingerprint(self.current_profile.path)
|
||||
self.current_profile.config_exists = True
|
||||
self.dirty = False
|
||||
self.save_btn.setEnabled(False)
|
||||
@@ -1835,15 +1853,16 @@ class MainWindow(QMainWindow):
|
||||
QMessageBox.warning(self, "Couldn't import", f"{Path(path).name}:\n{ex}")
|
||||
continue
|
||||
self._push_undo()
|
||||
added, replaced = 0, 0
|
||||
for name, data in servers.items():
|
||||
names = {s.name for s in self.servers}
|
||||
if name in names:
|
||||
name = f"{name}-imported"
|
||||
self.servers.append(core.ServerEntry(name, data, True))
|
||||
a, r = self._import_server(name, data)
|
||||
added += int(a)
|
||||
replaced += int(r)
|
||||
self._refresh_tables(select_index=len(self.servers) - 1)
|
||||
self._mark_dirty()
|
||||
self.status.setText(
|
||||
f"Imported {len(servers)} server(s) from {Path(path).name}. Review and Save."
|
||||
f"Imported {added} added, {replaced} replaced from {Path(path).name}. "
|
||||
"Review and Save."
|
||||
)
|
||||
break
|
||||
|
||||
|
||||
Reference in New Issue
Block a user