feat(fls): import installed programs and their settings

importProject carries a host's installed programs into the model with
the settings Swift keeps (web server port, Gnutella peer limit, DNS
recursion and forwarder, personal firewall) and two it does not (the echo
server's port, the generic client's target), plus Java's "switched on".
The Swift oracle writes programs and settings; they match on the 13 files
Swift reads completely.

Intended differences, named in the test:
- every FILIUS program is kept; Swift drops command prompt, text
  editor, file explorer, image viewer, echo server and generic client;
- a web server's files stay in /webserver, its document root; Swift
  moves them to /www. No program state goes into a host's files.
- multicast_server.fls: maxTeilnehmerZahl refers to a shared Integer
  (a button's icon text gap, 4); Swift falls back to 3.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-30 06:12:15 +00:00
co-authored by Claude Opus 5.5
parent 8080e7dcb2
commit c3fe56ab16
6 changed files with 800 additions and 92 deletions
@@ -384,6 +384,25 @@ func describeSystem(_ state: TopologyEditorState, _ node: TopologyNode) -> JSON
if let rows = state.runtimePortForwardingRowsByNodeID[node.id] {
json["portForwarding"] = rows.map { [$0.protocolValue, $0.publicPortValue, $0.lanIPAddress, $0.lanPortValue] }
}
if let programs = state.runtimeInstalledProgramsByNodeID[node.id] {
json["installedPrograms"] = programs.map(\.rawValue).sorted()
}
if let web = state.runtimeWebServerConfigurationsByNodeID[node.id] {
json["webServer"] = [
"port": web.port,
"documentRoot": web.documentRoot,
"virtualHosts": web.virtualHostConfiguration != nil,
] as JSON
}
if let gnutella = state.runtimeGnutellaConfigurationsByNodeID[node.id] {
json["gnutella"] = ["maximumKnownPeers": gnutella.maximumKnownPeers] as JSON
}
if let dns = state.runtimeDNSServerConfigurationsByNodeID[node.id] {
json["dnsServer"] = [
"recursiveResolutionEnabled": dns.recursiveResolutionEnabled,
"forwardingServerIPAddress": dns.forwardingServerIPAddress ?? NSNull(),
] as JSON
}
if let link = state.remoteLinkConfigurationsByNodeID[node.id] {
json["remoteLink"] = [
"pairIdentifier": link.pairIdentifier,