Geräte-Tab: "Feste Zuweisung entfernen" (Rückweg zu dynamisch)

RouterOS kennt kein make-dynamic als Umkehrung von make-static (per
Recherche-Agent gegen die offizielle DHCP-Doku bestätigt: nur
check-status/make-static/send-reconfigure existieren) — der offizielle
Weg ist, die statische Lease zu entfernen; das Gerät bekommt beim
naechsten Verbindungsaufbau automatisch wieder eine dynamische Adresse,
moeglicherweise eine andere IP als zuvor.

Neuer Kontextmenü-Eintrag bei fest zugewiesenen Geräten im Geräte-Tab,
mit Bestätigungsdialog (erklärt den Ablauf) und Nachkontrolle, dass der
Lease-Eintrag wirklich entfernt wurde, bevor Erfolg gemeldet wird -
gleiche Vorsicht wie beim bestehenden "Feste IP zuweisen". Vom Nutzer
live bestätigt (fest zuweisen -> entfernen -> Kabel/WLAN neu verbinden
-> wieder dynamisch), kein neuer Bug diesmal.

HANDOFF.md/CHATLOG.md aktualisiert: "Zurück auf dynamisch" aus den
offenen Punkten entfernt, M12-Beschreibung ergänzt.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CTgRxJTzaQwaRkngbaE1GJ
This commit is contained in:
Kay
2026-09-14 14:11:34 +02:00
co-authored by Claude Sonnet 5
parent e2b303963a
commit 8e99ab8e41
4 changed files with 145 additions and 36 deletions
+34 -4
View File
@@ -523,7 +523,37 @@ falsche Negative. Alle vier zusammen ergaben eine ungewöhnlich lange,
mehrstufige Fehlersuche mit wiederholten Rohdaten-Abgleichen und direkten
Router-Gegenchecks (SSH-CLI + Winbox) durch den Nutzer. Commit für M11
separat (`10f30a8`), M12 + HANDOFF/CHATLOG-Update in diesem Commit.
Offen: "Zurück auf dynamisch"-Aktion fehlt noch im Geräte-Tab; ob dieselbe
`.id`-Überlagerung auch im Experte-Tab bei anderen Menüs falsch zuordnen
kann, ist ungeprüft; alle bereits vorher offenen Punkte unverändert
(siehe HANDOFF.md Nächste Schritte).
Offen (Stand direkt nach M12): "Zurück auf dynamisch"-Aktion fehlt noch
im Geräte-Tab; ob dieselbe `.id`-Überlagerung auch im Experte-Tab bei
anderen Menüs falsch zuordnen kann, ist ungeprüft; alle bereits vorher
offenen Punkte unverändert (siehe HANDOFF.md Nächste Schritte).
## Nachtrag: "Feste Zuweisung entfernen"
- "jetzt Statisch IP zurück in dynamische IP. das scheint nicht so
einfach zu funktionieren. Meine Recherchen ergeben: man muss erst die
statische IP löschen, dann den client disconnecten und dann wieder
connecten. ... kannst du das bestätigen? ... gib mir ein
Lösungsvorschlag" → per Recherche-Agent gegen die offizielle
RouterOS-DHCP-Doku geprüft: bestätigt, kein `make-dynamic`-Befehl
existiert (nur `check-status`/`make-static`/`send-reconfigure`), der
offizielle Weg ist `remove` der Lease, Client muss selbst neu anfragen
(Kabel/WLAN trennen+verbinden, Neustart, oder eigener Renewal-Timer),
keine Garantie auf dieselbe IP danach. Lösungsvorschlag vorgelegt: neuer
Kontextmenü-Eintrag "Feste Zuweisung entfernen" bei festen Geräten
(`.remove` über MAC-Adresse, Bestätigungsdialog mit diesen Hinweisen,
Nachkontrolle dass der Eintrag wirklich weg ist).
- "ja, bau das so" → `DevicesViewModel.pendingStaticRemoval`/
`pendingRemovalCommand`/`confirmStaticRemoval`/`cancelStaticRemoval`,
zweiter `.confirmationDialog` in `DevicesView` (gleiches
Entkopplungs-Muster wie beim Zuweisen-Dialog, siehe Bug 16). Build+Tests
grün, Release neu gebaut+deployt.
- "das hat sofort funktioniert, habe die feste ip gelöscht und den client
danach disconnectet und wieder connected, alles ok. ip ist wieder
dynamisch. lassen wir so" — kein neuer Bug, erster Versuch erfolgreich,
kompletter Kreislauf (fest zuweisen → entfernen → neu verbinden →
dynamisch) vom Nutzer selbst bestätigt.
- "ja, committen und beides aktualisieren" → dieser Eintrag, HANDOFF.md
(M12-Beschreibung/Nächste-Schritte/Bekannte-Einschränkungen aktualisiert,
"Zurück auf dynamisch" als erledigt markiert) aktualisiert, Commit
erstellt.
+27 -27
View File
@@ -21,13 +21,16 @@ farblich nach Verbindungsart getrennt, Hover/Klick hebt Zusammenhänge
hervor). **M12 "Geräte"** ist ein LAN-Scanner (DHCP-Leases + ARP +
Bridge-Host-Tabelle, gruppiert als eine Tabelle pro physischem Port) mit
Rechtsklick-Aktion "Feste IP zuweisen" (RouterOS' "Make Static", per
`/ip dhcp-server lease make-static`). Beide Tabs live gegen den
hEX-Testrouter verifiziert — M12 dabei besonders gründlich, weil die
Fest/Dynamisch-Erkennung und die Zuweisung selbst sich als überraschend
zäh erwiesen: **vier** echte Bugs gefunden und gefixt (Bug 1417 unten),
am Ende vom Nutzer selbst bestätigt ("das funktioniert jetzt super").
Release-Build unter `/Applications/RouterOS Assistant.app` ist auf
aktuellem Stand.
`/ip dhcp-server lease make-static`) sowie ihr Gegenstück "Feste
Zuweisung entfernen" (RouterOS kennt kein `make-dynamic`, nur `remove`
vorher per Recherche-Agent gegen die offizielle Doku geprüft statt
geraten). Beide Tabs live gegen den hEX-Testrouter verifiziert — M12
dabei besonders gründlich, weil die Fest/Dynamisch-Erkennung und die
Zuweisung selbst sich als überraschend zäh erwiesen: **vier** echte Bugs
gefunden und gefixt (Bug 1417 unten), am Ende vom Nutzer selbst
bestätigt ("das funktioniert jetzt super", und für den Rückweg "das hat
sofort funktioniert"). Release-Build unter
`/Applications/RouterOS Assistant.app` ist auf aktuellem Stand.
## Ziel
@@ -103,7 +106,7 @@ RouterOSAssistant/
Wizard/Steps/Setup/ — Einrichten-Tab: Mode → Wan → Lan → (Vlan nur Experte) → Wifi → Firewall → Review/Apply
Expert/ — Experte-Tab (M10): ExpertView (Kategorie-/Menüliste + "eigener Pfad"), ExpertViewModel, ExpertMenuDetailView (Item-Liste + Add/Edit-Sheet)
Overview/ — Übersicht-Tab (M11): OverviewView (Diagramm+Legende+Detailpanel), OverviewViewModel (buildGraph, rein), OverviewLayout (Spalten/Zeilen-Geometrie)
Devices/ — Geräte-Tab (M12): DevicesView (Port-Tabellen+Rechtsklick+Rohdaten-Sheet), DevicesViewModel (buildDevices/buildPortGroups, rein; confirmStaticAssignment)
Devices/ — Geräte-Tab (M12): DevicesView (Port-Tabellen+Rechtsklick+Rohdaten-Sheet), DevicesViewModel (buildDevices/buildPortGroups, rein; confirmStaticAssignment/confirmStaticRemoval)
Backup/ — Sicherungen-Tab (Ordner wählen, Gefahrenzone: Werkseinstellungen wiederherstellen)
Resources/Assets.xcassets/AppIcon.appiconset/ — App-Icon "Signal Router" (16px1024px)
RouterOSAssistantTests/ — reine Unit-Tests (Command-Builder, CLI-Parser, Fallback-Logik via Mock-Transport)
@@ -377,9 +380,6 @@ wiederholen.
(M12/Bug 14) ist zusätzlich auf einer nirgends offiziell dokumentierten
Annahme aufgebaut (Query-String-Filter `?feld=wert` auf einem GET) —
komplett unverifiziert.
- **"Zurück auf dynamisch" fehlt im Geräte-Tab (M12)** — es gibt nur
"Feste IP zuweisen" (Winbox' "Make Static"), keine Umkehrung. Kein Bug,
einfach noch nicht gebaut; Nutzer hat danach gefragt.
- **`fetchMenuItems`s `.id`-Positions-Überlagerung: Verlässlichkeit für
andere Menüs ungeprüft** (siehe Bug 15) — bei `/ip dhcp-server lease`
live als falsch bestätigt (`.id` landete auf der falschen Zeile), für
@@ -600,13 +600,18 @@ beim Ändern die RouterOS-Suffix-Form zurück) — angewendet auf DHCP-Server
System/Werkzeuge/Mangle/Raw (siehe `OverviewGraph.unmappedAreas`),
weiterhin nur über den Experte-Tab erreichbar.
- ✅ M12: Geräte-Tab (LAN-Scanner: DHCP-Leases+ARP+Bridge-Host, eine
Tabelle pro physischem Port, "Feste IP zuweisen" für dynamische Leases)
— **live gegen Hardware verifiziert und vom Nutzer bestätigt
funktionierend** ("das funktioniert jetzt super"), nach vier gefundenen
und gefixten Bugs (Bug 1417, siehe oben) — die aufwendigste
Tabelle pro physischem Port, "Feste IP zuweisen" für dynamische Leases,
"Feste Zuweisung entfernen" für den Rückweg) — **live gegen Hardware
verifiziert und vom Nutzer bestätigt funktionierend** ("das funktioniert
jetzt super" / "das hat sofort funktioniert"), nach vier gefundenen und
gefixten Bugs beim Hinweg (Bug 1417, siehe oben) — die aufwendigste
Live-Verifikation aller Milestones bisher, mehrere Diagnose-Runden mit
Rohdaten-Vergleichen und direkten Router-Gegenchecks (SSH-CLI + Winbox).
Fehlt noch: "Zurück auf dynamisch" (siehe Bekannte Einschränkungen).
Rückweg ("Feste Zuweisung entfernen") lief dagegen beim ersten Versuch
fehlerfrei — RouterOS-Doku vorher per Recherche-Agent geprüft (kein
`make-dynamic`, nur `remove` + Client muss neu verbinden), nicht
geraten. Nutzer bestätigte den vollen Kreislauf (fest zuweisen →
entfernen → Kabel/WLAN trennen+verbinden → wieder dynamisch).
## Nächste Schritte
@@ -649,25 +654,20 @@ beim Ändern die RouterOS-Suffix-Form zurück) — angewendet auf DHCP-Server
"Wiederherstellen"-Button pro Eintrag im Sicherungen-Tab
(`BackupListView`) mit Bestätigungsdialog (ähnlich der bestehenden
Werksreset-Gefahrenzone).
11. **Geräte-Tab: "Zurück auf dynamisch"-Aktion bauen** — Gegenstück zu
"Feste IP zuweisen". RouterOS-Weg dafür noch nicht recherchiert/live
geprüft (vermutlich `remove` der Lease und Neuanlage, oder ein
eigenes Menü-Kommando wie bei `make-static` — vor dem Bauen klären,
nicht raten, siehe Bug 17 als Warnung).
12. **`.id`-Positions-Überlagerung (`fetchMenuItems`) auf weitere Menüs
11. **`.id`-Positions-Überlagerung (`fetchMenuItems`) auf weitere Menüs
prüfen** — live als falsch bestätigt für `/ip dhcp-server lease`
(Bug 15). Betrifft potenziell jedes `.set`/`.remove` im Experte-Tab.
Am ehesten zu prüfen: bei einem Menü mit mehreren gleichzeitig
vorhandenen Einträgen `:put [<Pfad> find]` und `<Pfad> print terse`
unabhängig ausführen und die Reihenfolgen von Hand vergleichen.
13. Optional: den neuen Dauer-Editor auch für den Geräte-Tab nutzen, falls
12. Optional: den neuen Dauer-Editor auch für den Geräte-Tab nutzen, falls
dort künftig Zeitfelder (z.B. eine Lease-Zeit-Anzeige) dazukommen.
14. `Kay-Uwes-iMac` (ether2, `defconf`) und der Test-Laptop `DEDELLB2M6GK3`
13. `Kay-Uwes-iMac` (ether2, `defconf`) und der Test-Laptop `DEDELLB2M6GK3`
(ether3, `dhcp5`/`defconf` je nach Testrunde) sind **echte Geräte des
Nutzers**, mit denen M12 live durchgetestet wurde — keine
Test-Artefakte, nicht aufräumen. Der Laptop hatte am Ende der Session
testweise eine feste IP zugewiesen bekommen; ob das der gewünschte
Dauerzustand ist oder nur zum Testen war, mit dem Nutzer klären.
Test-Artefakte, nicht aufräumen. Beide Geräte am Ende der Session
wieder dynamisch (kompletter Kreislauf fest→entfernt→dynamisch vom
Nutzer selbst am Laptop bestätigt).
Kein Gitea-Remote vorhanden — falls der Nutzer später eine Gitea-Instanz
aufsetzt (z.B. selbst gehostet auf der vorhandenen OMV-NAS, siehe
@@ -1,8 +1,8 @@
import SwiftUI
/// "Geräte" tab: LAN scanner one table per physical Ethernet/WLAN port, each listing the
/// devices resolved onto it (name/IP/MAC/status), plus a right-click action to give a device a
/// permanent static IP the same way Winbox's "Make Static" does.
/// devices resolved onto it (name/IP/MAC/status), plus right-click actions to give a device a
/// permanent static IP (Winbox's "Make Static") or remove that reservation again.
struct DevicesView: View {
@ObservedObject var connectionService: ConnectionService
@StateObject private var viewModel: DevicesViewModel
@@ -16,6 +16,8 @@ struct DevicesView: View {
/// dialogs elsewhere (BackupListView's factory-reset dialog): the dialog's own visibility and
/// its payload are two separate pieces of state.
@State private var showStaticConfirmation = false
/// Same reasoning as `showStaticConfirmation` independent of `pendingStaticRemoval`.
@State private var showRemovalConfirmation = false
init(connectionService: ConnectionService) {
self.connectionService = connectionService
@@ -105,6 +107,28 @@ struct DevicesView: View {
)
}
}
.confirmationDialog(
"Feste Zuweisung entfernen?",
isPresented: $showRemovalConfirmation,
titleVisibility: .visible
) {
Button("Entfernen", role: .destructive) {
Task { await viewModel.confirmStaticRemoval() }
}
Button("Abbrechen", role: .cancel) { viewModel.cancelStaticRemoval() }
} message: {
if let device = viewModel.pendingStaticRemoval {
Text(
"Die feste Zuweisung für \(device.macAddress)"
+ (device.hostName.map { " (\($0))" } ?? "")
+ " wird entfernt. RouterOS kennt keine direkte Umkehrung von \"Make Static\""
+ " das Gerät bekommt erst beim nächsten Verbindungsaufbau (Kabel/WLAN neu"
+ " verbinden, Neustart) automatisch wieder eine Adresse per DHCP, eventuell"
+ " eine andere IP als bisher."
+ "\n\nBefehl: \(viewModel.pendingRemovalCommand?.cliLine ?? "")"
)
}
}
.alert(
"Fehler",
isPresented: Binding(
@@ -127,7 +151,12 @@ struct DevicesView: View {
private func deviceMenu(for device: LanDevice) -> some View {
if device.hasLease {
if device.isStatic {
Text("Bereits fest zugewiesen")
Button {
viewModel.pendingStaticRemoval = device
showRemovalConfirmation = true
} label: {
Label("Feste Zuweisung entfernen", systemImage: "pin.slash")
}
} else {
Button {
viewModel.pendingStaticAssignment = device
@@ -2,8 +2,9 @@ import Foundation
/// Builds the "Geräte" tab's LAN device list from a live router DHCP leases enriched with the
/// physical port each device was learned on (ARP + bridge host table) grouped into one table
/// per physical port, and drives the one write action this tab offers: converting a dynamic
/// lease to a permanent static one.
/// per physical port, and drives the two write actions this tab offers: converting a dynamic
/// lease to a permanent static one, and removing a static lease again (RouterOS has no reverse
/// of "make-static", so "back to dynamic" is just removal see `pendingStaticRemoval`).
@MainActor
final class DevicesViewModel: ObservableObject {
@Published private(set) var devices: [LanDevice] = []
@@ -12,6 +13,12 @@ final class DevicesViewModel: ObservableObject {
@Published private(set) var loadError: String?
@Published var pendingStaticAssignment: LanDevice?
/// The device whose static reservation the user is about to remove RouterOS has no
/// "make-dynamic" (confirmed against the official DHCP docs: only check-status, make-static,
/// send-reconfigure exist for this menu), so "back to dynamic" is genuinely just removing the
/// static lease; the device gets a fresh dynamic lease on its own next DHCP request, not
/// immediately or necessarily on the same IP.
@Published var pendingStaticRemoval: LanDevice?
@Published private(set) var isApplying = false
@Published private(set) var applyError: String?
@@ -76,6 +83,17 @@ final class DevicesViewModel: ObservableObject {
)
}
/// The command "Feste Zuweisung entfernen" would run matched by MAC, same reasoning as
/// `pendingCommand`.
var pendingRemovalCommand: RouterOSCommand? {
guard let device = pendingStaticRemoval, device.hasLease else { return nil }
return .remove(
menuPath: "/ip dhcp-server lease", restPath: "ip/dhcp-server/lease",
matchField: "mac-address", matchValue: device.macAddress,
summary: "Feste Zuweisung entfernen für \(device.macAddress)"
)
}
/// Backs up once per connection before this tab's first write same shared flag the Expert
/// tool uses (`ConnectionService.hasExpertToolBackedUpThisSession`), so a session that already
/// backed up via one "power tool" doesn't back up again via the other.
@@ -125,6 +143,38 @@ final class DevicesViewModel: ObservableObject {
pendingStaticAssignment = nil
}
func confirmStaticRemoval() async {
guard let command = pendingRemovalCommand, let device = pendingStaticRemoval else { return }
isApplying = true
applyError = nil
do {
try await ensureSessionBackup()
try await connectionService.apply(command)
// Same lesson as `confirmStaticAssignment` (Bug 10/17 in HANDOFF.md): empty output
// isn't proof it worked. Verify the lease for this MAC is actually gone a lingering
// "no error" success is worth less than an honest check.
let remainingLeases = try await connectionService.fetchMenuItems(
menuPath: "/ip dhcp-server lease", restPath: "ip/dhcp-server/lease"
)
let stillPresent = remainingLeases.contains { $0.fields["mac-address"]?.lowercased() == device.macAddress.lowercased() }
guard !stillPresent else {
throw RouterOSError.invalidResponse(
"Befehl lief ohne Fehlermeldung, aber der Lease-Eintrag ist weiterhin vorhanden. Bitte manuell mit \"/ip dhcp-server lease print\" kontrollieren."
)
}
pendingStaticRemoval = nil
await load()
} catch {
pendingStaticRemoval = nil
applyError = error.localizedDescription
}
isApplying = false
}
func cancelStaticRemoval() {
pendingStaticRemoval = nil
}
func dismissApplyError() {
applyError = nil
}