Files
RouterOS/RouterOSAssistant/Core/Models/RouterOSSchema.swift
T
KayandClaude Sonnet 5 6f29bbcb2b M30: Experte-Tab Mode-Taste-Menü + SSH-Trust-/REST-Fixes
Neues Schema für /system routerboard mode-button (enabled/on-event/
hold-time), live gegen echte Hardware verifiziert.

Dabei drei echte Bugs gefunden und gefixt:
- SSH-Trust-Dead-End beim ersten Experte-Tab-Schreiben (Backup-vor-
  Schreiben-Pfad hatte keinen Weg, den Trust-Dialog auszulösen) —
  ConnectionService.noteUntrustedSSHHostKey
- RouterOSFieldSchema.clearable: optionale Felder senden nie mehr
  einen expliziten Leer-Wert, wenn das RouterOS ablehnt
- RouterOSMenuSchema.writesRequireSSH + ConnectionService.applyViaSSH:
  für Menüs ohne REST-Anbindung (bewiesen per direktem SSH-Test) wird
  zwingend eine dedizierte SSH-Verbindung genutzt statt REST

Live Ende-zu-Ende bestätigt: Skript anlegen, Mode-Taste zuweisen,
Tastendruck löst Skript korrekt aus.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-09-17 15:14:31 +02:00

132 lines
6.1 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
/// Whether blanking this field and saving should send an explicit `field=""` ("clear this")
/// to RouterOS. True by default — this is the normal, tested behavior for plain free-text
/// fields like "comment". False for fields whose RouterOS type isn't really free text (e.g. a
/// time-interval-range like mode-button's "hold-time") — confirmed live: RouterOS' REST API
/// can 500 on an empty string for a non-text property, where a leftover/never-set blank value
/// in the form should just mean "leave this alone", not "actively clear it".
let clearable: Bool
init(key: String, label: String, kind: Kind, help: String, defaultValue: String? = nil, required: Bool = false, clearable: Bool = true) {
self.key = key
self.label = label
self.kind = kind
self.help = help
self.defaultValue = defaultValue
self.required = required
self.clearable = clearable
}
}
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
/// True for menus confirmed live not to work over RouterOS' REST API at all — the exact same
/// write succeeds instantly over plain SSH, REST 500s regardless of which fields are sent (not
/// a data-formatting issue this app can work around). Routes writes through a dedicated SSH
/// connection instead (`ConnectionService.applyViaSSH`), bypassing whatever the session's
/// active transport is. Confirmed for `/system routerboard mode-button` (2026-09-17).
let writesRequireSSH: Bool
init(
menuPath: String,
restPath: String,
category: RouterOSMenuCategory,
displayName: String,
summary: String,
explanation: String,
warning: String? = nil,
fields: [RouterOSFieldSchema] = [],
listColumns: [String] = [],
isSingleton: Bool = false,
writesRequireSSH: 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
self.writesRequireSSH = writesRequireSSH
}
}