diff --git a/HANDOFF.md b/HANDOFF.md index dfea747..1de9178 100644 --- a/HANDOFF.md +++ b/HANDOFF.md @@ -896,13 +896,22 @@ beim Ändern die RouterOS-Suffix-Form zurück) — angewendet auf DHCP-Server (weder Exception noch im Output-Text) — bei einer leer bleibenden Liste in Übersicht/Geräte/Experte-Tab als erste Verdachtsquelle prüfen. -10. **Offener Nutzerwunsch, noch nicht umgesetzt:** ausführlichere - Tooltips im Experte-Tab für alle Sektionen, mit konkretem - Adress-Format-Beispiel (z.B. "10.10.10.1/24") statt nur "mit - Präfix". Viele `RouterOSFieldSchema.help`-Texte in - `RouterOSSchemaCatalog.swift` sind aktuell leer (`help: ""`) oder - knapp ohne Beispielwert — wurde diese Session zugunsten des - M8-Hardware-Tests zurückgestellt. +10. ~~Ausführlichere Tooltips im Experte-Tab~~ — erledigt: alle + `RouterOSFieldSchema.help`-Texte in `RouterOSSchemaCatalog.swift` + überarbeitet, jedes Adress-/Netz-/Bereichs-Feld hat jetzt ein + konkretes Beispiel (z.B. "192.168.88.1/24" oder + "192.168.88.10-192.168.88.254"), vorher leere `help: ""`-Texte + gefüllt (u.a. bei Mangle/Raw-Regeln, den meisten "Deaktiviert"- + Feldern, WireGuard-Peer-Feldern, System-/Tool-Menüs). Dabei zwei + weitere Nutzerwünsche ergänzt: eine fette Überschrift (Menüname + + Kategorie) oben in `ExpertMenuDetailView`, da vorher nicht erkennbar + war, in welcher Sektion man sich befindet; und eine hellblaue + Hervorhebung des gerade geöffneten Eintrags in der linken Liste + (`ExpertView`) — dafür `.listRowBackground` auf macOS' Sidebar-Stil + verworfen (wird vom System überschrieben/nicht sichtbar), stattdessen + Hintergrund direkt am Zeileninhalt (`Text` in `RoundedRectangle`- + Fill) gesetzt. Alles vom Nutzer live in Xcode bestätigt ("passt + gut"). Gitea-Remote `origin` ist eingerichtet und wird laufend gepusht (siehe oben) — dieser Hinweis war veraltet, korrigiert am 2026-09-15. diff --git a/RouterOSAssistant/Core/Models/RouterOSSchemaCatalog.swift b/RouterOSAssistant/Core/Models/RouterOSSchemaCatalog.swift index bb0a25e..99522b5 100644 --- a/RouterOSAssistant/Core/Models/RouterOSSchemaCatalog.swift +++ b/RouterOSAssistant/Core/Models/RouterOSSchemaCatalog.swift @@ -29,7 +29,7 @@ enum RouterOSSchemaCatalog { warning: "Reihenfolge zählt. Neue Regeln landen am Ende der Liste — eine bereits vorhandene, weiter oben stehende Regel kann eine neue Regel unerreichbar machen. Position danach mit \"/ip firewall filter print\" kontrollieren, notfalls mit \"move\" verschieben.", fields: [ RouterOSFieldSchema(key: "chain", label: "Chain", kind: .text, - help: "input = an den Router, forward = durch den Router, output = vom Router aus. Eigene Chains (Sprungziele über \"jump\") sind ebenfalls möglich.", + help: "input = an den Router, forward = durch den Router, output = vom Router aus. Eigene Chains (Sprungziele über \"jump\") sind ebenfalls möglich. Beispiel: forward", defaultValue: "forward", required: true), RouterOSFieldSchema(key: "action", label: "Aktion", kind: .enumPick(options: [ "accept", "drop", "reject", "log", "jump", "return", @@ -37,9 +37,9 @@ enum RouterOSSchemaCatalog { ]), help: "Was mit passenden Paketen geschieht. reject schickt zusätzlich eine Fehlermeldung zurück, drop verwirft stillschweigend.", defaultValue: "accept", required: true), RouterOSFieldSchema(key: "src-address", label: "Quell-Adresse", kind: .text, - help: "Einzel-IP oder Netz (z.B. 192.168.88.0/24). Leer = beliebig."), + help: "Einzel-IP oder Netz mit Präfix, z.B. 192.168.88.5 oder 192.168.88.0/24. Leer = beliebig."), RouterOSFieldSchema(key: "dst-address", label: "Ziel-Adresse", kind: .text, - help: "Einzel-IP oder Netz. Leer = beliebig."), + help: "Einzel-IP oder Netz mit Präfix, z.B. 192.168.88.5 oder 192.168.88.0/24. Leer = beliebig."), RouterOSFieldSchema(key: "src-address-list", label: "Quelle in Adress-Liste", kind: .menuItemPick(menuPath: "/ip firewall address-list", restPath: "ip/firewall/address-list", valueField: "list"), help: "Nur Pakete, deren Absender in dieser zuvor angelegten Adress-Liste steht."), @@ -47,14 +47,14 @@ enum RouterOSSchemaCatalog { kind: .menuItemPick(menuPath: "/ip firewall address-list", restPath: "ip/firewall/address-list", valueField: "list"), help: "Nur Pakete, deren Ziel in dieser zuvor angelegten Adress-Liste steht."), RouterOSFieldSchema(key: "in-interface", label: "Eingangs-Interface", kind: .interfacePick, - help: "Nur Pakete, die über dieses Interface hereinkommen."), + help: "Nur Pakete, die über dieses Interface hereinkommen, z.B. ether1 oder bridge."), RouterOSFieldSchema(key: "out-interface", label: "Ausgangs-Interface", kind: .interfacePick, - help: "Nur Pakete, die über dieses Interface hinausgehen."), + help: "Nur Pakete, die über dieses Interface hinausgehen, z.B. ether1 oder bridge."), RouterOSFieldSchema(key: "protocol", label: "Protokoll", kind: .enumPick(options: [ "tcp", "udp", "icmp", "gre", "ipsec-esp", "ipsec-ah" ]), help: "IP-Protokoll. Leer = alle."), RouterOSFieldSchema(key: "dst-port", label: "Ziel-Port(s)", kind: .text, - help: "Nur bei tcp/udp sinnvoll. Einzelner Port (80) oder Bereich (8000-8100)."), + help: "Nur bei tcp/udp sinnvoll. Einzelner Port (z.B. 80) oder Bereich (z.B. 8000-8100)."), RouterOSFieldSchema(key: "connection-state", label: "Verbindungsstatus", kind: .enumPick(options: [ "new", "established", "related", "invalid", "untracked" ]), help: "Status laut Connection-Tracking. \"established,related\" ist die übliche \"schon erlaubte Verbindung\"-Regel."), @@ -83,18 +83,20 @@ enum RouterOSSchemaCatalog { "masquerade", "src-nat", "dst-nat", "netmap", "redirect", "same" ]), help: "masquerade = automatisches NAT über die aktuelle WAN-IP (empfohlen bei wechselnder IP). src-nat = feste NAT-Adresse. dst-nat = Ziel umschreiben (Portweiterleitung). redirect = auf den Router selbst umlenken.", defaultValue: "masquerade", required: true), - RouterOSFieldSchema(key: "src-address", label: "Quell-Adresse", kind: .text, help: "Leer = beliebig."), - RouterOSFieldSchema(key: "dst-address", label: "Ziel-Adresse", kind: .text, help: "Leer = beliebig."), + RouterOSFieldSchema(key: "src-address", label: "Quell-Adresse", kind: .text, + help: "Einzel-IP oder Netz mit Präfix, z.B. 192.168.88.0/24. Leer = beliebig."), + RouterOSFieldSchema(key: "dst-address", label: "Ziel-Adresse", kind: .text, + help: "Einzel-IP oder Netz mit Präfix, z.B. 192.168.88.0/24. Leer = beliebig."), RouterOSFieldSchema(key: "in-interface", label: "Eingangs-Interface", kind: .interfacePick, - help: "Bei dstnat meist der WAN-Port (woher die Anfrage aus dem Internet kommt)."), + help: "Bei dstnat meist der WAN-Port (woher die Anfrage aus dem Internet kommt), z.B. ether1."), RouterOSFieldSchema(key: "out-interface", label: "Ausgangs-Interface", kind: .interfacePick, - help: "Bei srcnat/masquerade meist der WAN-Port."), + help: "Bei srcnat/masquerade meist der WAN-Port, z.B. ether1."), RouterOSFieldSchema(key: "protocol", label: "Protokoll", kind: .enumPick(options: ["tcp", "udp", "icmp"]), help: "Nötig, damit Ports geprüft werden können."), RouterOSFieldSchema(key: "dst-port", label: "Ziel-Port der Anfrage", kind: .text, - help: "Der Port, auf den die Anfrage von außen ankommt (z.B. 8080)."), + help: "Der Port, auf den die Anfrage von außen ankommt, z.B. 8080."), RouterOSFieldSchema(key: "to-addresses", label: "Weiterleiten an (interne IP)", kind: .text, - help: "Nur bei dst-nat: die interne IP-Adresse, an die weitergeleitet wird."), + help: "Nur bei dst-nat: die interne IP-Adresse, an die weitergeleitet wird, z.B. 192.168.88.10."), RouterOSFieldSchema(key: "to-ports", label: "Weiterleiten an (interner Port)", kind: .text, help: "Nur bei dst-nat: interner Port, falls abweichend vom Ziel-Port (z.B. extern 8080 → intern 80)."), RouterOSFieldSchema(key: "comment", label: "Kommentar", kind: .text, help: "Nur zur Wiedererkennung."), @@ -125,14 +127,21 @@ enum RouterOSSchemaCatalog { help: "Frei wählbarer Name für die Paketmarkierung."), RouterOSFieldSchema(key: "passthrough", label: "Weitere Mangle-Regeln noch prüfen", kind: .bool, help: "Ja (Standard) lässt nachfolgende Mangle-Regeln diese Verbindung noch zusätzlich prüfen.", defaultValue: "yes"), - RouterOSFieldSchema(key: "src-address", label: "Quell-Adresse", kind: .text, help: "Leer = beliebig."), - RouterOSFieldSchema(key: "dst-address", label: "Ziel-Adresse", kind: .text, help: "Leer = beliebig."), - RouterOSFieldSchema(key: "in-interface", label: "Eingangs-Interface", kind: .interfacePick, help: ""), - RouterOSFieldSchema(key: "out-interface", label: "Ausgangs-Interface", kind: .interfacePick, help: ""), - RouterOSFieldSchema(key: "protocol", label: "Protokoll", kind: .enumPick(options: ["tcp", "udp", "icmp"]), help: ""), - RouterOSFieldSchema(key: "dst-port", label: "Ziel-Port(s)", kind: .text, help: "Nur bei tcp/udp."), + RouterOSFieldSchema(key: "src-address", label: "Quell-Adresse", kind: .text, + help: "Einzel-IP oder Netz mit Präfix, z.B. 192.168.88.0/24. Leer = beliebig."), + RouterOSFieldSchema(key: "dst-address", label: "Ziel-Adresse", kind: .text, + help: "Einzel-IP oder Netz mit Präfix, z.B. 192.168.88.0/24. Leer = beliebig."), + RouterOSFieldSchema(key: "in-interface", label: "Eingangs-Interface", kind: .interfacePick, + help: "Nur Pakete, die über dieses Interface hereinkommen, z.B. ether1 oder bridge."), + RouterOSFieldSchema(key: "out-interface", label: "Ausgangs-Interface", kind: .interfacePick, + help: "Nur Pakete, die über dieses Interface hinausgehen, z.B. ether1 oder bridge."), + RouterOSFieldSchema(key: "protocol", label: "Protokoll", kind: .enumPick(options: ["tcp", "udp", "icmp"]), + help: "IP-Protokoll. Leer = alle."), + RouterOSFieldSchema(key: "dst-port", label: "Ziel-Port(s)", kind: .text, + help: "Nur bei tcp/udp sinnvoll. Einzelner Port (z.B. 80) oder Bereich (z.B. 8000-8100)."), RouterOSFieldSchema(key: "comment", label: "Kommentar", kind: .text, help: "Nur zur Wiedererkennung."), - RouterOSFieldSchema(key: "disabled", label: "Deaktiviert", kind: .bool, help: "", defaultValue: "no") + RouterOSFieldSchema(key: "disabled", label: "Deaktiviert", kind: .bool, + help: "Regel inaktiv schalten, ohne sie zu löschen.", defaultValue: "no") ], listColumns: ["chain", "action", "comment"] ), @@ -146,17 +155,24 @@ enum RouterOSSchemaCatalog { warning: "notrack nimmt den betroffenen Verkehr aus dem Connection-Tracking heraus — danach greifen dafür keine Filter-/NAT-Regeln mehr, die sich auf connection-state oder NAT verlassen. Nur gezielt einsetzen.", fields: [ RouterOSFieldSchema(key: "chain", label: "Chain", kind: .enumPick(options: ["prerouting", "output"]), - help: "", defaultValue: "prerouting", required: true), + help: "prerouting = eingehende Pakete, bevor der Router sie verarbeitet. output = vom Router selbst erzeugte Pakete.", + defaultValue: "prerouting", required: true), RouterOSFieldSchema(key: "action", label: "Aktion", kind: .enumPick(options: ["accept", "drop", "notrack"]), help: "notrack = von der Connection-Tracking-Verfolgung ausnehmen (siehe Warnhinweis oben).", defaultValue: "accept", required: true), - RouterOSFieldSchema(key: "src-address", label: "Quell-Adresse", kind: .text, help: ""), - RouterOSFieldSchema(key: "dst-address", label: "Ziel-Adresse", kind: .text, help: ""), - RouterOSFieldSchema(key: "in-interface", label: "Eingangs-Interface", kind: .interfacePick, help: ""), - RouterOSFieldSchema(key: "protocol", label: "Protokoll", kind: .enumPick(options: ["tcp", "udp", "icmp"]), help: ""), - RouterOSFieldSchema(key: "dst-port", label: "Ziel-Port(s)", kind: .text, help: ""), - RouterOSFieldSchema(key: "comment", label: "Kommentar", kind: .text, help: ""), - RouterOSFieldSchema(key: "disabled", label: "Deaktiviert", kind: .bool, help: "", defaultValue: "no") + RouterOSFieldSchema(key: "src-address", label: "Quell-Adresse", kind: .text, + help: "Einzel-IP oder Netz mit Präfix, z.B. 192.168.88.0/24. Leer = beliebig."), + RouterOSFieldSchema(key: "dst-address", label: "Ziel-Adresse", kind: .text, + help: "Einzel-IP oder Netz mit Präfix, z.B. 192.168.88.0/24. Leer = beliebig."), + RouterOSFieldSchema(key: "in-interface", label: "Eingangs-Interface", kind: .interfacePick, + help: "Nur Pakete, die über dieses Interface hereinkommen, z.B. ether1."), + RouterOSFieldSchema(key: "protocol", label: "Protokoll", kind: .enumPick(options: ["tcp", "udp", "icmp"]), + help: "IP-Protokoll. Leer = alle."), + RouterOSFieldSchema(key: "dst-port", label: "Ziel-Port(s)", kind: .text, + help: "Nur bei tcp/udp sinnvoll. Einzelner Port (z.B. 80) oder Bereich (z.B. 8000-8100)."), + RouterOSFieldSchema(key: "comment", label: "Kommentar", kind: .text, help: "Nur zur eigenen Wiedererkennung."), + RouterOSFieldSchema(key: "disabled", label: "Deaktiviert", kind: .bool, + help: "Regel inaktiv schalten, ohne sie zu löschen.", defaultValue: "no") ], listColumns: ["chain", "action", "comment"] ), @@ -169,12 +185,12 @@ enum RouterOSSchemaCatalog { explanation: "Statt in jeder Regel einzelne IPs aufzuzählen, legst du hier eine benannte Liste an (z.B. \"gesperrt\" oder \"vertraut\") und verweist in Filter-/NAT-/Mangle-Regeln per \"src-address-list\"/\"dst-address-list\" darauf. Einträge können dauerhaft oder mit Ablaufzeit (timeout) sein.", fields: [ RouterOSFieldSchema(key: "list", label: "Listen-Name", kind: .text, - help: "Frei wählbarer Name, wird in anderen Regeln referenziert.", required: true), + help: "Frei wählbarer Name, wird in anderen Regeln referenziert, z.B. gesperrt.", required: true), RouterOSFieldSchema(key: "address", label: "Adresse", kind: .text, - help: "Einzel-IP oder Netz, z.B. 192.168.88.5 oder 10.0.0.0/24.", required: true), + help: "Einzel-IP oder Netz mit Präfix, z.B. 192.168.88.5 oder 10.0.0.0/24.", required: true), RouterOSFieldSchema(key: "timeout", label: "Ablaufzeit", kind: .duration, help: "Optional. Nach dieser Zeit wird der Eintrag automatisch entfernt. Alles auf 0 lassen = bleibt dauerhaft, bis manuell entfernt."), - RouterOSFieldSchema(key: "comment", label: "Kommentar", kind: .text, help: "") + RouterOSFieldSchema(key: "comment", label: "Kommentar", kind: .text, help: "Nur zur eigenen Wiedererkennung.") ], listColumns: ["list", "address"] ) @@ -192,7 +208,8 @@ enum RouterOSSchemaCatalog { RouterOSFieldSchema(key: "name", label: "Name", kind: .text, help: "Frei wählbar, z.B. bridge-lan.", required: true), RouterOSFieldSchema(key: "vlan-filtering", label: "VLAN-Filterung (802.1Q)", kind: .bool, help: "Aktiviert echte VLAN-Trennung über diese Bridge — nötig, wenn mehrere VLANs über dieselben Bridge-Ports laufen sollen.", defaultValue: "no"), - RouterOSFieldSchema(key: "disabled", label: "Deaktiviert", kind: .bool, help: "", defaultValue: "no") + RouterOSFieldSchema(key: "disabled", label: "Deaktiviert", kind: .bool, + help: "Bridge inaktiv schalten, ohne sie zu löschen.", defaultValue: "no") ], listColumns: ["name"] ), @@ -204,9 +221,9 @@ enum RouterOSSchemaCatalog { fields: [ RouterOSFieldSchema(key: "bridge", label: "Bridge", kind: .text, help: "Name der Bridge, z.B. bridge-lan.", required: true), RouterOSFieldSchema(key: "interface", label: "Physischer Port", kind: .interfacePick, - help: "Der Port, der der Bridge hinzugefügt wird.", required: true), + help: "Der Port, der der Bridge hinzugefügt wird, z.B. ether2.", required: true), RouterOSFieldSchema(key: "pvid", label: "Port-VLAN-ID (PVID)", kind: .int, - help: "Nur mit aktivierter VLAN-Filterung relevant: VLAN, dem untagged ankommender Verkehr an diesem Port zugeordnet wird.") + help: "Nur mit aktivierter VLAN-Filterung relevant: VLAN, dem untagged ankommender Verkehr an diesem Port zugeordnet wird, z.B. 20.") ], listColumns: ["bridge", "interface"] ), @@ -217,10 +234,11 @@ enum RouterOSSchemaCatalog { explanation: "Ein eigenes logisches Netzwerk auf demselben Kabel, unterschieden durch eine VLAN-Kennung im Ethernet-Rahmen.", fields: [ RouterOSFieldSchema(key: "name", label: "Name", kind: .text, help: "Frei wählbar, z.B. vlan20-gaeste.", required: true), - RouterOSFieldSchema(key: "vlan-id", label: "VLAN-ID", kind: .int, help: "Eindeutige Kennung, 2–4094.", required: true), + RouterOSFieldSchema(key: "vlan-id", label: "VLAN-ID", kind: .int, help: "Eindeutige Kennung, 2–4094, z.B. 20.", required: true), RouterOSFieldSchema(key: "interface", label: "Basis-Interface", kind: .interfacePick, - help: "Physischer Port oder Bridge, auf dem dieses VLAN aufsetzt.", required: true), - RouterOSFieldSchema(key: "disabled", label: "Deaktiviert", kind: .bool, help: "", defaultValue: "no") + help: "Physischer Port oder Bridge, auf dem dieses VLAN aufsetzt, z.B. bridge.", required: true), + RouterOSFieldSchema(key: "disabled", label: "Deaktiviert", kind: .bool, + help: "VLAN-Interface inaktiv schalten, ohne es zu löschen.", defaultValue: "no") ], listColumns: ["name", "vlan-id", "interface"] ), @@ -236,7 +254,8 @@ enum RouterOSSchemaCatalog { help: "Port, auf dem dieser Tunnel auf eingehende Verbindungen lauscht, z.B. 51820.", defaultValue: "51820"), RouterOSFieldSchema(key: "private-key", label: "Privater Schlüssel", kind: .text, help: "Geheim halten. Leer lassen, damit RouterOS automatisch einen erzeugt."), - RouterOSFieldSchema(key: "disabled", label: "Deaktiviert", kind: .bool, help: "", defaultValue: "no") + RouterOSFieldSchema(key: "disabled", label: "Deaktiviert", kind: .bool, + help: "Interface inaktiv schalten, ohne es zu löschen.", defaultValue: "no") ], listColumns: ["name", "listen-port"] ), @@ -249,12 +268,14 @@ enum RouterOSSchemaCatalog { RouterOSFieldSchema(key: "interface", label: "WireGuard-Interface", kind: .menuItemPick(menuPath: "/interface wireguard", restPath: "interface/wireguard", valueField: "name"), help: "Name des zuvor angelegten WireGuard-Interfaces.", required: true), - RouterOSFieldSchema(key: "public-key", label: "Öffentlicher Schlüssel der Gegenstelle", kind: .text, help: "", required: true), + RouterOSFieldSchema(key: "public-key", label: "Öffentlicher Schlüssel der Gegenstelle", kind: .text, + help: "Vom Gerät der Gegenstelle kopieren (z.B. per \"wg show public-key\").", required: true), RouterOSFieldSchema(key: "allowed-address", label: "Erlaubte Adressen", kind: .text, - help: "Welche IP-Adressen/Netze über diesen Peer laufen dürfen, z.B. 10.10.10.2/32."), + help: "Welche IP-Adressen/Netze über diesen Peer laufen dürfen, mit Präfix, z.B. 10.10.10.2/32."), RouterOSFieldSchema(key: "endpoint-address", label: "Feste Adresse der Gegenstelle", kind: .text, - help: "Nur nötig, wenn diese Gegenstelle selbst erreichbar sein muss (Site-to-Site). Bei Roadwarrior-Clients, die sich selbst melden, leer lassen."), - RouterOSFieldSchema(key: "endpoint-port", label: "Port der Gegenstelle", kind: .text, help: ""), + help: "Nur nötig, wenn diese Gegenstelle selbst erreichbar sein muss (Site-to-Site), z.B. eine feste öffentliche IP oder ein DNS-Name. Bei Roadwarrior-Clients, die sich selbst melden, leer lassen."), + RouterOSFieldSchema(key: "endpoint-port", label: "Port der Gegenstelle", kind: .text, + help: "Meist derselbe Port wie der Listen-Port der Gegenstelle, z.B. 51820."), RouterOSFieldSchema(key: "persistent-keepalive", label: "Keepalive (Sekunden)", kind: .text, help: "Z.B. 25 — hält die Verbindung durch NAT/Firewalls am Leben. Wichtig bei Clients hinter NAT.") ], @@ -268,10 +289,11 @@ enum RouterOSSchemaCatalog { fields: [ RouterOSFieldSchema(key: "name", label: "Name", kind: .text, help: "Frei wählbar, z.B. pppoe-wan.", required: true), RouterOSFieldSchema(key: "interface", label: "Physischer Port", kind: .interfacePick, - help: "Der Port, über den die Einwahl läuft, meist der WAN-Port.", required: true), + help: "Der Port, über den die Einwahl läuft, meist der WAN-Port, z.B. ether1.", required: true), RouterOSFieldSchema(key: "user", label: "Benutzername", kind: .text, help: "Zugangsdaten des Providers.", required: true), - RouterOSFieldSchema(key: "password", label: "Passwort", kind: .text, help: "", required: true), - RouterOSFieldSchema(key: "disabled", label: "Deaktiviert", kind: .bool, help: "", defaultValue: "no") + RouterOSFieldSchema(key: "password", label: "Passwort", kind: .text, help: "Zugangsdaten des Providers.", required: true), + RouterOSFieldSchema(key: "disabled", label: "Deaktiviert", kind: .bool, + help: "Einwahl inaktiv schalten, ohne sie zu löschen.", defaultValue: "no") ], listColumns: ["name", "interface"] ), @@ -281,7 +303,7 @@ enum RouterOSSchemaCatalog { summary: "Bündelt mehrere physische Ports zu einer logischen, ausfalltoleranten/schnelleren Verbindung.", explanation: "", fields: [ - RouterOSFieldSchema(key: "name", label: "Name", kind: .text, help: "Frei wählbar.", required: true), + RouterOSFieldSchema(key: "name", label: "Name", kind: .text, help: "Frei wählbar, z.B. bond1.", required: true), RouterOSFieldSchema(key: "slaves", label: "Gebündelte Ports", kind: .text, help: "Kommagetrennte Liste physischer Ports, z.B. ether2,ether3."), RouterOSFieldSchema(key: "mode", label: "Modus", kind: .enumPick(options: [ @@ -302,9 +324,11 @@ enum RouterOSSchemaCatalog { explanation: "Jede IP-Adresse hängt an genau einem Interface (physischer Port, Bridge oder VLAN).", fields: [ RouterOSFieldSchema(key: "address", label: "Adresse", kind: .text, - help: "Mit Präfix, z.B. 192.168.88.1/24.", required: true), - RouterOSFieldSchema(key: "interface", label: "Interface", kind: .interfacePick, help: "", required: true), - RouterOSFieldSchema(key: "disabled", label: "Deaktiviert", kind: .bool, help: "", defaultValue: "no") + help: "IP-Adresse mit Netzmaske als Präfix (Router-Adresse in diesem Netz), z.B. 192.168.88.1/24 oder 10.10.10.1/24. Das /24 legt fest, wie viele Geräte in dieses Netz passen (/24 = bis zu 254).", required: true), + RouterOSFieldSchema(key: "interface", label: "Interface", kind: .interfacePick, + help: "Interface, dem diese Adresse zugewiesen wird, z.B. bridge oder ether4.", required: true), + RouterOSFieldSchema(key: "disabled", label: "Deaktiviert", kind: .bool, + help: "Adresse inaktiv schalten, ohne sie zu löschen.", defaultValue: "no") ], listColumns: ["address", "interface"] ), @@ -314,9 +338,9 @@ enum RouterOSSchemaCatalog { summary: "Adressbereiche, aus denen DHCP-Server oder PPP-Profile Adressen vergeben.", explanation: "", fields: [ - RouterOSFieldSchema(key: "name", label: "Name", kind: .text, help: "Frei wählbar.", required: true), + RouterOSFieldSchema(key: "name", label: "Name", kind: .text, help: "Frei wählbar, z.B. dhcp_pool_lan.", required: true), RouterOSFieldSchema(key: "ranges", label: "Bereich(e)", kind: .text, - help: "Z.B. 192.168.88.10-192.168.88.254. Mehrere Bereiche kommagetrennt.", required: true) + help: "Von-bis-Adresse ohne Präfix, z.B. 192.168.88.10-192.168.88.254. Mehrere Bereiche kommagetrennt.", required: true) ], listColumns: ["name", "ranges"] ), @@ -326,15 +350,16 @@ enum RouterOSSchemaCatalog { summary: "Vergibt automatisch IP-Adressen an Geräte in einem Netzwerk.", explanation: "Für die üblichen Fälle deckt das bereits der Einrichten-Assistent (LAN-/VLAN-Schritt) ab — hier direkter Zugriff für Sonderfälle.", fields: [ - RouterOSFieldSchema(key: "name", label: "Name", kind: .text, help: "Frei wählbar.", required: true), + RouterOSFieldSchema(key: "name", label: "Name", kind: .text, help: "Frei wählbar, z.B. dhcp_lan.", required: true), RouterOSFieldSchema(key: "interface", label: "Interface", kind: .interfacePick, - help: "Netzwerk, in dem dieser Server Adressen vergibt.", required: true), + help: "Netzwerk, in dem dieser Server Adressen vergibt, z.B. bridge.", required: true), RouterOSFieldSchema(key: "address-pool", label: "Adress-Pool", kind: .menuItemPick(menuPath: "/ip pool", restPath: "ip/pool", valueField: "name"), help: "Name eines zuvor angelegten Adress-Pools.", required: true), RouterOSFieldSchema(key: "lease-time", label: "Lease-Zeit", kind: .duration, - help: "Wie lange ein Gerät seine Adresse behält, bevor sie erneuert werden muss."), - RouterOSFieldSchema(key: "disabled", label: "Deaktiviert", kind: .bool, help: "", defaultValue: "no") + help: "Wie lange ein Gerät seine Adresse behält, bevor sie erneuert werden muss, z.B. 1d oder 12h."), + RouterOSFieldSchema(key: "disabled", label: "Deaktiviert", kind: .bool, + help: "DHCP-Server inaktiv schalten, ohne ihn zu löschen.", defaultValue: "no") ], listColumns: ["name", "interface"] ), @@ -345,10 +370,11 @@ enum RouterOSSchemaCatalog { explanation: "Getrennt vom DHCP-Server selbst, weil dieselben Netzwerk-Optionen für mehrere DHCP-Server gelten können.", fields: [ RouterOSFieldSchema(key: "address", label: "Netz", kind: .text, - help: "Für dieses Netz gelten die folgenden Optionen, z.B. 192.168.88.0/24.", required: true), + help: "Für dieses Netz gelten die folgenden Optionen — die Netzadresse mit Präfix, z.B. 192.168.88.0/24.", required: true), RouterOSFieldSchema(key: "gateway", label: "Gateway", kind: .text, - help: "In der Regel die Router-Adresse in diesem Netz.", required: true), - RouterOSFieldSchema(key: "dns-server", label: "DNS-Server", kind: .text, help: "Meist der Router selbst.") + help: "In der Regel die Router-Adresse in diesem Netz, ohne Präfix, z.B. 192.168.88.1.", required: true), + RouterOSFieldSchema(key: "dns-server", label: "DNS-Server", kind: .text, + help: "Meist der Router selbst, z.B. 192.168.88.1. Mehrere Server kommagetrennt möglich.") ], listColumns: ["address", "gateway"] ), @@ -359,12 +385,13 @@ enum RouterOSSchemaCatalog { explanation: "", fields: [ RouterOSFieldSchema(key: "interface", label: "Interface", kind: .interfacePick, - help: "Meist der WAN-Port.", required: true), + help: "Meist der WAN-Port, z.B. ether1.", required: true), RouterOSFieldSchema(key: "add-default-route", label: "Standardroute übernehmen", kind: .bool, help: "Übernimmt die vom Provider mitgeteilte Standardroute ins Internet.", defaultValue: "yes"), RouterOSFieldSchema(key: "use-peer-dns", label: "DNS-Server übernehmen", kind: .bool, help: "Übernimmt die vom Provider mitgeteilten DNS-Server.", defaultValue: "yes"), - RouterOSFieldSchema(key: "disabled", label: "Deaktiviert", kind: .bool, help: "", defaultValue: "no") + RouterOSFieldSchema(key: "disabled", label: "Deaktiviert", kind: .bool, + help: "DHCP-Client inaktiv schalten, ohne ihn zu löschen.", defaultValue: "no") ], listColumns: ["interface"] ), @@ -377,7 +404,7 @@ enum RouterOSSchemaCatalog { RouterOSFieldSchema(key: "servers", label: "DNS-Server", kind: .text, help: "Ein oder mehrere Server, kommagetrennt, z.B. 1.1.1.1,8.8.8.8."), RouterOSFieldSchema(key: "allow-remote-requests", label: "Als DNS-Server fürs LAN erlauben", kind: .bool, - help: "Lässt Geräte im LAN den Router selbst als DNS-Server nutzen.", defaultValue: "no") + help: "Lässt Geräte im LAN den Router selbst als DNS-Server nutzen. Ohne das schlagen DNS-Anfragen von Geräten an den Router fehl, selbst wenn sie ihn als DNS-Server eingetragen haben.", defaultValue: "no") ], listColumns: ["servers"], isSingleton: true @@ -392,10 +419,11 @@ enum RouterOSSchemaCatalog { RouterOSFieldSchema(key: "name", label: "Dienst", kind: .enumPick(options: [ "www", "www-ssl", "ssh", "api", "api-ssl", "winbox", "ftp", "telnet" ]), help: "Welcher Verwaltungsdienst geändert wird.", required: true), - RouterOSFieldSchema(key: "port", label: "Port", kind: .int, help: "Auf welchem Port der Dienst lauscht."), + RouterOSFieldSchema(key: "port", label: "Port", kind: .int, help: "Auf welchem Port der Dienst lauscht, z.B. 22 für ssh."), RouterOSFieldSchema(key: "available-from", label: "Erlaubt von", kind: .text, - help: "Optional: nur von dieser Adresse/diesem Netz aus erreichbar, z.B. 192.168.88.0/24."), - RouterOSFieldSchema(key: "disabled", label: "Deaktiviert", kind: .bool, help: "", defaultValue: "no") + help: "Optional: nur von dieser Adresse/diesem Netz aus erreichbar, mit Präfix, z.B. 192.168.88.0/24."), + RouterOSFieldSchema(key: "disabled", label: "Deaktiviert", kind: .bool, + help: "Dienst inaktiv schalten, ohne den Eintrag zu löschen.", defaultValue: "no") ], listColumns: ["name", "port"] ), @@ -409,13 +437,14 @@ enum RouterOSSchemaCatalog { explanation: "Für alles außer \"Standard-Internet über eine WAN-Schnittstelle\" (das übernimmt bereits die DHCP-Client-/PPPoE-Route automatisch).", fields: [ RouterOSFieldSchema(key: "dst-address", label: "Zielnetz", kind: .text, - help: "Z.B. 10.0.0.0/24, oder 0.0.0.0/0 für eine Standardroute.", required: true), + help: "Netz mit Präfix, z.B. 10.0.0.0/24, oder 0.0.0.0/0 für eine Standardroute.", required: true), RouterOSFieldSchema(key: "gateway", label: "Gateway", kind: .text, - help: "Nächster Hop — IP-Adresse oder Name eines Interfaces.", required: true), + help: "Nächster Hop — IP-Adresse ohne Präfix (z.B. 192.168.88.254) oder Name eines Interfaces (z.B. ether1).", required: true), RouterOSFieldSchema(key: "distance", label: "Distanz", kind: .int, - help: "Priorität bei mehreren passenden Routen zum selben Ziel — kleinere Zahl wird bevorzugt."), - RouterOSFieldSchema(key: "comment", label: "Kommentar", kind: .text, help: ""), - RouterOSFieldSchema(key: "disabled", label: "Deaktiviert", kind: .bool, help: "", defaultValue: "no") + help: "Priorität bei mehreren passenden Routen zum selben Ziel — kleinere Zahl wird bevorzugt, z.B. 1."), + RouterOSFieldSchema(key: "comment", label: "Kommentar", kind: .text, help: "Nur zur eigenen Wiedererkennung."), + RouterOSFieldSchema(key: "disabled", label: "Deaktiviert", kind: .bool, + help: "Route inaktiv schalten, ohne sie zu löschen.", defaultValue: "no") ], listColumns: ["dst-address", "gateway"] ) @@ -442,8 +471,8 @@ enum RouterOSSchemaCatalog { summary: "Zugangsdaten für PPPoE-/L2TP-/PPTP-/OpenVPN-Einwahl in den Router.", explanation: "Jeder Benutzer kann optional einem Profil zugeordnet werden, das IP-Pool/DNS/Verschlüsselung vorgibt.", fields: [ - RouterOSFieldSchema(key: "name", label: "Benutzername", kind: .text, help: "", required: true), - RouterOSFieldSchema(key: "password", label: "Passwort", kind: .text, help: "", required: true), + RouterOSFieldSchema(key: "name", label: "Benutzername", kind: .text, help: "Frei wählbar, z.B. gast1.", required: true), + RouterOSFieldSchema(key: "password", label: "Passwort", kind: .text, help: "Ausreichend lang und zufällig wählen.", required: true), RouterOSFieldSchema(key: "service", label: "Dienst", kind: .enumPick(options: [ "any", "pppoe", "l2tp", "pptp", "ovpn", "sstp" ]), help: "Für welche Einwahl-Art dieser Benutzer gilt.", defaultValue: "any"), @@ -451,10 +480,11 @@ enum RouterOSSchemaCatalog { kind: .menuItemPick(menuPath: "/ppp profile", restPath: "ppp/profile", valueField: "name"), help: "Name eines zuvor angelegten PPP-Profils."), RouterOSFieldSchema(key: "local-address", label: "Lokale Adresse", kind: .text, - help: "IP-Adresse des Routers innerhalb dieser Verbindung."), + help: "IP-Adresse des Routers innerhalb dieser Verbindung, ohne Präfix, z.B. 10.10.10.1."), RouterOSFieldSchema(key: "remote-address", label: "Adresse für den Client", kind: .text, - help: "Feste IP für diesen Benutzer, oder Name eines Adress-Pools."), - RouterOSFieldSchema(key: "disabled", label: "Deaktiviert", kind: .bool, help: "", defaultValue: "no") + help: "Feste IP für diesen Benutzer (z.B. 10.10.10.2), oder Name eines Adress-Pools."), + RouterOSFieldSchema(key: "disabled", label: "Deaktiviert", kind: .bool, + help: "Benutzer inaktiv schalten, ohne ihn zu löschen.", defaultValue: "no") ], listColumns: ["name", "service"] ), @@ -464,11 +494,13 @@ enum RouterOSSchemaCatalog { summary: "Vorlagen (IP-Pool, DNS, Verschlüsselung) für PPP-Benutzer.", explanation: "", fields: [ - RouterOSFieldSchema(key: "name", label: "Name", kind: .text, help: "", required: true), - RouterOSFieldSchema(key: "local-address", label: "Lokale Adresse", kind: .text, help: "IP-Adresse des Routers."), + RouterOSFieldSchema(key: "name", label: "Name", kind: .text, help: "Frei wählbar, z.B. vpn-clients.", required: true), + RouterOSFieldSchema(key: "local-address", label: "Lokale Adresse", kind: .text, + help: "IP-Adresse des Routers, ohne Präfix, z.B. 10.10.10.1."), RouterOSFieldSchema(key: "remote-address", label: "Adress-Pool für Clients", kind: .text, help: "Name eines zuvor angelegten Adress-Pools."), - RouterOSFieldSchema(key: "dns-server", label: "DNS-Server für Clients", kind: .text, help: "") + RouterOSFieldSchema(key: "dns-server", label: "DNS-Server für Clients", kind: .text, + help: "Ein oder mehrere Server, kommagetrennt, z.B. 1.1.1.1,8.8.8.8.") ], listColumns: ["name"] ), @@ -489,12 +521,14 @@ enum RouterOSSchemaCatalog { summary: "WLAN-Interfaces auf älteren/Standard-Wireless-Chips.", explanation: "Diese Interfaces existieren bereits ab Werk (ein WLAN-Chip = ein Interface) — hier werden sie nur konfiguriert, nicht neu angelegt. Ein Sicherheitsprofil (siehe \"WLAN-Sicherheitsprofile\") muss vorher angelegt sein.", fields: [ - RouterOSFieldSchema(key: "ssid", label: "Netzwerkname (SSID)", kind: .text, help: "", required: true), + RouterOSFieldSchema(key: "ssid", label: "Netzwerkname (SSID)", kind: .text, + help: "Der Name, den WLAN-Geräte in der Netzwerkliste sehen, z.B. MeinWLAN.", required: true), RouterOSFieldSchema(key: "security-profile", label: "Sicherheitsprofil", kind: .text, help: "Name eines zuvor unter \"WLAN-Sicherheitsprofile\" angelegten Profils."), RouterOSFieldSchema(key: "mode", label: "Modus", kind: .enumPick(options: ["ap-bridge", "station", "bridge"]), help: "ap-bridge = Access Point (Normalfall), station = als Client mit einem anderen AP verbinden.", defaultValue: "ap-bridge"), - RouterOSFieldSchema(key: "disabled", label: "Deaktiviert", kind: .bool, help: "", defaultValue: "no") + RouterOSFieldSchema(key: "disabled", label: "Deaktiviert", kind: .bool, + help: "WLAN-Interface inaktiv schalten, ohne es zu löschen.", defaultValue: "no") ], listColumns: ["ssid", "mode"] ), @@ -504,7 +538,7 @@ enum RouterOSSchemaCatalog { summary: "Verschlüsselung/Passwort-Vorlagen für WLAN-Interfaces (Legacy-Treiber).", explanation: "Ein Profil wird angelegt und dann bei einem WLAN-Interface als \"security-profile\" eingetragen.", fields: [ - RouterOSFieldSchema(key: "name", label: "Name", kind: .text, help: "", required: true), + RouterOSFieldSchema(key: "name", label: "Name", kind: .text, help: "Frei wählbar, z.B. heimnetz-wpa2.", required: true), RouterOSFieldSchema(key: "mode", label: "Modus", kind: .enumPick(options: ["none", "static-keys-required", "dynamic-keys"]), help: "dynamic-keys ist der übliche WPA/WPA2-Modus.", defaultValue: "dynamic-keys"), RouterOSFieldSchema(key: "authentication-types", label: "Authentifizierung", kind: .enumPick(options: [ @@ -521,14 +555,16 @@ enum RouterOSSchemaCatalog { explanation: "Anderes, verschachteltes Konfigurationsschema als der Legacy-Treiber (Punkt-Notation wie \"security.passphrase\") — nicht mit \"/interface wireless\" mischen. Nur auf Geräten mit entsprechend neuem WLAN-Chip vorhanden.", warning: "Diese App hat den .set-Pfad für den neuen Treiber noch nie gegen echte Hardware getestet (siehe HANDOFF.md) — nach dem Anwenden unbedingt prüfen.", fields: [ - RouterOSFieldSchema(key: "ssid", label: "Netzwerkname (SSID)", kind: .text, help: "", required: true), + RouterOSFieldSchema(key: "ssid", label: "Netzwerkname (SSID)", kind: .text, + help: "Der Name, den WLAN-Geräte in der Netzwerkliste sehen, z.B. MeinWLAN.", required: true), RouterOSFieldSchema(key: "security.authentication-types", label: "Authentifizierung", kind: .enumPick(options: [ "wpa2-psk", "wpa3-psk", "wpa2-psk,wpa3-psk" ]), help: "wpa2-psk,wpa3-psk deckt sowohl ältere als auch neuere Geräte ab."), RouterOSFieldSchema(key: "security.passphrase", label: "Passwort", kind: .text, help: "Mindestens 8 Zeichen."), RouterOSFieldSchema(key: "datapath.bridge", label: "Ziel-Bridge", kind: .text, - help: "Name der Bridge, der dieses WLAN-Netz zugeordnet wird (üblicherweise dieselbe wie das kabelgebundene LAN)."), - RouterOSFieldSchema(key: "disabled", label: "Deaktiviert", kind: .bool, help: "", defaultValue: "no") + help: "Name der Bridge, der dieses WLAN-Netz zugeordnet wird (üblicherweise dieselbe wie das kabelgebundene LAN), z.B. bridge."), + RouterOSFieldSchema(key: "disabled", label: "Deaktiviert", kind: .bool, + help: "WLAN-Interface inaktiv schalten, ohne es zu löschen.", defaultValue: "no") ], listColumns: ["ssid"] ), @@ -546,14 +582,15 @@ enum RouterOSSchemaCatalog { summary: "Begrenzt Up-/Download einer IP-Adresse oder eines Netzes.", explanation: "Reicht für die meisten Heim-/Kleinnetz-Fälle ohne Mangle-Markierungen aus.", fields: [ - RouterOSFieldSchema(key: "name", label: "Name", kind: .text, help: "Frei wählbar.", required: true), + RouterOSFieldSchema(key: "name", label: "Name", kind: .text, help: "Frei wählbar, z.B. limit-gast.", required: true), RouterOSFieldSchema(key: "target", label: "Ziel", kind: .text, - help: "IP-Adresse oder Netz, das begrenzt wird, z.B. 192.168.88.50/32.", required: true), + help: "IP-Adresse oder Netz, das begrenzt wird, mit Präfix, z.B. 192.168.88.50/32.", required: true), RouterOSFieldSchema(key: "max-limit", label: "Max. Bandbreite (Upload/Download)", kind: .text, help: "Zwei Werte getrennt durch \"/\", z.B. 10M/50M (Upload/Download).", required: true), RouterOSFieldSchema(key: "burst-limit", label: "Burst-Bandbreite", kind: .text, help: "Optional: kurzzeitig erlaubte höhere Bandbreite, z.B. 15M/60M."), - RouterOSFieldSchema(key: "disabled", label: "Deaktiviert", kind: .bool, help: "", defaultValue: "no") + RouterOSFieldSchema(key: "disabled", label: "Deaktiviert", kind: .bool, + help: "Begrenzung inaktiv schalten, ohne sie zu löschen.", defaultValue: "no") ], listColumns: ["name", "target", "max-limit"] ), @@ -564,13 +601,14 @@ enum RouterOSSchemaCatalog { explanation: "Statt einer festen Adresse wirkt ein Queue-Baum-Eintrag auf Verkehr mit einer bestimmten Mangle-Markierung.", warning: "Setzt eine passende Mangle-Regel voraus, die genau diese Markierung setzt (siehe Firewall: Mangle) — ohne die greift ein Queue-Baum-Eintrag ins Leere.", fields: [ - RouterOSFieldSchema(key: "name", label: "Name", kind: .text, help: "Frei wählbar.", required: true), + RouterOSFieldSchema(key: "name", label: "Name", kind: .text, help: "Frei wählbar, z.B. queue-gast.", required: true), RouterOSFieldSchema(key: "parent", label: "Übergeordnet", kind: .text, - help: "Ein Interface (z.B. der WAN-Port) oder der Name eines anderen Queue-Baum-Eintrags.", required: true), + help: "Ein Interface (z.B. der WAN-Port, etwa ether1) oder der Name eines anderen Queue-Baum-Eintrags.", required: true), RouterOSFieldSchema(key: "packet-mark", label: "Paket-/Verbindungsmarkierung", kind: .text, help: "Name der Mangle-Markierung, auf die dieser Eintrag wirkt."), RouterOSFieldSchema(key: "max-limit", label: "Max. Bandbreite", kind: .text, help: "Einzelwert, z.B. 20M."), - RouterOSFieldSchema(key: "disabled", label: "Deaktiviert", kind: .bool, help: "", defaultValue: "no") + RouterOSFieldSchema(key: "disabled", label: "Deaktiviert", kind: .bool, + help: "Begrenzung inaktiv schalten, ohne sie zu löschen.", defaultValue: "no") ], listColumns: ["name", "parent", "max-limit"] ) @@ -584,7 +622,9 @@ enum RouterOSSchemaCatalog { displayName: "Router-Name", summary: "Der Name, unter dem sich der Router meldet (z.B. in Winbox/Terminal-Prompt).", explanation: "", - fields: [RouterOSFieldSchema(key: "name", label: "Name", kind: .text, help: "", required: true)], + fields: [ + RouterOSFieldSchema(key: "name", label: "Name", kind: .text, help: "Frei wählbar, z.B. MeinRouter.", required: true) + ], listColumns: ["name"], isSingleton: true ), @@ -605,7 +645,8 @@ enum RouterOSSchemaCatalog { explanation: "", warning: "Falsche Systemzeit kann Zertifikatsprüfungen (HTTPS/REST) und Log-Zeitstempel durcheinanderbringen.", fields: [ - RouterOSFieldSchema(key: "enabled", label: "Aktiviert", kind: .bool, help: "", defaultValue: "yes"), + RouterOSFieldSchema(key: "enabled", label: "Aktiviert", kind: .bool, + help: "Zeitsynchronisation ein-/ausschalten.", defaultValue: "yes"), RouterOSFieldSchema(key: "servers", label: "Zeitserver", kind: .text, help: "Ein oder mehrere Server, kommagetrennt, z.B. pool.ntp.org.") ], @@ -622,14 +663,15 @@ enum RouterOSSchemaCatalog { explanation: "", warning: "Ein fehlerhaftes geplantes Skript kann unbeaufsichtigt wiederholt Änderungen vornehmen — vor dem Einplanen einmal manuell testen.", fields: [ - RouterOSFieldSchema(key: "name", label: "Name", kind: .text, help: "", required: true), + RouterOSFieldSchema(key: "name", label: "Name", kind: .text, help: "Frei wählbar, z.B. taeglicher-neustart.", required: true), RouterOSFieldSchema(key: "start-time", label: "Startzeit", kind: .text, help: "Z.B. 00:00:00 oder \"startup\"."), RouterOSFieldSchema(key: "interval", label: "Intervall", kind: .text, help: "Wie oft wiederholt wird, z.B. 1d oder 01:00:00. Leer = nur einmal zur Startzeit."), RouterOSFieldSchema(key: "on-event", label: "Auszuführendes Skript", kind: .menuItemPick(menuPath: "/system script", restPath: "system/script", valueField: "name"), help: "Name eines zuvor unter \"Skripte\" angelegten Skripts."), - RouterOSFieldSchema(key: "disabled", label: "Deaktiviert", kind: .bool, help: "", defaultValue: "no") + RouterOSFieldSchema(key: "disabled", label: "Deaktiviert", kind: .bool, + help: "Zeitplan inaktiv schalten, ohne ihn zu löschen.", defaultValue: "no") ], listColumns: ["name", "interval"] ), @@ -640,7 +682,7 @@ enum RouterOSSchemaCatalog { explanation: "", warning: "Skripte laufen mit den Rechten des Routers selbst — kein Unterschied zu manuell eingegebenen Befehlen.", fields: [ - RouterOSFieldSchema(key: "name", label: "Name", kind: .text, help: "", required: true), + RouterOSFieldSchema(key: "name", label: "Name", kind: .text, help: "Frei wählbar, z.B. backup-taeglich.", required: true), RouterOSFieldSchema(key: "source", label: "Skript-Inhalt", kind: .text, help: "RouterOS-Befehle, z.B. \":log info \\\"Test\\\"\".") ], @@ -653,11 +695,12 @@ enum RouterOSSchemaCatalog { explanation: "", warning: "Das zum Verbinden genutzte eigene Konto hier nicht versehentlich löschen oder herabstufen.", fields: [ - RouterOSFieldSchema(key: "name", label: "Benutzername", kind: .text, help: "", required: true), - RouterOSFieldSchema(key: "password", label: "Passwort", kind: .text, help: ""), + RouterOSFieldSchema(key: "name", label: "Benutzername", kind: .text, help: "Frei wählbar, z.B. admin2.", required: true), + RouterOSFieldSchema(key: "password", label: "Passwort", kind: .text, help: "Ausreichend lang und zufällig wählen."), RouterOSFieldSchema(key: "group", label: "Rechte-Gruppe", kind: .enumPick(options: ["full", "write", "read"]), help: "full = Vollzugriff, write = ohne Benutzerverwaltung, read = nur lesen.", defaultValue: "full"), - RouterOSFieldSchema(key: "disabled", label: "Deaktiviert", kind: .bool, help: "", defaultValue: "no") + RouterOSFieldSchema(key: "disabled", label: "Deaktiviert", kind: .bool, + help: "Konto inaktiv schalten, ohne es zu löschen.", defaultValue: "no") ], listColumns: ["name", "group"] ) @@ -672,12 +715,15 @@ enum RouterOSSchemaCatalog { summary: "Überwacht laufend, ob eine Adresse erreichbar ist, und kann bei Statuswechsel ein Skript auslösen.", explanation: "", fields: [ - RouterOSFieldSchema(key: "host", label: "Zu überwachende Adresse", kind: .text, help: "", required: true), + RouterOSFieldSchema(key: "host", label: "Zu überwachende Adresse", kind: .text, + help: "IP-Adresse oder Hostname, ohne Präfix, z.B. 192.168.88.1 oder 8.8.8.8.", required: true), RouterOSFieldSchema(key: "interval", label: "Prüf-Intervall", kind: .text, help: "Z.B. 00:00:10.", defaultValue: "00:00:10"), RouterOSFieldSchema(key: "up-script", label: "Skript bei Erreichbarkeit", kind: .text, help: "Name eines Skripts unter \"Skripte\"."), - RouterOSFieldSchema(key: "down-script", label: "Skript bei Nichterreichbarkeit", kind: .text, help: ""), - RouterOSFieldSchema(key: "disabled", label: "Deaktiviert", kind: .bool, help: "", defaultValue: "no") + RouterOSFieldSchema(key: "down-script", label: "Skript bei Nichterreichbarkeit", kind: .text, + help: "Name eines Skripts unter \"Skripte\"."), + RouterOSFieldSchema(key: "disabled", label: "Deaktiviert", kind: .bool, + help: "Überwachung inaktiv schalten, ohne sie zu löschen.", defaultValue: "no") ], listColumns: ["host"] ), @@ -687,11 +733,12 @@ enum RouterOSSchemaCatalog { summary: "Postausgangs-Server, den Scheduler/Netwatch/Skripte für Benachrichtigungen nutzen.", explanation: "Ein einzelner, geräteweiter Satz Einstellungen — kein Menü mit mehreren Einträgen.", fields: [ - RouterOSFieldSchema(key: "address", label: "Mailserver-Adresse", kind: .text, help: "", required: true), + RouterOSFieldSchema(key: "address", label: "Mailserver-Adresse", kind: .text, + help: "Hostname oder IP-Adresse des Mailservers, z.B. smtp.gmail.com.", required: true), RouterOSFieldSchema(key: "port", label: "Port", kind: .int, help: "Meist 587 (STARTTLS) oder 465 (SSL)."), - RouterOSFieldSchema(key: "from", label: "Absenderadresse", kind: .text, help: ""), - RouterOSFieldSchema(key: "user", label: "Benutzername", kind: .text, help: ""), - RouterOSFieldSchema(key: "password", label: "Passwort", kind: .text, help: "") + RouterOSFieldSchema(key: "from", label: "Absenderadresse", kind: .text, help: "Z.B. router@example.com."), + RouterOSFieldSchema(key: "user", label: "Benutzername", kind: .text, help: "Zugangsdaten des Mailservers."), + RouterOSFieldSchema(key: "password", label: "Passwort", kind: .text, help: "Zugangsdaten des Mailservers.") ], listColumns: ["address"], isSingleton: true diff --git a/RouterOSAssistant/Features/Expert/ExpertMenuDetailView.swift b/RouterOSAssistant/Features/Expert/ExpertMenuDetailView.swift index 4415a9c..9b88184 100644 --- a/RouterOSAssistant/Features/Expert/ExpertMenuDetailView.swift +++ b/RouterOSAssistant/Features/Expert/ExpertMenuDetailView.swift @@ -7,6 +7,12 @@ struct ExpertMenuDetailView: View { var body: some View { Form { Section { + Text(schema.displayName) + .font(.title2) + .bold() + Text(schema.category.rawValue) + .font(.caption) + .foregroundStyle(.secondary) Text(schema.explanation) if let warning = schema.warning { Label(warning, systemImage: "exclamationmark.triangle") diff --git a/RouterOSAssistant/Features/Expert/ExpertView.swift b/RouterOSAssistant/Features/Expert/ExpertView.swift index e35f08a..2a3ec3e 100644 --- a/RouterOSAssistant/Features/Expert/ExpertView.swift +++ b/RouterOSAssistant/Features/Expert/ExpertView.swift @@ -25,8 +25,19 @@ struct ExpertView: View { if !schemas.isEmpty { Section(category.rawValue) { ForEach(schemas) { schema in - Button(schema.displayName) { + let isSelected = viewModel.selectedSchema?.menuPath == schema.menuPath + Button { viewModel.open(schema) + } label: { + Text(schema.displayName) + .frame(maxWidth: .infinity, alignment: .leading) + .padding(.vertical, 4) + .padding(.horizontal, 6) + .background( + RoundedRectangle(cornerRadius: 6) + .fill(isSelected ? Color.blue.opacity(0.25) : Color.clear) + ) + .contentShape(Rectangle()) } .buttonStyle(.plain) }