M9: Einrichten-Wizard bekommt einen Einfach/Experte-Modusschalter (ModeStepView). Einfach überspringt VLAN, erlaubt nur ein LAN-Netzwerk ohne Isolation, Firewall-Grundschutz fest an. M10: neuer "Experte"-Tab mit generischem Motor (RouterOSMenuItem, RouterOSCommand.remove, ConnectionService.fetchMenuItems, freies "eigener Menüpfad"-Feld) plus kuratierten Formularen mit Tooltips (RouterOSSchemaCatalog) für Firewall/NAT/Mangle/Raw/Adress-Listen, Interfaces, IP, VPN, WLAN, Queues, System, Werkzeuge. Live gegen einen hEX-Testrouter verifiziert (erst per SSH, dann vom Nutzer selbst in der App), dabei 7 reale Bugs gefunden und gefixt — der wichtigste: RouterOS' SSH-CLI gibt bei fehlgeschlagenen Befehlen Exit-Code 0 zurück, wodurch apply() app-weit Fehler verschluckte statt sie zu melden. Danach ergänzt: Bestätigungsdialog vor Anlegen/Ändern + Auto-Backup vor dem ersten Experte-Tab-Schreibvorgang je Sitzung (Angleichung an den Wizard), sowie ein Dauer-Editor (Tage/Std/Min/Sek) für Lease-/Ablaufzeit-Felder statt Freitext. Details zu allen Bugs/Fixes: HANDOFF.md, CHATLOG.md. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01EW3r6rW1xCf6UT5jNvt6rn
116 lines
4.9 KiB
Swift
116 lines
4.9 KiB
Swift
import Foundation
|
|
|
|
/// One configurable field of a curated RouterOS menu — drives the Expert tool's add/edit form
|
|
/// (label, input kind, tooltip) instead of a bare key=value box.
|
|
struct RouterOSFieldSchema: Identifiable, Equatable {
|
|
enum Kind: Equatable {
|
|
case text
|
|
case bool
|
|
case int
|
|
/// Fixed choice list, e.g. a chain name or an action.
|
|
case enumPick(options: [String])
|
|
/// Populated at runtime from the connected device's interfaces.
|
|
case interfacePick
|
|
/// Populated at runtime from the existing items of another RouterOS menu — e.g. a DHCP
|
|
/// server's "address-pool" field should offer the pools already defined under
|
|
/// "/ip pool", not require typing the name from memory. `valueField` is which field of
|
|
/// that menu's items to show/use (e.g. "name", or "list" for address-lists).
|
|
case menuItemPick(menuPath: String, restPath: String, valueField: String)
|
|
/// A RouterOS time value (e.g. "1d12h30m" or a bare number of seconds) — edited as
|
|
/// separate day/hour/minute/second steppers instead of free text, since the suffix
|
|
/// syntax isn't obvious (confirmed live: a bare number is seconds, units combine in one
|
|
/// string, e.g. DHCP "lease-time").
|
|
case duration
|
|
}
|
|
|
|
var id: String { key }
|
|
/// The actual RouterOS parameter name, e.g. "chain", "action", "dst-port".
|
|
let key: String
|
|
let label: String
|
|
let kind: Kind
|
|
/// Tooltip: what this field does.
|
|
let help: String
|
|
let defaultValue: String?
|
|
/// Whether a value is required before the item can be added.
|
|
let required: Bool
|
|
|
|
init(key: String, label: String, kind: Kind, help: String, defaultValue: String? = nil, required: Bool = false) {
|
|
self.key = key
|
|
self.label = label
|
|
self.kind = kind
|
|
self.help = help
|
|
self.defaultValue = defaultValue
|
|
self.required = required
|
|
}
|
|
}
|
|
|
|
enum RouterOSMenuCategory: String, CaseIterable, Identifiable {
|
|
case firewallFilter = "Firewall: Filter-Regeln"
|
|
case firewallNat = "Firewall: NAT (Portweiterleitung etc.)"
|
|
case firewallMangle = "Firewall: Mangle (Markierung/QoS-Vorbereitung)"
|
|
case firewallRaw = "Firewall: Raw (vor Connection-Tracking)"
|
|
case firewallAddressLists = "Firewall: Adress-Listen"
|
|
case interfaces = "Interfaces (Bridge, VLAN, VPN-Tunnel...)"
|
|
case ipAddressing = "IP-Adressierung & Dienste"
|
|
case routing = "Routing"
|
|
case vpn = "VPN-Server/Clients"
|
|
case wireless = "WLAN / CAPsMAN"
|
|
case queues = "Queues / Bandbreiten-Steuerung"
|
|
case system = "System"
|
|
case tools = "Werkzeuge & Überwachung"
|
|
|
|
var id: String { rawValue }
|
|
}
|
|
|
|
/// Describes one RouterOS menu the Expert tool can browse/edit. `fields` is the curated form;
|
|
/// an empty array means "no curated form yet" — the Expert tool falls back to a fully generic
|
|
/// key=value editor for that menu, so every menu is still reachable even before it's curated.
|
|
struct RouterOSMenuSchema: Identifiable, Equatable {
|
|
var id: String { menuPath }
|
|
let menuPath: String
|
|
let restPath: String
|
|
let category: RouterOSMenuCategory
|
|
let displayName: String
|
|
/// One-line "what is this" shown above the item list.
|
|
let summary: String
|
|
/// Longer "was passiert, wenn ich hier etwas ändere" explanation.
|
|
let explanation: String
|
|
/// Dependency/gotcha callout, shown as a warning box when present.
|
|
let warning: String?
|
|
let fields: [RouterOSFieldSchema]
|
|
/// A couple of field keys to show per row in the item list (e.g. ["chain", "action"]) so
|
|
/// entries are recognizable without opening each one. Falls back to showing every field.
|
|
let listColumns: [String]
|
|
/// True for menus that hold exactly one settable item, not a list (e.g. "/ip dns",
|
|
/// "/system identity") — confirmed live against RouterOS 7.24.2: these reject
|
|
/// "print terse" outright ("bad parameter terse"), only support plain "print" (colon-value
|
|
/// output) and a direct "set key=value" with no `[find ...]`, and can't be added to or
|
|
/// removed. The Expert tool skips the add/remove UI and the transports fetch/apply them
|
|
/// differently for menus flagged this way.
|
|
let isSingleton: Bool
|
|
|
|
init(
|
|
menuPath: String,
|
|
restPath: String,
|
|
category: RouterOSMenuCategory,
|
|
displayName: String,
|
|
summary: String,
|
|
explanation: String,
|
|
warning: String? = nil,
|
|
fields: [RouterOSFieldSchema] = [],
|
|
listColumns: [String] = [],
|
|
isSingleton: Bool = false
|
|
) {
|
|
self.menuPath = menuPath
|
|
self.restPath = restPath
|
|
self.category = category
|
|
self.displayName = displayName
|
|
self.summary = summary
|
|
self.explanation = explanation
|
|
self.warning = warning
|
|
self.fields = fields
|
|
self.listColumns = listColumns
|
|
self.isSingleton = isSingleton
|
|
}
|
|
}
|