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:
@@ -17,6 +17,7 @@ Windows-Desktopanwendung zum Synchronisieren virtueller Maschinen sowie HPE-/Aru
|
|||||||
- Synchronisierung von VMs, Hardwaredaten, Interfaces und IP-Adressen
|
- Synchronisierung von VMs, Hardwaredaten, Interfaces und IP-Adressen
|
||||||
- Übernahme primärer IPv4- und IPv6-Adressen
|
- Übernahme primärer IPv4- und IPv6-Adressen
|
||||||
- Getrennte Haupt-Tabs für Virtualisierung und Switche
|
- Getrennte Haupt-Tabs für Virtualisierung und Switche
|
||||||
|
- Gemeinsame, synchronisierte Kundenprofil- und NetBox-Zielansicht in beiden Tabs
|
||||||
- Verschlüsselte SSH-Zugänge pro bestehendem NetBox-Switch
|
- Verschlüsselte SSH-Zugänge pro bestehendem NetBox-Switch
|
||||||
- SSH-Erkennung für Aruba CX, ArubaOS-Switch/ProCurve und HPE Comware
|
- SSH-Erkennung für Aruba CX, ArubaOS-Switch/ProCurve und HPE Comware
|
||||||
- Synchronisierung von Switch-Schnittstellen und optionalen LLDP-Verbindungen als NetBox-Kabel
|
- Synchronisierung von Switch-Schnittstellen und optionalen LLDP-Verbindungen als NetBox-Kabel
|
||||||
|
|||||||
@@ -842,6 +842,8 @@ class NetBoxVMImporter:
|
|||||||
self.vrfs = []
|
self.vrfs = []
|
||||||
self.platforms = []
|
self.platforms = []
|
||||||
self.profile_name = tk.StringVar()
|
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.netbox_ignore_ssl = tk.BooleanVar(value=False)
|
||||||
self.status_text = tk.StringVar(value="Bereit")
|
self.status_text = tk.StringVar(value="Bereit")
|
||||||
self.store = ProfileStore()
|
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="Bearbeiten", command=self.edit_source).pack(side="left", padx=5)
|
||||||
ttk.Button(source_buttons, text="Entfernen", command=self.remove_source).pack(side="left")
|
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_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)
|
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(
|
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)
|
row=2, column=0, columnspan=2, sticky="w", pady=(10, 0)
|
||||||
)
|
)
|
||||||
@@ -977,21 +979,48 @@ class NetBoxVMImporter:
|
|||||||
main = ttk.Frame(parent, padding=18)
|
main = ttk.Frame(parent, padding=18)
|
||||||
main.pack(fill="both", expand=True)
|
main.pack(fill="both", expand=True)
|
||||||
main.columnconfigure(0, weight=1)
|
main.columnconfigure(0, weight=1)
|
||||||
main.rowconfigure(2, weight=2)
|
main.rowconfigure(3, weight=2)
|
||||||
main.rowconfigure(4, weight=3)
|
main.rowconfigure(5, weight=2)
|
||||||
|
|
||||||
header = ttk.Frame(main)
|
header = ttk.Frame(main)
|
||||||
header.grid(row=0, column=0, sticky="ew", pady=(0, 12))
|
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="HPE & Aruba Switche", style="Title.TLabel").pack(anchor="w")
|
||||||
ttk.Label(
|
ttk.Label(
|
||||||
header,
|
header,
|
||||||
text="Bestehende NetBox-Geräte per SSH auslesen · Profil: ",
|
text="Bestehende NetBox-Geräte per SSH auslesen und dokumentieren",
|
||||||
style="Subtitle.TLabel",
|
style="Subtitle.TLabel",
|
||||||
).pack(side="left")
|
).pack(anchor="w")
|
||||||
ttk.Label(header, textvariable=self.profile_name, style="Subtitle.TLabel").pack(side="left")
|
|
||||||
|
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 = 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")
|
switch_columns = ("device", "platform", "host", "active")
|
||||||
self.switch_tree = ttk.Treeview(source_card, columns=switch_columns, show="headings", height=6)
|
self.switch_tree = ttk.Treeview(source_card, columns=switch_columns, show="headings", height=6)
|
||||||
for column, title, width in (
|
for column, title, width in (
|
||||||
@@ -1018,7 +1047,7 @@ class NetBoxVMImporter:
|
|||||||
).pack(side="right")
|
).pack(side="right")
|
||||||
|
|
||||||
result_card = ttk.LabelFrame(main, text="Schnittstellen & LLDP-Nachbarn", style="Card.TLabelframe")
|
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")
|
result_columns = ("switch", "interface", "state", "speed", "neighbor", "remote_port")
|
||||||
self.switch_result_tree = ttk.Treeview(result_card, columns=result_columns, show="headings")
|
self.switch_result_tree = ttk.Treeview(result_card, columns=result_columns, show="headings")
|
||||||
result_headings = {
|
result_headings = {
|
||||||
@@ -1035,22 +1064,22 @@ class NetBoxVMImporter:
|
|||||||
result_scroll.pack(side="right", fill="y")
|
result_scroll.pack(side="right", fill="y")
|
||||||
|
|
||||||
actions = ttk.Frame(main)
|
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)
|
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.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")
|
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 = 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(
|
self.switch_log_text = tk.Text(
|
||||||
log_card, height=8, borderwidth=0, bg="#f7f9fb", foreground="#243b53", font=("Consolas", 9), state="disabled"
|
log_card, height=8, borderwidth=0, bg="#f7f9fb", foreground="#243b53", font=("Consolas", 9), state="disabled"
|
||||||
)
|
)
|
||||||
self.switch_log_text.pack(fill="both", expand=True)
|
self.switch_log_text.pack(fill="both", expand=True)
|
||||||
|
|
||||||
@staticmethod
|
@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)
|
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)
|
entry.grid(row=row, column=1, sticky="ew", pady=5)
|
||||||
parent.columnconfigure(1, weight=1)
|
parent.columnconfigure(1, weight=1)
|
||||||
return entry
|
return entry
|
||||||
@@ -1084,7 +1113,10 @@ class NetBoxVMImporter:
|
|||||||
messagebox.showwarning("Konfiguration", f"Profile konnten nicht geladen werden:\n{error}")
|
messagebox.showwarning("Konfiguration", f"Profile konnten nicht geladen werden:\n{error}")
|
||||||
|
|
||||||
def refresh_profile_list(self):
|
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):
|
def profile_from_form(self, name):
|
||||||
return {
|
return {
|
||||||
@@ -1242,8 +1274,16 @@ class NetBoxVMImporter:
|
|||||||
return False
|
return False
|
||||||
self.connect_netbox()
|
self.connect_netbox()
|
||||||
self.switch_log("Lade vorhandene NetBox-Geräte …")
|
self.switch_log("Lade vorhandene NetBox-Geräte …")
|
||||||
self.netbox_devices = sorted(self.nb.dcim.devices.all(), key=lambda item: item.name.casefold())
|
devices = list(self.nb.dcim.devices.all())
|
||||||
self.switch_log(f"{len(self.netbox_devices)} NetBox-Geräte geladen")
|
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
|
return True
|
||||||
except Exception as error:
|
except Exception as error:
|
||||||
messagebox.showerror("NetBox-Verbindung", str(error), parent=self.root)
|
messagebox.showerror("NetBox-Verbindung", str(error), parent=self.root)
|
||||||
|
|||||||
Vendored
BIN
Binary file not shown.
Reference in New Issue
Block a user