fix: load unnamed NetBox devices safely

Skip devices without names and add the shared customer profile and NetBox target controls to the Switches tab.
This commit is contained in:
2026-07-30 09:45:49 +02:00
parent cf9f2ac53a
commit 08add7982a
3 changed files with 57 additions and 16 deletions
+56 -16
View File
@@ -842,6 +842,8 @@ class NetBoxVMImporter:
self.vrfs = []
self.platforms = []
self.profile_name = tk.StringVar()
self.netbox_url_var = tk.StringVar()
self.netbox_token_var = tk.StringVar()
self.netbox_ignore_ssl = tk.BooleanVar(value=False)
self.status_text = tk.StringVar(value="Bereit")
self.store = ProfileStore()
@@ -925,8 +927,8 @@ class NetBoxVMImporter:
ttk.Button(source_buttons, text="Bearbeiten", command=self.edit_source).pack(side="left", padx=5)
ttk.Button(source_buttons, text="Entfernen", command=self.remove_source).pack(side="left")
self.netbox_url = self.add_field(netbox_card, 0, "URL")
self.netbox_token = self.add_field(netbox_card, 1, "API-Token", secret=True)
self.netbox_url = self.add_field(netbox_card, 0, "URL", variable=self.netbox_url_var)
self.netbox_token = self.add_field(netbox_card, 1, "API-Token", secret=True, variable=self.netbox_token_var)
ttk.Checkbutton(netbox_card, text="Zertifikatsprüfung deaktivieren", variable=self.netbox_ignore_ssl).grid(
row=2, column=0, columnspan=2, sticky="w", pady=(10, 0)
)
@@ -977,21 +979,48 @@ class NetBoxVMImporter:
main = ttk.Frame(parent, padding=18)
main.pack(fill="both", expand=True)
main.columnconfigure(0, weight=1)
main.rowconfigure(2, weight=2)
main.rowconfigure(4, weight=3)
main.rowconfigure(3, weight=2)
main.rowconfigure(5, weight=2)
header = ttk.Frame(main)
header.grid(row=0, column=0, sticky="ew", pady=(0, 12))
ttk.Label(header, text="HPE & Aruba Switche", style="Title.TLabel").pack(anchor="w")
ttk.Label(
header,
text="Bestehende NetBox-Geräte per SSH auslesen · Profil: ",
text="Bestehende NetBox-Geräte per SSH auslesen und dokumentieren",
style="Subtitle.TLabel",
).pack(side="left")
ttk.Label(header, textvariable=self.profile_name, style="Subtitle.TLabel").pack(side="left")
).pack(anchor="w")
settings = ttk.Panedwindow(main, orient="horizontal")
settings.grid(row=1, column=0, sticky="ew", pady=(0, 10))
profile_card = ttk.LabelFrame(settings, text="Kundenprofil", style="Card.TLabelframe")
netbox_card = ttk.LabelFrame(settings, text="NetBox-Ziel", style="Card.TLabelframe")
settings.add(profile_card, weight=3)
settings.add(netbox_card, weight=2)
self.switch_profile_combo = ttk.Combobox(profile_card, textvariable=self.profile_name, state="readonly")
self.switch_profile_combo.pack(side="left", fill="x", expand=True, padx=(0, 8))
self.switch_profile_combo.bind("<<ComboboxSelected>>", self.on_profile_selected)
ttk.Button(profile_card, text="Neu", command=self.new_profile).pack(side="left", padx=3)
ttk.Button(profile_card, text="Speichern", command=self.save_profile).pack(side="left", padx=3)
ttk.Button(profile_card, text="Löschen", command=self.delete_profile).pack(side="left", padx=3)
self.switch_netbox_url = self.add_field(netbox_card, 0, "URL", variable=self.netbox_url_var)
self.switch_netbox_token = self.add_field(
netbox_card,
1,
"API-Token",
secret=True,
variable=self.netbox_token_var,
)
ttk.Checkbutton(
netbox_card,
text="Zertifikatsprüfung deaktivieren",
variable=self.netbox_ignore_ssl,
).grid(row=2, column=0, columnspan=2, sticky="w", pady=(6, 0))
source_card = ttk.LabelFrame(main, text="Switch-Zugänge", style="Card.TLabelframe")
source_card.grid(row=1, column=0, sticky="nsew", pady=(0, 10))
source_card.grid(row=2, column=0, sticky="nsew", pady=(0, 10))
switch_columns = ("device", "platform", "host", "active")
self.switch_tree = ttk.Treeview(source_card, columns=switch_columns, show="headings", height=6)
for column, title, width in (
@@ -1018,7 +1047,7 @@ class NetBoxVMImporter:
).pack(side="right")
result_card = ttk.LabelFrame(main, text="Schnittstellen & LLDP-Nachbarn", style="Card.TLabelframe")
result_card.grid(row=2, column=0, sticky="nsew", pady=(0, 10))
result_card.grid(row=3, column=0, sticky="nsew", pady=(0, 10))
result_columns = ("switch", "interface", "state", "speed", "neighbor", "remote_port")
self.switch_result_tree = ttk.Treeview(result_card, columns=result_columns, show="headings")
result_headings = {
@@ -1035,22 +1064,22 @@ class NetBoxVMImporter:
result_scroll.pack(side="right", fill="y")
actions = ttk.Frame(main)
actions.grid(row=3, column=0, sticky="ew", pady=(0, 10))
actions.grid(row=4, column=0, sticky="ew", pady=(0, 10))
self.create_neighbor_cables = tk.BooleanVar(value=True)
ttk.Checkbutton(actions, text="LLDP-Verbindungen als Kabel anlegen", variable=self.create_neighbor_cables).pack(side="left")
ttk.Button(actions, text="Nach NetBox synchronisieren", command=self.sync_switches, style="Primary.TButton").pack(side="right")
log_card = ttk.LabelFrame(main, text="Switch-Protokoll", style="Card.TLabelframe")
log_card.grid(row=4, column=0, sticky="nsew")
log_card.grid(row=5, column=0, sticky="nsew")
self.switch_log_text = tk.Text(
log_card, height=8, borderwidth=0, bg="#f7f9fb", foreground="#243b53", font=("Consolas", 9), state="disabled"
)
self.switch_log_text.pack(fill="both", expand=True)
@staticmethod
def add_field(parent, row, label, secret=False):
def add_field(parent, row, label, secret=False, variable=None):
ttk.Label(parent, text=label).grid(row=row, column=0, sticky="w", padx=(0, 10), pady=5)
entry = ttk.Entry(parent, show="" if secret else "")
entry = ttk.Entry(parent, show="" if secret else "", textvariable=variable)
entry.grid(row=row, column=1, sticky="ew", pady=5)
parent.columnconfigure(1, weight=1)
return entry
@@ -1084,7 +1113,10 @@ class NetBoxVMImporter:
messagebox.showwarning("Konfiguration", f"Profile konnten nicht geladen werden:\n{error}")
def refresh_profile_list(self):
self.profile_combo["values"] = sorted(self.profiles, key=str.casefold)
values = sorted(self.profiles, key=str.casefold)
self.profile_combo["values"] = values
if hasattr(self, "switch_profile_combo"):
self.switch_profile_combo["values"] = values
def profile_from_form(self, name):
return {
@@ -1242,8 +1274,16 @@ class NetBoxVMImporter:
return False
self.connect_netbox()
self.switch_log("Lade vorhandene NetBox-Geräte …")
self.netbox_devices = sorted(self.nb.dcim.devices.all(), key=lambda item: item.name.casefold())
self.switch_log(f"{len(self.netbox_devices)} NetBox-Geräte geladen")
devices = list(self.nb.dcim.devices.all())
self.netbox_devices = sorted(
(device for device in devices if getattr(device, "name", None)),
key=lambda item: str(item.name).casefold(),
)
ignored = len(devices) - len(self.netbox_devices)
text = f"{len(self.netbox_devices)} benannte NetBox-Geräte geladen"
if ignored:
text += f" ({ignored} Gerät(e) ohne Namen übersprungen)"
self.switch_log(text)
return True
except Exception as error:
messagebox.showerror("NetBox-Verbindung", str(error), parent=self.root)