forked from kay/RouterOS
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:
+34
-4
@@ -523,7 +523,37 @@ falsche Negative. Alle vier zusammen ergaben eine ungewöhnlich lange,
|
|||||||
mehrstufige Fehlersuche mit wiederholten Rohdaten-Abgleichen und direkten
|
mehrstufige Fehlersuche mit wiederholten Rohdaten-Abgleichen und direkten
|
||||||
Router-Gegenchecks (SSH-CLI + Winbox) durch den Nutzer. Commit für M11
|
Router-Gegenchecks (SSH-CLI + Winbox) durch den Nutzer. Commit für M11
|
||||||
separat (`10f30a8`), M12 + HANDOFF/CHATLOG-Update in diesem Commit.
|
separat (`10f30a8`), M12 + HANDOFF/CHATLOG-Update in diesem Commit.
|
||||||
Offen: "Zurück auf dynamisch"-Aktion fehlt noch im Geräte-Tab; ob dieselbe
|
Offen (Stand direkt nach M12): "Zurück auf dynamisch"-Aktion fehlt noch
|
||||||
`.id`-Überlagerung auch im Experte-Tab bei anderen Menüs falsch zuordnen
|
im Geräte-Tab; ob dieselbe `.id`-Überlagerung auch im Experte-Tab bei
|
||||||
kann, ist ungeprüft; alle bereits vorher offenen Punkte unverändert
|
anderen Menüs falsch zuordnen kann, ist ungeprüft; alle bereits vorher
|
||||||
(siehe HANDOFF.md Nächste Schritte).
|
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
@@ -21,13 +21,16 @@ farblich nach Verbindungsart getrennt, Hover/Klick hebt Zusammenhänge
|
|||||||
hervor). **M12 "Geräte"** ist ein LAN-Scanner (DHCP-Leases + ARP +
|
hervor). **M12 "Geräte"** ist ein LAN-Scanner (DHCP-Leases + ARP +
|
||||||
Bridge-Host-Tabelle, gruppiert als eine Tabelle pro physischem Port) mit
|
Bridge-Host-Tabelle, gruppiert als eine Tabelle pro physischem Port) mit
|
||||||
Rechtsklick-Aktion "Feste IP zuweisen" (RouterOS' "Make Static", per
|
Rechtsklick-Aktion "Feste IP zuweisen" (RouterOS' "Make Static", per
|
||||||
`/ip dhcp-server lease make-static`). Beide Tabs live gegen den
|
`/ip dhcp-server lease make-static`) sowie ihr Gegenstück "Feste
|
||||||
hEX-Testrouter verifiziert — M12 dabei besonders gründlich, weil die
|
Zuweisung entfernen" (RouterOS kennt kein `make-dynamic`, nur `remove` —
|
||||||
Fest/Dynamisch-Erkennung und die Zuweisung selbst sich als überraschend
|
vorher per Recherche-Agent gegen die offizielle Doku geprüft statt
|
||||||
zäh erwiesen: **vier** echte Bugs gefunden und gefixt (Bug 14–17 unten),
|
geraten). Beide Tabs live gegen den hEX-Testrouter verifiziert — M12
|
||||||
am Ende vom Nutzer selbst bestätigt ("das funktioniert jetzt super").
|
dabei besonders gründlich, weil die Fest/Dynamisch-Erkennung und die
|
||||||
Release-Build unter `/Applications/RouterOS Assistant.app` ist auf
|
Zuweisung selbst sich als überraschend zäh erwiesen: **vier** echte Bugs
|
||||||
aktuellem Stand.
|
gefunden und gefixt (Bug 14–17 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
|
## Ziel
|
||||||
|
|
||||||
@@ -103,7 +106,7 @@ RouterOSAssistant/
|
|||||||
Wizard/Steps/Setup/ — Einrichten-Tab: Mode → Wan → Lan → (Vlan nur Experte) → Wifi → Firewall → Review/Apply
|
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)
|
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)
|
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)
|
Backup/ — Sicherungen-Tab (Ordner wählen, Gefahrenzone: Werkseinstellungen wiederherstellen)
|
||||||
Resources/Assets.xcassets/AppIcon.appiconset/ — App-Icon "Signal Router" (16px–1024px)
|
Resources/Assets.xcassets/AppIcon.appiconset/ — App-Icon "Signal Router" (16px–1024px)
|
||||||
RouterOSAssistantTests/ — reine Unit-Tests (Command-Builder, CLI-Parser, Fallback-Logik via Mock-Transport)
|
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
|
(M12/Bug 14) ist zusätzlich auf einer nirgends offiziell dokumentierten
|
||||||
Annahme aufgebaut (Query-String-Filter `?feld=wert` auf einem GET) —
|
Annahme aufgebaut (Query-String-Filter `?feld=wert` auf einem GET) —
|
||||||
komplett unverifiziert.
|
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
|
- **`fetchMenuItems`s `.id`-Positions-Überlagerung: Verlässlichkeit für
|
||||||
andere Menüs ungeprüft** (siehe Bug 15) — bei `/ip dhcp-server lease`
|
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
|
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`),
|
System/Werkzeuge/Mangle/Raw (siehe `OverviewGraph.unmappedAreas`),
|
||||||
weiterhin nur über den Experte-Tab erreichbar.
|
weiterhin nur über den Experte-Tab erreichbar.
|
||||||
- ✅ M12: Geräte-Tab (LAN-Scanner: DHCP-Leases+ARP+Bridge-Host, eine
|
- ✅ M12: Geräte-Tab (LAN-Scanner: DHCP-Leases+ARP+Bridge-Host, eine
|
||||||
Tabelle pro physischem Port, "Feste IP zuweisen" für dynamische Leases)
|
Tabelle pro physischem Port, "Feste IP zuweisen" für dynamische Leases,
|
||||||
— **live gegen Hardware verifiziert und vom Nutzer bestätigt
|
"Feste Zuweisung entfernen" für den Rückweg) — **live gegen Hardware
|
||||||
funktionierend** ("das funktioniert jetzt super"), nach vier gefundenen
|
verifiziert und vom Nutzer bestätigt funktionierend** ("das funktioniert
|
||||||
und gefixten Bugs (Bug 14–17, siehe oben) — die aufwendigste
|
jetzt super" / "das hat sofort funktioniert"), nach vier gefundenen und
|
||||||
|
gefixten Bugs beim Hinweg (Bug 14–17, siehe oben) — die aufwendigste
|
||||||
Live-Verifikation aller Milestones bisher, mehrere Diagnose-Runden mit
|
Live-Verifikation aller Milestones bisher, mehrere Diagnose-Runden mit
|
||||||
Rohdaten-Vergleichen und direkten Router-Gegenchecks (SSH-CLI + Winbox).
|
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
|
## 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
|
"Wiederherstellen"-Button pro Eintrag im Sicherungen-Tab
|
||||||
(`BackupListView`) mit Bestätigungsdialog (ähnlich der bestehenden
|
(`BackupListView`) mit Bestätigungsdialog (ähnlich der bestehenden
|
||||||
Werksreset-Gefahrenzone).
|
Werksreset-Gefahrenzone).
|
||||||
11. **Geräte-Tab: "Zurück auf dynamisch"-Aktion bauen** — Gegenstück zu
|
11. **`.id`-Positions-Überlagerung (`fetchMenuItems`) auf weitere Menüs
|
||||||
"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
|
|
||||||
prüfen** — live als falsch bestätigt für `/ip dhcp-server lease`
|
prüfen** — live als falsch bestätigt für `/ip dhcp-server lease`
|
||||||
(Bug 15). Betrifft potenziell jedes `.set`/`.remove` im Experte-Tab.
|
(Bug 15). Betrifft potenziell jedes `.set`/`.remove` im Experte-Tab.
|
||||||
Am ehesten zu prüfen: bei einem Menü mit mehreren gleichzeitig
|
Am ehesten zu prüfen: bei einem Menü mit mehreren gleichzeitig
|
||||||
vorhandenen Einträgen `:put [<Pfad> find]` und `<Pfad> print terse`
|
vorhandenen Einträgen `:put [<Pfad> find]` und `<Pfad> print terse`
|
||||||
unabhängig ausführen und die Reihenfolgen von Hand vergleichen.
|
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.
|
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
|
(ether3, `dhcp5`/`defconf` je nach Testrunde) sind **echte Geräte des
|
||||||
Nutzers**, mit denen M12 live durchgetestet wurde — keine
|
Nutzers**, mit denen M12 live durchgetestet wurde — keine
|
||||||
Test-Artefakte, nicht aufräumen. Der Laptop hatte am Ende der Session
|
Test-Artefakte, nicht aufräumen. Beide Geräte am Ende der Session
|
||||||
testweise eine feste IP zugewiesen bekommen; ob das der gewünschte
|
wieder dynamisch (kompletter Kreislauf fest→entfernt→dynamisch vom
|
||||||
Dauerzustand ist oder nur zum Testen war, mit dem Nutzer klären.
|
Nutzer selbst am Laptop bestätigt).
|
||||||
|
|
||||||
Kein Gitea-Remote vorhanden — falls der Nutzer später eine Gitea-Instanz
|
Kein Gitea-Remote vorhanden — falls der Nutzer später eine Gitea-Instanz
|
||||||
aufsetzt (z.B. selbst gehostet auf der vorhandenen OMV-NAS, siehe
|
aufsetzt (z.B. selbst gehostet auf der vorhandenen OMV-NAS, siehe
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
/// "Geräte" tab: LAN scanner — one table per physical Ethernet/WLAN port, each listing the
|
/// "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
|
/// devices resolved onto it (name/IP/MAC/status), plus right-click actions to give a device a
|
||||||
/// permanent static IP the same way Winbox's "Make Static" does.
|
/// permanent static IP (Winbox's "Make Static") or remove that reservation again.
|
||||||
struct DevicesView: View {
|
struct DevicesView: View {
|
||||||
@ObservedObject var connectionService: ConnectionService
|
@ObservedObject var connectionService: ConnectionService
|
||||||
@StateObject private var viewModel: DevicesViewModel
|
@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
|
/// dialogs elsewhere (BackupListView's factory-reset dialog): the dialog's own visibility and
|
||||||
/// its payload are two separate pieces of state.
|
/// its payload are two separate pieces of state.
|
||||||
@State private var showStaticConfirmation = false
|
@State private var showStaticConfirmation = false
|
||||||
|
/// Same reasoning as `showStaticConfirmation` — independent of `pendingStaticRemoval`.
|
||||||
|
@State private var showRemovalConfirmation = false
|
||||||
|
|
||||||
init(connectionService: ConnectionService) {
|
init(connectionService: ConnectionService) {
|
||||||
self.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(
|
.alert(
|
||||||
"Fehler",
|
"Fehler",
|
||||||
isPresented: Binding(
|
isPresented: Binding(
|
||||||
@@ -127,7 +151,12 @@ struct DevicesView: View {
|
|||||||
private func deviceMenu(for device: LanDevice) -> some View {
|
private func deviceMenu(for device: LanDevice) -> some View {
|
||||||
if device.hasLease {
|
if device.hasLease {
|
||||||
if device.isStatic {
|
if device.isStatic {
|
||||||
Text("Bereits fest zugewiesen")
|
Button {
|
||||||
|
viewModel.pendingStaticRemoval = device
|
||||||
|
showRemovalConfirmation = true
|
||||||
|
} label: {
|
||||||
|
Label("Feste Zuweisung entfernen", systemImage: "pin.slash")
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
Button {
|
Button {
|
||||||
viewModel.pendingStaticAssignment = device
|
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
|
/// 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
|
/// 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
|
/// per physical port, and drives the two write actions this tab offers: converting a dynamic
|
||||||
/// lease to a permanent static one.
|
/// 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
|
@MainActor
|
||||||
final class DevicesViewModel: ObservableObject {
|
final class DevicesViewModel: ObservableObject {
|
||||||
@Published private(set) var devices: [LanDevice] = []
|
@Published private(set) var devices: [LanDevice] = []
|
||||||
@@ -12,6 +13,12 @@ final class DevicesViewModel: ObservableObject {
|
|||||||
@Published private(set) var loadError: String?
|
@Published private(set) var loadError: String?
|
||||||
|
|
||||||
@Published var pendingStaticAssignment: LanDevice?
|
@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 isApplying = false
|
||||||
@Published private(set) var applyError: String?
|
@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
|
/// 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
|
/// tool uses (`ConnectionService.hasExpertToolBackedUpThisSession`), so a session that already
|
||||||
/// backed up via one "power tool" doesn't back up again via the other.
|
/// backed up via one "power tool" doesn't back up again via the other.
|
||||||
@@ -125,6 +143,38 @@ final class DevicesViewModel: ObservableObject {
|
|||||||
pendingStaticAssignment = nil
|
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() {
|
func dismissApplyError() {
|
||||||
applyError = nil
|
applyError = nil
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user