From b6c530dfb94a0055193ff13fe8cb92be5a87c0ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Tue, 5 Dec 2023 06:15:41 +0000 Subject: [PATCH 01/40] Add an AgamaNetworkAdapter * At this point, it just wraps a NetworkManagerAdapter. --- web/src/client/index.js | 4 +- web/src/client/network/agama_network.js | 156 ++++++++++++++++++++++ web/src/client/network/index.js | 12 +- web/src/client/network/network_manager.js | 2 - 4 files changed, 167 insertions(+), 7 deletions(-) create mode 100644 web/src/client/network/agama_network.js diff --git a/web/src/client/index.js b/web/src/client/index.js index 34942691c8..add4d7fb0e 100644 --- a/web/src/client/index.js +++ b/web/src/client/index.js @@ -29,7 +29,7 @@ import { StorageClient } from "./storage"; import { UsersClient } from "./users"; import phase from "./phase"; import { QuestionsClient } from "./questions"; -import { NetworkClient } from "./network"; +import { NetworkClient, AgamaNetworkAdapter } from "./network"; import cockpit from "../lib/cockpit"; const BUS_ADDRESS_FILE = "/run/agama/bus.address"; @@ -74,7 +74,7 @@ const createClient = (address = "unix:path=/run/agama/bus") => { const l10n = new L10nClient(address); const manager = new ManagerClient(address); const monitor = new Monitor(address, MANAGER_SERVICE); - const network = new NetworkClient(); + const network = new NetworkClient(new AgamaNetworkAdapter(address)); const software = new SoftwareClient(address); const storage = new StorageClient(address); const users = new UsersClient(address); diff --git a/web/src/client/network/agama_network.js b/web/src/client/network/agama_network.js new file mode 100644 index 0000000000..56fe2433e3 --- /dev/null +++ b/web/src/client/network/agama_network.js @@ -0,0 +1,156 @@ +/* + * Copyright (c) [2023] SUSE LLC + * + * All Rights Reserved. + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of version 2 of the GNU General Public License as published + * by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, contact SUSE LLC. + * + * To contact SUSE LLC about this file by physical or electronic mail, you may + * find current contact information at www.suse.com. + */ + +// @ts-check +// +import DBusClient from "../dbus"; +import { NetworkManagerAdapter } from "./network_manager"; + +const SERVICE_NAME = "org.opensuse.Agama1"; + +/** + * @typedef {import("./index").NetworkEventFn} NetworkEventFn + * @typedef {import("./model").Connection} Connection + */ + +/** + * NetworkClient adapter for Agama network service + */ +class AgamaNetworkAdapter { + /** + * @param {string} address - D-Bus address + */ + constructor(address) { + this.nm = new NetworkManagerAdapter(); + this.client = new DBusClient(SERVICE_NAME, address); + this.proxies = { + connections: {} + }; + this.eventsHandler = null; + } + + /** + * Set up the client + * + * @param {NetworkEventFn} handler - Events handler + */ + async setUp(handler) { + if (this.setUpDone) return; + + return this.nm.setUp(handler); + } + + /** + * Returns the active connections + * + * @return {ActiveConnection[]} + */ + activeConnections() { + return this.nm.activeConnections(); + } + + /** + * Returns the connection settings + * + * @return {Promise} + */ + connections() { + return this.nm.connections(); + } + + /** + * Returns the list of available wireless access points (AP) + * + * @return {AccessPoint[]} + */ + accessPoints() { + return this.nm.accessPoints(); + } + + /** + * Connects to given Wireless network + * + * @param {Connection} connection - connection to be activated + */ + async connectTo(connection) { + return this.nm.connectTo(connection); + } + + /** + * Add the connection for the given Wireless network and activate it + * + * @param {string} ssid - Network id + * @param {object} options - connection options + */ + async addAndConnectTo(ssid, options) { + return this.nm.addAndConnectTo(ssid, options); + } + + /** + * Adds a new connection + * + * @param {Connection} connection - Connection to add + */ + async addConnection(connection) { + return this.nm.addConnection(connection); + } + + /** + * Returns the connection with the given ID + * + * @param {string} id - Connection ID + * @return {Promise} + */ + async getConnection(id) { + return this.nm.getConnection(id); + } + + /** + * Updates the connection + * + * It uses the 'path' to match the connection in the backend. + * + * @param {Connection} connection - Connection to update + */ + async updateConnection(connection) { + return this.nm.updateConnection(connection); + } + + /** + * Deletes the connection + * + * It uses the 'path' to match the connection in the backend. + * + * @param {Connection} connection - Connection to delete + */ + async deleteConnection(connection) { + return this.nm.deleteConnection(connection); + } + + /* + * Returns network general settings + */ + settings() { + return this.nm.settings(); + } +} + +export { AgamaNetworkAdapter }; diff --git a/web/src/client/network/index.js b/web/src/client/network/index.js index 99641f2f9a..eb12df0ce6 100644 --- a/web/src/client/network/index.js +++ b/web/src/client/network/index.js @@ -23,6 +23,7 @@ import { NetworkManagerAdapter } from "./network_manager"; import { ConnectionTypes, ConnectionState } from "./model"; +import { AgamaNetworkAdapter } from "./agama_network"; /** * @typedef {import("./model").NetworkSettings} NetworkSettings @@ -80,8 +81,8 @@ class NetworkClient { * @param {NetworkAdapter} [adapter] - Network adapter. By default, it is set to o * NetworkManagerAdapter. */ - constructor(adapter) { - this.adapter = adapter || new NetworkManagerAdapter(); + constructor({ adapter, address }) { + this.adapter = adapter || new AgamaNetworkAdapter(address); /** @type {!boolean} */ this.subscribed = false; this.setUpDone = false; @@ -221,5 +222,10 @@ o * NetworkManagerAdapter. } export { - ConnectionState, ConnectionTypes, NetworkClient, NetworkManagerAdapter, NetworkEventTypes + AgamaNetworkAdapter, + ConnectionState, + ConnectionTypes, + NetworkClient, + NetworkManagerAdapter, + NetworkEventTypes }; diff --git a/web/src/client/network/network_manager.js b/web/src/client/network/network_manager.js index 562ccf9739..a4595f8f09 100644 --- a/web/src/client/network/network_manager.js +++ b/web/src/client/network/network_manager.js @@ -173,8 +173,6 @@ const mergeConnectionSettings = (settings, connection) => { class NetworkManagerAdapter { constructor() { this.client = new DBusClient(SERVICE_NAME); - /** @type {{[k: string]: string}} */ - this.connectionIds = {}; this.proxies = { accessPoints: {}, activeConnections: {}, From f4a2cacd7a9f1371c7beed79668806d3bf3eb6f6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Tue, 5 Dec 2023 08:55:00 +0000 Subject: [PATCH 02/40] Read connections from Agama's network service --- web/src/client/network/agama_network.js | 115 ++++++++++++++++++++++-- web/src/client/network/index.js | 6 +- web/src/client/network/model.js | 11 +++ 3 files changed, 120 insertions(+), 12 deletions(-) diff --git a/web/src/client/network/agama_network.js b/web/src/client/network/agama_network.js index 56fe2433e3..08598c95f9 100644 --- a/web/src/client/network/agama_network.js +++ b/web/src/client/network/agama_network.js @@ -23,8 +23,16 @@ // import DBusClient from "../dbus"; import { NetworkManagerAdapter } from "./network_manager"; +import cockpit from "../../lib/cockpit"; +import { createConnection } from "./model"; const SERVICE_NAME = "org.opensuse.Agama1"; +const CONNECTIONS_IFACE = "org.opensuse.Agama1.Network.Connections"; +const CONNECTIONS_PATH = "/org/opensuse/Agama1/Network/connections"; +const CONNECTION_IFACE = "org.opensuse.Agama1.Network.Connection"; +const CONNECTIONS_NAMESPACE = "/org/opensuse/Agama1/Network/connections"; +const IP_IFACE = "org.opensuse.Agama1.Network.Connection.IP"; +const WIRELESS_IFACE = "org.opensuse.Agama1.Network.Connection.Wireless"; /** * @typedef {import("./index").NetworkEventFn} NetworkEventFn @@ -42,9 +50,13 @@ class AgamaNetworkAdapter { this.nm = new NetworkManagerAdapter(); this.client = new DBusClient(SERVICE_NAME, address); this.proxies = { - connections: {} + connectionsRoot: null, + connections: {}, + ipConfigs: {}, + wireless: {} }; this.eventsHandler = null; + this.setUpDone = false; } /** @@ -55,6 +67,13 @@ class AgamaNetworkAdapter { async setUp(handler) { if (this.setUpDone) return; + this.proxies = { + connectionsRoot: await this.client.proxy(CONNECTIONS_IFACE, CONNECTIONS_PATH), + connections: await this.client.proxies(CONNECTION_IFACE, CONNECTIONS_NAMESPACE), + ipConfigs: await this.client.proxies(IP_IFACE, CONNECTIONS_NAMESPACE), + wireless: await this.client.proxies(WIRELESS_IFACE, CONNECTIONS_NAMESPACE) + }; + this.setUpDone = true; return this.nm.setUp(handler); } @@ -72,7 +91,7 @@ class AgamaNetworkAdapter { * * @return {Promise} */ - connections() { + async connections() { return this.nm.connections(); } @@ -116,11 +135,14 @@ class AgamaNetworkAdapter { /** * Returns the connection with the given ID * - * @param {string} id - Connection ID - * @return {Promise} + * @param {string} uuid - Connection ID + * @return {Promise} */ - async getConnection(id) { - return this.nm.getConnection(id); + async getConnection(uuid) { + const path = await this.getConnectionPath(uuid); + if (path) { + return this.connectionFromPath(path); + } } /** @@ -131,7 +153,29 @@ class AgamaNetworkAdapter { * @param {Connection} connection - Connection to update */ async updateConnection(connection) { - return this.nm.updateConnection(connection); + const path = await this.getConnectionPath(connection.uuid); + if (path === undefined) { + return; + } + + const { ipv4, wireless } = connection; + const ipProxy = this.proxies.ipConfigs[path]; + ipProxy.Method4 = ipv4.method; + ipProxy.Addresses = ipv4.addresses.map(addr => `${addr.address}/${addr.prefix}`); + ipProxy.Gateway4 = ipv4.gateway; + ipProxy.Nameservers = ipv4.nameServers; + + if (wireless) { + const wirelessProxy = this.proxies.wireless[path]; + wirelessProxy.ssid = cockpit.byte_array(wireless.ssid); + // TODO: handle hidden + wirelessProxy.hidden = false; + wirelessProxy.mode = "infrastructure"; + } + + // TODO: apply the changes only in this connection + this.proxies.connectionsRoot.Apply(); + return true; } /** @@ -146,11 +190,64 @@ class AgamaNetworkAdapter { } /* - * Returns network general settings - */ + * Returns network general settings + */ settings() { return this.nm.settings(); } + + /** + * Returns a connection from the given D-Bus path + * + * @param {string} path - Path of the D-Bus object representing the connection + * @return {Promise} + */ + async connectionFromPath(path) { + const connection = await this.proxies.connections[path]; + const ip = await this.proxies.ipConfigs[path]; + + const conn = { + id: connection.Id, + name: connection.Interface, + ipv4: { + method: ip.Method4, + nameServers: ip.Nameservers, + addresses: ip.Addresses.map(addr => { + const [address, prefix] = addr.split("/"); + return { address, prefix }; + }), + gateway: ip.Gateway4 + }, + }; + + const wireless = await this.proxies.wireless[path]; + if (wireless) { + conn.wireless = { + ssid: window.atob(wireless.ssid.v), + hidden: false, // TODO implement the 'hidden' property + mode: wireless.mode, + security: wireless.security // see AgamaSecurityProtocols + }; + } + + return conn; + } + + + /** + * Returns the D-Bus path of the connection. + * + * @param {string} uuid - Connection UUID + * @return {Promise} - Connection D-Bus path + */ + async getConnectionPath(uuid) { + for (const path in this.proxies.connections) { + const proxy = await this.proxies.connections[path]; + if (proxy.Uuid === uuid) { + return path; + } + } + } } export { AgamaNetworkAdapter }; diff --git a/web/src/client/network/index.js b/web/src/client/network/index.js index eb12df0ce6..4cd976af06 100644 --- a/web/src/client/network/index.js +++ b/web/src/client/network/index.js @@ -78,11 +78,11 @@ const NetworkEventTypes = Object.freeze({ */ class NetworkClient { /** - * @param {NetworkAdapter} [adapter] - Network adapter. By default, it is set to + * @param {NetworkAdapter} adapter - Network adapter. By default, it is set to o * NetworkManagerAdapter. */ - constructor({ adapter, address }) { - this.adapter = adapter || new AgamaNetworkAdapter(address); + constructor(adapter) { + this.adapter = adapter; /** @type {!boolean} */ this.subscribed = false; this.setUpDone = false; diff --git a/web/src/client/network/model.js b/web/src/client/network/model.js index f4dae72ecf..f3674761fb 100644 --- a/web/src/client/network/model.js +++ b/web/src/client/network/model.js @@ -60,6 +60,17 @@ const SecurityProtocols = Object.freeze({ _8021X: "802.1X" }); +// security protocols +const AgamaSecurityProtocols = Object.freeze({ + WEP: "none", + OWE: "owe", + DynamicWEP: "ieee8021x", + WPA2: "wpa-psk", + WPA3Personal: "sae", + WPA2Enterprise: "wpa-eap", + WPA3Only: "wpa-eap-suite-b-192" +}); + /** * @typedef {object} IPAddress * @property {string} address - like "129.168.1.2" From b7e636592e781ff03e0f0ced5a34e3e006abaa6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Thu, 28 Dec 2023 09:43:29 +0000 Subject: [PATCH 03/40] Fix the processing of addresses and nameservers in the web UI --- web/src/components/network/AddressesDataList.jsx | 4 ++-- web/src/components/network/DnsDataList.jsx | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/web/src/components/network/AddressesDataList.jsx b/web/src/components/network/AddressesDataList.jsx index d8d4b9d888..4c1af00b21 100644 --- a/web/src/components/network/AddressesDataList.jsx +++ b/web/src/components/network/AddressesDataList.jsx @@ -88,7 +88,7 @@ export default function AddressesDataList({ updateAddress(id, "address", value)} + onChange={(_, value) => updateAddress(id, "address", value)} // TRANSLATORS: input field name placeholder={_("IP Address")} aria-label={_("IP Address")} @@ -97,7 +97,7 @@ export default function AddressesDataList({ updateAddress(id, "prefix", value)} + onChange={(_, value) => updateAddress(id, "prefix", value)} // TRANSLATORS: input field name placeholder={_("Prefix length or netmask")} aria-label={_("Prefix length or netmask")} diff --git a/web/src/components/network/DnsDataList.jsx b/web/src/components/network/DnsDataList.jsx index ed5011518b..bffbd88eb2 100644 --- a/web/src/components/network/DnsDataList.jsx +++ b/web/src/components/network/DnsDataList.jsx @@ -73,7 +73,7 @@ export default function DnsDataList({ servers: originalServers, updateDnsServers updateServer(id, "address", value)} + onChange={(_, value) => updateServer(id, "address", value)} // TRANSLATORS: input field name placeholder={_("Server IP")} aria-label={_("Server IP")} From ff6186575aec182aeedc967375cd4b62b55ba0bc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Thu, 28 Dec 2023 10:16:20 +0000 Subject: [PATCH 04/40] Write connections using Agama's network service --- web/src/client/network/agama_network.js | 3 ++- web/src/client/network/model.js | 5 ++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/web/src/client/network/agama_network.js b/web/src/client/network/agama_network.js index 08598c95f9..738d0c3572 100644 --- a/web/src/client/network/agama_network.js +++ b/web/src/client/network/agama_network.js @@ -208,6 +208,7 @@ class AgamaNetworkAdapter { const conn = { id: connection.Id, + uuid: connection.Uuid, name: connection.Interface, ipv4: { method: ip.Method4, @@ -230,7 +231,7 @@ class AgamaNetworkAdapter { }; } - return conn; + return createConnection(conn); } diff --git a/web/src/client/network/model.js b/web/src/client/network/model.js index f3674761fb..563ff8cd75 100644 --- a/web/src/client/network/model.js +++ b/web/src/client/network/model.js @@ -90,6 +90,7 @@ const AgamaSecurityProtocols = Object.freeze({ * @typedef {object} Connection * @property {string} id * @property {string} name + * @property {string} uuid * @property {IPv4} [ipv4] * @property {Wireless} [wireless] */ @@ -152,15 +153,17 @@ const createIPv4 = ({ method, addresses, nameServers, gateway }) => { * * @param {object} options * @param {string} [options.id] - Connection ID + * @param {string} [options.uuid] - Connection UUID * @param {string} [options.name] - Connection name * @param {object} [options.ipv4] IPv4 Settings * @param {object} [options.wireless] Wireless Settings * @return {Connection} */ -const createConnection = ({ id, name, ipv4, wireless }) => { +const createConnection = ({ id, uuid, name, ipv4, wireless }) => { const connection = { id, name, + uuid, ipv4: createIPv4(ipv4 || {}), }; From 7a8cac1993b4ad739025267165b785d9501c9963 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Tue, 9 Jan 2024 12:58:06 +0000 Subject: [PATCH 05/40] Use the Set method to update network properties * We need to make sure that all changes happens before calling Apply. * We cannot wait for the usual proxy assignment (e.g., proxy.Property = 'Value'). --- web/src/client/network/agama_network.js | 34 +++++++++++++++++-------- 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/web/src/client/network/agama_network.js b/web/src/client/network/agama_network.js index 738d0c3572..bdc43cd53f 100644 --- a/web/src/client/network/agama_network.js +++ b/web/src/client/network/agama_network.js @@ -151,26 +151,25 @@ class AgamaNetworkAdapter { * It uses the 'path' to match the connection in the backend. * * @param {Connection} connection - Connection to update + * @return {Promise} - the promise resolves to true if the connection + * was successfully updated and to false it it does not exist. */ async updateConnection(connection) { const path = await this.getConnectionPath(connection.uuid); if (path === undefined) { - return; + return false; } const { ipv4, wireless } = connection; - const ipProxy = this.proxies.ipConfigs[path]; - ipProxy.Method4 = ipv4.method; - ipProxy.Addresses = ipv4.addresses.map(addr => `${addr.address}/${addr.prefix}`); - ipProxy.Gateway4 = ipv4.gateway; - ipProxy.Nameservers = ipv4.nameServers; + await this.setProperty(path, IP_IFACE, "Method4", cockpit.variant("s", ipv4.method)); + await this.setProperty(path, IP_IFACE, "Gateway4", cockpit.variant("s", ipv4.gateway)); + const addresses = ipv4.addresses.map(a => `${a.address}/${a.prefix}`); + await this.setProperty(path, IP_IFACE, "Addresses", cockpit.variant("as", addresses)); + await this.setProperty(path, IP_IFACE, "Nameservers", cockpit.variant("as", ipv4.nameServers)); if (wireless) { - const wirelessProxy = this.proxies.wireless[path]; - wirelessProxy.ssid = cockpit.byte_array(wireless.ssid); - // TODO: handle hidden - wirelessProxy.hidden = false; - wirelessProxy.mode = "infrastructure"; + await this.setProperty(path, WIRELESS_IFACE, "SSID", cockpit.byte_array(wireless.ssid)); + await this.setProperty(path, WIRELESS_IFACE, "Mode", cockpit.variant("s", "infrastructure")); } // TODO: apply the changes only in this connection @@ -249,6 +248,19 @@ class AgamaNetworkAdapter { } } } + + /** + * Sets a property for a given path + * + * @param {string} path - Object path. + * @param {string} iface - Interface name. + * @param {string} property - Property name. + * @param {object} value - Property value. The value should be created by + * using the cockpit.variant() function. + */ + async setProperty(path, iface, property, value) { + return this.client.call(path, "org.freedesktop.DBus.Properties", "Set", [iface, property, value]);;;; + } } export { AgamaNetworkAdapter }; From 037f1755bc65fe1aed87bffc0b4e14874bdd42b3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Wed, 10 Jan 2024 11:14:30 +0000 Subject: [PATCH 06/40] Use Agama network to connect to a wireless network --- web/src/client/network/agama_network.js | 78 +++++++++++++++++++------ 1 file changed, 59 insertions(+), 19 deletions(-) diff --git a/web/src/client/network/agama_network.js b/web/src/client/network/agama_network.js index bdc43cd53f..415fceefbd 100644 --- a/web/src/client/network/agama_network.js +++ b/web/src/client/network/agama_network.js @@ -20,7 +20,7 @@ */ // @ts-check -// + import DBusClient from "../dbus"; import { NetworkManagerAdapter } from "./network_manager"; import cockpit from "../../lib/cockpit"; @@ -34,6 +34,14 @@ const CONNECTIONS_NAMESPACE = "/org/opensuse/Agama1/Network/connections"; const IP_IFACE = "org.opensuse.Agama1.Network.Connection.IP"; const WIRELESS_IFACE = "org.opensuse.Agama1.Network.Connection.Wireless"; +const DeviceType = Object.freeze({ + LOOPBACK: 0, + ETHERNET: 1, + WIRELESS: 2, + DUMMY: 3, + BOND: 4 +}); + /** * @typedef {import("./index").NetworkEventFn} NetworkEventFn * @typedef {import("./model").Connection} Connection @@ -120,16 +128,34 @@ class AgamaNetworkAdapter { * @param {object} options - connection options */ async addAndConnectTo(ssid, options) { - return this.nm.addAndConnectTo(ssid, options); + // duplicated code (see network manager adapter) + const wireless = { ssid }; + if (options.security) wireless.security = options.security; + if (options.password) wireless.password = options.password; + if (options.hidden) wireless.hidden = options.hidden; + + const connection = createConnection({ + name: ssid, + wireless + }); + + const added = await this.addConnection(connection); + return this.connectTo(added); } /** * Adds a new connection * * @param {Connection} connection - Connection to add + * @return {Promise} the added connection */ async addConnection(connection) { - return this.nm.addConnection(connection); + const { name } = connection; + const proxy = await this.client.proxy(CONNECTIONS_IFACE, CONNECTIONS_PATH); + const ctype = (connection.wireless) ? DeviceType.WIRELESS : DeviceType.ETHERNET; + const path = await proxy.AddConnection(name, ctype); + await this.updateConnectionAt(path, { ...connection, id: name }); + return this.connectionFromPath(path); } /** @@ -148,7 +174,7 @@ class AgamaNetworkAdapter { /** * Updates the connection * - * It uses the 'path' to match the connection in the backend. + * It uses the 'uuid' to match the connection in the backend. * * @param {Connection} connection - Connection to update * @return {Promise} - the promise resolves to true if the connection @@ -160,20 +186,7 @@ class AgamaNetworkAdapter { return false; } - const { ipv4, wireless } = connection; - await this.setProperty(path, IP_IFACE, "Method4", cockpit.variant("s", ipv4.method)); - await this.setProperty(path, IP_IFACE, "Gateway4", cockpit.variant("s", ipv4.gateway)); - const addresses = ipv4.addresses.map(a => `${a.address}/${a.prefix}`); - await this.setProperty(path, IP_IFACE, "Addresses", cockpit.variant("as", addresses)); - await this.setProperty(path, IP_IFACE, "Nameservers", cockpit.variant("as", ipv4.nameServers)); - - if (wireless) { - await this.setProperty(path, WIRELESS_IFACE, "SSID", cockpit.byte_array(wireless.ssid)); - await this.setProperty(path, WIRELESS_IFACE, "Mode", cockpit.variant("s", "infrastructure")); - } - - // TODO: apply the changes only in this connection - this.proxies.connectionsRoot.Apply(); + await this.updateConnectionAt(path, connection); return true; } @@ -259,7 +272,34 @@ class AgamaNetworkAdapter { * using the cockpit.variant() function. */ async setProperty(path, iface, property, value) { - return this.client.call(path, "org.freedesktop.DBus.Properties", "Set", [iface, property, value]);;;; + return this.client.call(path, "org.freedesktop.DBus.Properties", "Set", [iface, property, value]); + } + + /** + * Updates the connection in the given path + * + * + * @param {string} path - D-Bus path of the connection to update. + * @param {Connection} connection - Connection to update. + */ + async updateConnectionAt(path, connection) { + const { ipv4, wireless } = connection; + await this.setProperty(path, IP_IFACE, "Method4", cockpit.variant("s", ipv4.method)); + await this.setProperty(path, IP_IFACE, "Gateway4", cockpit.variant("s", ipv4.gateway)); + const addresses = ipv4.addresses.map(a => `${a.address}/${a.prefix}`); + await this.setProperty(path, IP_IFACE, "Addresses", cockpit.variant("as", addresses)); + await this.setProperty(path, IP_IFACE, "Nameservers", cockpit.variant("as", ipv4.nameServers)); + + if (wireless) { + await this.setProperty(path, WIRELESS_IFACE, "Mode", cockpit.variant("s", "infrastructure")); + await this.setProperty(path, WIRELESS_IFACE, "Password", cockpit.variant("s", wireless.password)); + await this.setProperty(path, WIRELESS_IFACE, "Security", cockpit.variant("s", wireless.security)) + const ssid = cockpit.byte_array(wireless.ssid); + await this.setProperty(path, WIRELESS_IFACE, "SSID", cockpit.variant("ay", ssid)); + } + + // TODO: apply the changes only in this connection + return this.proxies.connectionsRoot.Apply(); } } From 69c2e26aef4bc10f74271933a1eeb042dadcbd70 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Wed, 10 Jan 2024 12:36:40 +0000 Subject: [PATCH 07/40] Fix editing of wireless networks --- web/src/client/network/agama_network.js | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/web/src/client/network/agama_network.js b/web/src/client/network/agama_network.js index 415fceefbd..83fcfa4f93 100644 --- a/web/src/client/network/agama_network.js +++ b/web/src/client/network/agama_network.js @@ -236,10 +236,10 @@ class AgamaNetworkAdapter { const wireless = await this.proxies.wireless[path]; if (wireless) { conn.wireless = { - ssid: window.atob(wireless.ssid.v), + ssid: window.atob(wireless.SSID), hidden: false, // TODO implement the 'hidden' property - mode: wireless.mode, - security: wireless.security // see AgamaSecurityProtocols + mode: wireless.Mode, + security: wireless.Security // see AgamaSecurityProtocols }; } @@ -292,7 +292,9 @@ class AgamaNetworkAdapter { if (wireless) { await this.setProperty(path, WIRELESS_IFACE, "Mode", cockpit.variant("s", "infrastructure")); - await this.setProperty(path, WIRELESS_IFACE, "Password", cockpit.variant("s", wireless.password)); + if (wireless.password) { + await this.setProperty(path, WIRELESS_IFACE, "Password", cockpit.variant("s", wireless.password)); + } await this.setProperty(path, WIRELESS_IFACE, "Security", cockpit.variant("s", wireless.security)) const ssid = cockpit.byte_array(wireless.ssid); await this.setProperty(path, WIRELESS_IFACE, "SSID", cockpit.variant("ay", ssid)); From 512366cd4e26060e1e15f35ca8fa3b1db5545739 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Fri, 12 Jan 2024 09:49:50 +0000 Subject: [PATCH 08/40] [rust] Only write the network connections that changed --- rust/agama-dbus-server/src/network/model.rs | 10 ++-------- rust/agama-dbus-server/src/network/nm/adapter.rs | 9 +++++++++ 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/rust/agama-dbus-server/src/network/model.rs b/rust/agama-dbus-server/src/network/model.rs index 0205b2fe2b..3338d06c84 100644 --- a/rust/agama-dbus-server/src/network/model.rs +++ b/rust/agama-dbus-server/src/network/model.rs @@ -318,7 +318,7 @@ pub struct Device { } /// Represents an availble network connection. -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq)] pub struct Connection { pub id: String, pub uuid: Uuid, @@ -331,12 +331,6 @@ pub struct Connection { pub config: ConnectionConfig, } -impl PartialEq for Connection { - fn eq(&self, other: &Self) -> bool { - self.id == other.id && self.uuid == other.uuid && self.ip_config == other.ip_config - } -} - impl Connection { pub fn new(id: String, device_type: DeviceType) -> Self { let config = match device_type { @@ -428,7 +422,7 @@ impl From for ConnectionConfig { #[error("Invalid MAC address: {0}")] pub struct InvalidMacAddress(String); -#[derive(Debug, Default, Clone)] +#[derive(Debug, Default, Clone, PartialEq)] pub enum MacAddress { MacAddress(macaddr::MacAddr6), Preserve, diff --git a/rust/agama-dbus-server/src/network/nm/adapter.rs b/rust/agama-dbus-server/src/network/nm/adapter.rs index eb42b36afb..65bbcfa4ed 100644 --- a/rust/agama-dbus-server/src/network/nm/adapter.rs +++ b/rust/agama-dbus-server/src/network/nm/adapter.rs @@ -43,6 +43,8 @@ impl<'a> Adapter for NetworkManagerAdapter<'a> { /// /// * `network`: network model. async fn write(&self, network: &NetworkState) -> Result<(), Box> { + let old_state = self.read().await?; + // By now, traits do not support async functions. Using `task::block_on` allows // to use 'await'. for conn in ordered_connections(network) { @@ -50,6 +52,13 @@ impl<'a> Adapter for NetworkManagerAdapter<'a> { continue; } + if let Some(old_conn) = old_state.get_connection_by_uuid(conn.uuid) { + if old_conn == conn { + continue; + } + } + + log::info!("Updating connection {} ({})", conn.id, conn.uuid); let result = if conn.is_removed() { self.client.remove_connection(conn.uuid).await } else { From 965c9775f203c8ef66870431f9ac2b612ee6a343 Mon Sep 17 00:00:00 2001 From: Knut Anderssen Date: Fri, 12 Jan 2024 15:43:45 +0000 Subject: [PATCH 09/40] Use the network client without an specific Adapter --- web/src/client/index.js | 4 +- web/src/client/network/agama_network.js | 308 ---------------------- web/src/client/network/index.js | 204 ++++++++++++-- web/src/client/network/model.js | 18 +- web/src/client/network/network_manager.js | 48 +--- 5 files changed, 191 insertions(+), 391 deletions(-) delete mode 100644 web/src/client/network/agama_network.js diff --git a/web/src/client/index.js b/web/src/client/index.js index add4d7fb0e..0f64ac1bcb 100644 --- a/web/src/client/index.js +++ b/web/src/client/index.js @@ -29,7 +29,7 @@ import { StorageClient } from "./storage"; import { UsersClient } from "./users"; import phase from "./phase"; import { QuestionsClient } from "./questions"; -import { NetworkClient, AgamaNetworkAdapter } from "./network"; +import { NetworkClient } from "./network"; import cockpit from "../lib/cockpit"; const BUS_ADDRESS_FILE = "/run/agama/bus.address"; @@ -74,7 +74,7 @@ const createClient = (address = "unix:path=/run/agama/bus") => { const l10n = new L10nClient(address); const manager = new ManagerClient(address); const monitor = new Monitor(address, MANAGER_SERVICE); - const network = new NetworkClient(new AgamaNetworkAdapter(address)); + const network = new NetworkClient(address); const software = new SoftwareClient(address); const storage = new StorageClient(address); const users = new UsersClient(address); diff --git a/web/src/client/network/agama_network.js b/web/src/client/network/agama_network.js deleted file mode 100644 index 83fcfa4f93..0000000000 --- a/web/src/client/network/agama_network.js +++ /dev/null @@ -1,308 +0,0 @@ -/* - * Copyright (c) [2023] SUSE LLC - * - * All Rights Reserved. - * - * This program is free software; you can redistribute it and/or modify it - * under the terms of version 2 of the GNU General Public License as published - * by the Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or - * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for - * more details. - * - * You should have received a copy of the GNU General Public License along - * with this program; if not, contact SUSE LLC. - * - * To contact SUSE LLC about this file by physical or electronic mail, you may - * find current contact information at www.suse.com. - */ - -// @ts-check - -import DBusClient from "../dbus"; -import { NetworkManagerAdapter } from "./network_manager"; -import cockpit from "../../lib/cockpit"; -import { createConnection } from "./model"; - -const SERVICE_NAME = "org.opensuse.Agama1"; -const CONNECTIONS_IFACE = "org.opensuse.Agama1.Network.Connections"; -const CONNECTIONS_PATH = "/org/opensuse/Agama1/Network/connections"; -const CONNECTION_IFACE = "org.opensuse.Agama1.Network.Connection"; -const CONNECTIONS_NAMESPACE = "/org/opensuse/Agama1/Network/connections"; -const IP_IFACE = "org.opensuse.Agama1.Network.Connection.IP"; -const WIRELESS_IFACE = "org.opensuse.Agama1.Network.Connection.Wireless"; - -const DeviceType = Object.freeze({ - LOOPBACK: 0, - ETHERNET: 1, - WIRELESS: 2, - DUMMY: 3, - BOND: 4 -}); - -/** - * @typedef {import("./index").NetworkEventFn} NetworkEventFn - * @typedef {import("./model").Connection} Connection - */ - -/** - * NetworkClient adapter for Agama network service - */ -class AgamaNetworkAdapter { - /** - * @param {string} address - D-Bus address - */ - constructor(address) { - this.nm = new NetworkManagerAdapter(); - this.client = new DBusClient(SERVICE_NAME, address); - this.proxies = { - connectionsRoot: null, - connections: {}, - ipConfigs: {}, - wireless: {} - }; - this.eventsHandler = null; - this.setUpDone = false; - } - - /** - * Set up the client - * - * @param {NetworkEventFn} handler - Events handler - */ - async setUp(handler) { - if (this.setUpDone) return; - - this.proxies = { - connectionsRoot: await this.client.proxy(CONNECTIONS_IFACE, CONNECTIONS_PATH), - connections: await this.client.proxies(CONNECTION_IFACE, CONNECTIONS_NAMESPACE), - ipConfigs: await this.client.proxies(IP_IFACE, CONNECTIONS_NAMESPACE), - wireless: await this.client.proxies(WIRELESS_IFACE, CONNECTIONS_NAMESPACE) - }; - this.setUpDone = true; - return this.nm.setUp(handler); - } - - /** - * Returns the active connections - * - * @return {ActiveConnection[]} - */ - activeConnections() { - return this.nm.activeConnections(); - } - - /** - * Returns the connection settings - * - * @return {Promise} - */ - async connections() { - return this.nm.connections(); - } - - /** - * Returns the list of available wireless access points (AP) - * - * @return {AccessPoint[]} - */ - accessPoints() { - return this.nm.accessPoints(); - } - - /** - * Connects to given Wireless network - * - * @param {Connection} connection - connection to be activated - */ - async connectTo(connection) { - return this.nm.connectTo(connection); - } - - /** - * Add the connection for the given Wireless network and activate it - * - * @param {string} ssid - Network id - * @param {object} options - connection options - */ - async addAndConnectTo(ssid, options) { - // duplicated code (see network manager adapter) - const wireless = { ssid }; - if (options.security) wireless.security = options.security; - if (options.password) wireless.password = options.password; - if (options.hidden) wireless.hidden = options.hidden; - - const connection = createConnection({ - name: ssid, - wireless - }); - - const added = await this.addConnection(connection); - return this.connectTo(added); - } - - /** - * Adds a new connection - * - * @param {Connection} connection - Connection to add - * @return {Promise} the added connection - */ - async addConnection(connection) { - const { name } = connection; - const proxy = await this.client.proxy(CONNECTIONS_IFACE, CONNECTIONS_PATH); - const ctype = (connection.wireless) ? DeviceType.WIRELESS : DeviceType.ETHERNET; - const path = await proxy.AddConnection(name, ctype); - await this.updateConnectionAt(path, { ...connection, id: name }); - return this.connectionFromPath(path); - } - - /** - * Returns the connection with the given ID - * - * @param {string} uuid - Connection ID - * @return {Promise} - */ - async getConnection(uuid) { - const path = await this.getConnectionPath(uuid); - if (path) { - return this.connectionFromPath(path); - } - } - - /** - * Updates the connection - * - * It uses the 'uuid' to match the connection in the backend. - * - * @param {Connection} connection - Connection to update - * @return {Promise} - the promise resolves to true if the connection - * was successfully updated and to false it it does not exist. - */ - async updateConnection(connection) { - const path = await this.getConnectionPath(connection.uuid); - if (path === undefined) { - return false; - } - - await this.updateConnectionAt(path, connection); - return true; - } - - /** - * Deletes the connection - * - * It uses the 'path' to match the connection in the backend. - * - * @param {Connection} connection - Connection to delete - */ - async deleteConnection(connection) { - return this.nm.deleteConnection(connection); - } - - /* - * Returns network general settings - */ - settings() { - return this.nm.settings(); - } - - /** - * Returns a connection from the given D-Bus path - * - * @param {string} path - Path of the D-Bus object representing the connection - * @return {Promise} - */ - async connectionFromPath(path) { - const connection = await this.proxies.connections[path]; - const ip = await this.proxies.ipConfigs[path]; - - const conn = { - id: connection.Id, - uuid: connection.Uuid, - name: connection.Interface, - ipv4: { - method: ip.Method4, - nameServers: ip.Nameservers, - addresses: ip.Addresses.map(addr => { - const [address, prefix] = addr.split("/"); - return { address, prefix }; - }), - gateway: ip.Gateway4 - }, - }; - - const wireless = await this.proxies.wireless[path]; - if (wireless) { - conn.wireless = { - ssid: window.atob(wireless.SSID), - hidden: false, // TODO implement the 'hidden' property - mode: wireless.Mode, - security: wireless.Security // see AgamaSecurityProtocols - }; - } - - return createConnection(conn); - } - - - /** - * Returns the D-Bus path of the connection. - * - * @param {string} uuid - Connection UUID - * @return {Promise} - Connection D-Bus path - */ - async getConnectionPath(uuid) { - for (const path in this.proxies.connections) { - const proxy = await this.proxies.connections[path]; - if (proxy.Uuid === uuid) { - return path; - } - } - } - - /** - * Sets a property for a given path - * - * @param {string} path - Object path. - * @param {string} iface - Interface name. - * @param {string} property - Property name. - * @param {object} value - Property value. The value should be created by - * using the cockpit.variant() function. - */ - async setProperty(path, iface, property, value) { - return this.client.call(path, "org.freedesktop.DBus.Properties", "Set", [iface, property, value]); - } - - /** - * Updates the connection in the given path - * - * - * @param {string} path - D-Bus path of the connection to update. - * @param {Connection} connection - Connection to update. - */ - async updateConnectionAt(path, connection) { - const { ipv4, wireless } = connection; - await this.setProperty(path, IP_IFACE, "Method4", cockpit.variant("s", ipv4.method)); - await this.setProperty(path, IP_IFACE, "Gateway4", cockpit.variant("s", ipv4.gateway)); - const addresses = ipv4.addresses.map(a => `${a.address}/${a.prefix}`); - await this.setProperty(path, IP_IFACE, "Addresses", cockpit.variant("as", addresses)); - await this.setProperty(path, IP_IFACE, "Nameservers", cockpit.variant("as", ipv4.nameServers)); - - if (wireless) { - await this.setProperty(path, WIRELESS_IFACE, "Mode", cockpit.variant("s", "infrastructure")); - if (wireless.password) { - await this.setProperty(path, WIRELESS_IFACE, "Password", cockpit.variant("s", wireless.password)); - } - await this.setProperty(path, WIRELESS_IFACE, "Security", cockpit.variant("s", wireless.security)) - const ssid = cockpit.byte_array(wireless.ssid); - await this.setProperty(path, WIRELESS_IFACE, "SSID", cockpit.variant("ay", ssid)); - } - - // TODO: apply the changes only in this connection - return this.proxies.connectionsRoot.Apply(); - } -} - -export { AgamaNetworkAdapter }; diff --git a/web/src/client/network/index.js b/web/src/client/network/index.js index 4cd976af06..7c871d1394 100644 --- a/web/src/client/network/index.js +++ b/web/src/client/network/index.js @@ -1,5 +1,5 @@ /* - * Copyright (c) [2022] SUSE LLC + * Copyright (c) [2023] SUSE LLC * * All Rights Reserved. * @@ -21,9 +21,26 @@ // @ts-check +import DBusClient from "../dbus"; import { NetworkManagerAdapter } from "./network_manager"; -import { ConnectionTypes, ConnectionState } from "./model"; -import { AgamaNetworkAdapter } from "./agama_network"; +import cockpit from "../../lib/cockpit"; +import { createConnection, ConnectionTypes, ConnectionState } from "./model"; + +const SERVICE_NAME = "org.opensuse.Agama1"; +const CONNECTIONS_IFACE = "org.opensuse.Agama1.Network.Connections"; +const CONNECTIONS_PATH = "/org/opensuse/Agama1/Network/connections"; +const CONNECTION_IFACE = "org.opensuse.Agama1.Network.Connection"; +const CONNECTIONS_NAMESPACE = "/org/opensuse/Agama1/Network/connections"; +const IP_IFACE = "org.opensuse.Agama1.Network.Connection.IP"; +const WIRELESS_IFACE = "org.opensuse.Agama1.Network.Connection.Wireless"; + +const DeviceType = Object.freeze({ + LOOPBACK: 0, + ETHERNET: 1, + WIRELESS: 2, + DUMMY: 3, + BOND: 4 +}); /** * @typedef {import("./model").NetworkSettings} NetworkSettings @@ -78,15 +95,20 @@ const NetworkEventTypes = Object.freeze({ */ class NetworkClient { /** - * @param {NetworkAdapter} adapter - Network adapter. By default, it is set to -o * NetworkManagerAdapter. + * @param {string} address - D-Bus address */ - constructor(adapter) { - this.adapter = adapter; - /** @type {!boolean} */ + constructor(address) { this.subscribed = false; + this.nm = new NetworkManagerAdapter(); + this.client = new DBusClient(SERVICE_NAME, address); + this.proxies = { + connectionsRoot: null, + connections: {}, + ipConfigs: {}, + wireless: {} + }; + this.eventsHandler = null; this.setUpDone = false; - /** @type {NetworkEventFn[]} */ this.handlers = []; } @@ -111,7 +133,15 @@ o * NetworkManagerAdapter. async setUp() { if (this.setUpDone) return; - return this.adapter.setUp(e => this.handlers.forEach(f => f(e))); + this.proxies = { + connectionsRoot: await this.client.proxy(CONNECTIONS_IFACE, CONNECTIONS_PATH), + connections: await this.client.proxies(CONNECTION_IFACE, CONNECTIONS_NAMESPACE), + ipConfigs: await this.client.proxies(IP_IFACE, CONNECTIONS_NAMESPACE), + wireless: await this.client.proxies(WIRELESS_IFACE, CONNECTIONS_NAMESPACE) + }; + + this.setUpDone = true; + return this.nm.setUp(e => this.handlers.forEach(f => f(e))); } /** @@ -120,7 +150,7 @@ o * NetworkManagerAdapter. * @return {ActiveConnection[]} */ activeConnections() { - return this.adapter.activeConnections(); + return this.nm.activeConnections(); } /** @@ -129,7 +159,7 @@ o * NetworkManagerAdapter. * @return {Promise} */ connections() { - return this.adapter.connections(); + return this.nm.connections(); } /** @@ -138,7 +168,7 @@ o * NetworkManagerAdapter. * @return {AccessPoint[]} */ accessPoints() { - return this.adapter.accessPoints(); + return this.nm.accessPoints(); } /** @@ -147,7 +177,7 @@ o * NetworkManagerAdapter. * @param {Connection} connection - connection to be activated */ async connectTo(connection) { - return this.adapter.connectTo(connection); + return this.nm.connectTo(connection); } /** @@ -157,37 +187,161 @@ o * NetworkManagerAdapter. * @param {object} options - connection options */ async addAndConnectTo(ssid, options) { - return this.adapter.addAndConnectTo(ssid, options); + // duplicated code (see network manager adapter) + const wireless = { ssid }; + if (options.security) wireless.security = options.security; + if (options.password) wireless.password = options.password; + if (options.hidden) wireless.hidden = options.hidden; + + const connection = createConnection({ + name: ssid, + wireless + }); + + const added = await this.addConnection(connection); + return this.connectTo(added); } /** * Adds a new connection * * @param {Connection} connection - Connection to add + * @return {Promise} the added connection */ async addConnection(connection) { - return this.adapter.addConnection(connection); + const { name } = connection; + const proxy = await this.client.proxy(CONNECTIONS_IFACE, CONNECTIONS_PATH); + const ctype = (connection.wireless) ? DeviceType.WIRELESS : DeviceType.ETHERNET; + const path = await proxy.AddConnection(name, ctype); + await this.updateConnectionAt(path, { ...connection, id: name }); + return this.connectionFromPath(path); } /** * Returns the connection with the given ID * - * @param {string} id - Connection ID + * @param {string} uuid - Connection ID + * @return {Promise} + */ + async getConnection(uuid) { + const path = await this.getConnectionPath(uuid); + if (path) { + return this.connectionFromPath(path); + } + } + + /** + * Returns a connection from the given D-Bus path + * + * @param {string} path - Path of the D-Bus object representing the connection * @return {Promise} */ - async getConnection(id) { - return this.adapter.getConnection(id); + async connectionFromPath(path) { + const connection = await this.proxies.connections[path]; + const ip = await this.proxies.ipConfigs[path]; + + const conn = { + id: connection.Id, + uuid: connection.Uuid, + name: connection.Interface, + ipv4: { + method: ip.Method4, + nameServers: ip.Nameservers, + addresses: ip.Addresses.map(addr => { + const [address, prefix] = addr.split("/"); + return { address, prefix }; + }), + gateway: ip.Gateway4 + }, + }; + + const wireless = await this.proxies.wireless[path]; + if (wireless) { + conn.wireless = { + ssid: window.atob(wireless.SSID), + hidden: false, // TODO implement the 'hidden' property + mode: wireless.Mode, + security: wireless.Security // see AgamaSecurityProtocols + }; + } + + return createConnection(conn); + } + + /** + * Sets a property for a given path + * + * @param {string} path - Object path. + * @param {string} iface - Interface name. + * @param {string} property - Property name. + * @param {object} value - Property value. The value should be created by + * using the cockpit.variant() function. + */ + async setProperty(path, iface, property, value) { + return this.client.call(path, "org.freedesktop.DBus.Properties", "Set", [iface, property, value]); + } + + /** + * Returns the D-Bus path of the connection. + * + * @param {string} uuid - Connection UUID + * @return {Promise} - Connection D-Bus path + */ + async getConnectionPath(uuid) { + for (const path in this.proxies.connections) { + const proxy = await this.proxies.connections[path]; + if (proxy.Uuid === uuid) { + return path; + } + } } /** * Updates the connection * - * It uses the 'path' to match the connection in the backend. + * It uses the 'uuid' to match the connection in the backend. * * @param {Connection} connection - Connection to update + * @return {Promise} - the promise resolves to true if the connection + * was successfully updated and to false it it does not exist. */ async updateConnection(connection) { - return this.adapter.updateConnection(connection); + const path = await this.getConnectionPath(connection.uuid); + if (path === undefined) { + return false; + } + + await this.updateConnectionAt(path, connection); + return true; + } + + /** + * Updates the connection in the given path + * + * + * @param {string} path - D-Bus path of the connection to update. + * @param {Connection} connection - Connection to update. + */ + async updateConnectionAt(path, connection) { + const { ipv4, wireless } = connection; + await this.setProperty(path, IP_IFACE, "Method4", cockpit.variant("s", ipv4.method)); + await this.setProperty(path, IP_IFACE, "Gateway4", cockpit.variant("s", ipv4.gateway)); + const addresses = ipv4.addresses.map(a => `${a.address}/${a.prefix}`); + await this.setProperty(path, IP_IFACE, "Addresses", cockpit.variant("as", addresses)); + await this.setProperty(path, IP_IFACE, "Nameservers", cockpit.variant("as", ipv4.nameServers)); + + if (wireless) { + await this.setProperty(path, WIRELESS_IFACE, "Mode", cockpit.variant("s", "infrastructure")); + if (wireless.password) { + await this.setProperty(path, WIRELESS_IFACE, "Password", cockpit.variant("s", wireless.password)); + } + await this.setProperty(path, WIRELESS_IFACE, "Security", cockpit.variant("s", wireless.security)); + const ssid = cockpit.byte_array(wireless.ssid); + await this.setProperty(path, WIRELESS_IFACE, "SSID", cockpit.variant("ay", ssid)); + } + + // TODO: apply the changes only in this connection + return this.proxies.connectionsRoot.Apply(); } /** @@ -198,7 +352,7 @@ o * NetworkManagerAdapter. * @param {Connection} connection - Connection to delete */ async deleteConnection(connection) { - return this.adapter.deleteConnection(connection); + return this.nm.deleteConnection(connection); } /* @@ -209,7 +363,7 @@ o * NetworkManagerAdapter. * @return {Promise} */ addresses() { - const conns = this.adapter.activeConnections(); + const conns = this.activeConnections(); return conns.flatMap(c => c.addresses); } @@ -217,15 +371,13 @@ o * NetworkManagerAdapter. * Returns network general settings */ settings() { - return this.adapter.settings(); + return this.nm.settings(); } } export { - AgamaNetworkAdapter, ConnectionState, ConnectionTypes, NetworkClient, - NetworkManagerAdapter, NetworkEventTypes }; diff --git a/web/src/client/network/model.js b/web/src/client/network/model.js index 563ff8cd75..f90e4e4918 100644 --- a/web/src/client/network/model.js +++ b/web/src/client/network/model.js @@ -61,15 +61,15 @@ const SecurityProtocols = Object.freeze({ }); // security protocols -const AgamaSecurityProtocols = Object.freeze({ - WEP: "none", - OWE: "owe", - DynamicWEP: "ieee8021x", - WPA2: "wpa-psk", - WPA3Personal: "sae", - WPA2Enterprise: "wpa-eap", - WPA3Only: "wpa-eap-suite-b-192" -}); +// const AgamaSecurityProtocols = Object.freeze({ +// WEP: "none", +// OWE: "owe", +// DynamicWEP: "ieee8021x", +// WPA2: "wpa-psk", +// WPA3Personal: "sae", +// WPA2Enterprise: "wpa-eap", +// WPA3Only: "wpa-eap-suite-b-192" +// }); /** * @typedef {object} IPAddress diff --git a/web/src/client/network/network_manager.js b/web/src/client/network/network_manager.js index a4595f8f09..cd80dbabb6 100644 --- a/web/src/client/network/network_manager.js +++ b/web/src/client/network/network_manager.js @@ -24,7 +24,7 @@ import DBusClient from "../dbus"; import cockpit from "../../lib/cockpit"; import { NetworkEventTypes } from "./index"; -import { createAccessPoint, createConnection, SecurityProtocols } from "./model"; +import { createAccessPoint, SecurityProtocols } from "./model"; import { ipPrefixFor } from "./utils"; /** @@ -257,6 +257,7 @@ class NetworkManagerAdapter { const { connection, ipv4, "802-11-wireless": wireless, path } = settings; const conn = { id: connection.uuid.v, + uuid: connection.uuid.v, name: connection.id.v, type: connection.type.v }; @@ -284,19 +285,6 @@ class NetworkManagerAdapter { return conn; } - /** - * Returns the connection with the given ID - * - * @param {string} id - Connection ID - * @return {Promise} - */ - async getConnection(id) { - const settingsProxy = await this.connectionSettingsObject(id); - const settings = await settingsProxy.GetSettings(); - - return this.connectionFromSettings(settings); - } - /** * Connects to given Wireless network * @@ -307,38 +295,6 @@ class NetworkManagerAdapter { await this.activateConnection(settingsProxy.path); } - /** - * Connects to given Wireless network - * - * @param {string} ssid - Network id - * @param {object} options - connection options - */ - async addAndConnectTo(ssid, options = {}) { - const wireless = { ssid }; - if (options.security) wireless.security = options.security; - if (options.password) wireless.password = options.password; - if (options.hidden) wireless.hidden = options.hidden; - - const connection = createConnection({ - name: ssid, - wireless - }); - - await this.addConnection(connection); - } - - /** - * Adds a new connection - * - * @param {Connection} connection - Connection to add - */ - async addConnection(connection) { - const proxy = await this.client.proxy(SETTINGS_IFACE); - const connCockpit = connectionToCockpit(connection); - const path = await proxy.AddConnection(connCockpit); - await this.activateConnection(path); - } - /** * Updates the connection * From a79c3516e13b94368ff73c7af937dc3b04431448 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Wed, 17 Jan 2024 10:23:02 +0000 Subject: [PATCH 10/40] [web] Improve onNetworkEvent side-effect initialization --- web/src/components/overview/NetworkSection.jsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/web/src/components/overview/NetworkSection.jsx b/web/src/components/overview/NetworkSection.jsx index bba8796141..f56ae195ea 100644 --- a/web/src/components/overview/NetworkSection.jsx +++ b/web/src/components/overview/NetworkSection.jsx @@ -44,6 +44,8 @@ export default function NetworkSection() { }, [client, initialized]); useEffect(() => { + if (!initialized) return; + return client.onNetworkEvent(({ type, payload }) => { switch (type) { case NetworkEventTypes.ACTIVE_CONNECTION_ADDED: { @@ -68,7 +70,7 @@ export default function NetworkSection() { } } }); - }); + }, [client, initialized]); const Content = () => { if (!initialized) return ; From 0c41325df83ee70b2bd7bcf692d531e78fe7cf5f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Wed, 17 Jan 2024 11:25:05 +0000 Subject: [PATCH 11/40] [web] Reload the list of connections on network events --- .../components/overview/NetworkSection.jsx | 26 +++---------------- 1 file changed, 3 insertions(+), 23 deletions(-) diff --git a/web/src/components/overview/NetworkSection.jsx b/web/src/components/overview/NetworkSection.jsx index f56ae195ea..df8c69cea4 100644 --- a/web/src/components/overview/NetworkSection.jsx +++ b/web/src/components/overview/NetworkSection.jsx @@ -46,35 +46,15 @@ export default function NetworkSection() { useEffect(() => { if (!initialized) return; - return client.onNetworkEvent(({ type, payload }) => { - switch (type) { - case NetworkEventTypes.ACTIVE_CONNECTION_ADDED: { - setConnections(conns => { - const newConnections = conns.filter(c => c.id !== payload.id); - return [...newConnections, payload]; - }); - break; - } - - case NetworkEventTypes.ACTIVE_CONNECTION_UPDATED: { - setConnections(conns => { - const newConnections = conns.filter(c => c.id !== payload.id); - return [...newConnections, payload]; - }); - break; - } - - case NetworkEventTypes.ACTIVE_CONNECTION_REMOVED: { - setConnections(conns => conns.filter(c => c.id !== payload.id)); - break; - } - } + return client.onNetworkEvent(() => { + setConnections(client.activeConnections()); }); }, [client, initialized]); const Content = () => { if (!initialized) return ; + console.log("Connetions", connections); const activeConnections = connections.filter(c => [ConnectionTypes.WIFI, ConnectionTypes.ETHERNET].includes(c.type)); if (activeConnections.length === 0) return _("No network connections detected"); From 8412ccd82c6d8fefd412bfee4bf0bfbc643e97b8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Wed, 17 Jan 2024 11:30:06 +0000 Subject: [PATCH 12/40] [web] Remove unused import --- web/src/components/overview/NetworkSection.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/components/overview/NetworkSection.jsx b/web/src/components/overview/NetworkSection.jsx index df8c69cea4..f43a9013d2 100644 --- a/web/src/components/overview/NetworkSection.jsx +++ b/web/src/components/overview/NetworkSection.jsx @@ -23,7 +23,7 @@ import React, { useEffect, useState } from "react"; import { sprintf } from "sprintf-js"; import { Em, Section, SectionSkeleton } from "~/components/core"; -import { ConnectionTypes, NetworkEventTypes } from "~/client/network"; +import { ConnectionTypes } from "~/client/network"; import { useInstallerClient } from "~/context/installer"; import { formatIp } from "~/client/network/utils"; import { _, n_ } from "~/i18n"; From 51742c97bc91f5a1a9b1cf3e20ab8b7fba411174 Mon Sep 17 00:00:00 2001 From: Knut Anderssen Date: Wed, 17 Jan 2024 17:23:49 +0000 Subject: [PATCH 13/40] Fix delete connection --- web/src/client/network/index.js | 5 ++++- web/src/client/network/network_manager.js | 4 +++- web/src/components/network/NetworkPage.jsx | 5 ++++- 3 files changed, 11 insertions(+), 3 deletions(-) diff --git a/web/src/client/network/index.js b/web/src/client/network/index.js index 7c871d1394..ed835b06bb 100644 --- a/web/src/client/network/index.js +++ b/web/src/client/network/index.js @@ -352,7 +352,10 @@ class NetworkClient { * @param {Connection} connection - Connection to delete */ async deleteConnection(connection) { - return this.nm.deleteConnection(connection); + const proxy = await this.client.proxy(CONNECTIONS_IFACE, CONNECTIONS_PATH); + const conn = await this.getConnection(connection.uuid); + await proxy.RemoveConnection(conn.id); + return this.proxies.connectionsRoot.Apply(); } /* diff --git a/web/src/client/network/network_manager.js b/web/src/client/network/network_manager.js index cd80dbabb6..c23d965ad3 100644 --- a/web/src/client/network/network_manager.js +++ b/web/src/client/network/network_manager.js @@ -203,6 +203,7 @@ class NetworkManagerAdapter { settings: await this.client.proxy(SETTINGS_IFACE), connections: await this.client.proxies(CONNECTION_IFACE, SETTINGS_NAMESPACE) }; + this.subscribeToEvents(); } @@ -345,7 +346,7 @@ class NetworkManagerAdapter { let connection; if (eventType === NetworkEventTypes.CONNECTION_REMOVED) { - connection = { id: proxy.id, path: proxy.path }; + connection = { id: proxy.uuid, path: proxy.path }; } else { connection = await this.connectionFromProxy(proxy); } @@ -417,6 +418,7 @@ class NetworkManagerAdapter { return { id: proxy.Uuid, + uuid: proxy.Uuid, name: proxy.Id, addresses, type: proxy.Type, diff --git a/web/src/components/network/NetworkPage.jsx b/web/src/components/network/NetworkPage.jsx index a5454e5543..d6b132e1d6 100644 --- a/web/src/components/network/NetworkPage.jsx +++ b/web/src/components/network/NetworkPage.jsx @@ -141,7 +141,10 @@ export default function NetworkPage() { const forgetConnection = async ({ id }) => { const connection = await client.getConnection(id); - client.deleteConnection(connection); + + if (connection) { + client.deleteConnection(connection); + } }; const activeWiredConnections = connections.filter(c => c.type === ConnectionTypes.ETHERNET); From dc4d9bba6176d2d754efef4f7ec68af9179d49b1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Wed, 17 Jan 2024 21:57:12 +0000 Subject: [PATCH 14/40] Use UUIDs to identify connections --- rust/agama-dbus-server/src/network/action.rs | 4 +- .../network/dbus/interfaces/connections.rs | 20 ++++--- .../src/network/dbus/tree.rs | 60 +++++++------------ rust/agama-dbus-server/src/network/model.rs | 23 ++++--- rust/agama-dbus-server/src/network/system.rs | 10 ++-- 5 files changed, 55 insertions(+), 62 deletions(-) diff --git a/rust/agama-dbus-server/src/network/action.rs b/rust/agama-dbus-server/src/network/action.rs index 0b3d00b5c2..3059493b6d 100644 --- a/rust/agama-dbus-server/src/network/action.rs +++ b/rust/agama-dbus-server/src/network/action.rs @@ -24,7 +24,7 @@ pub enum Action { /// Gets a connection GetConnection(Uuid, Responder>), /// Gets a connection - GetConnectionPath(String, Responder>), + GetConnectionPath(Uuid, Responder>), /// Get connections paths GetConnectionsPaths(Responder>), /// Gets a controller connection @@ -44,7 +44,7 @@ pub enum Action { /// Update a connection (replacing the old one). UpdateConnection(Box), /// Remove the connection with the given Uuid. - RemoveConnection(String), + RemoveConnection(Uuid), /// Apply the current configuration. Apply, } diff --git a/rust/agama-dbus-server/src/network/dbus/interfaces/connections.rs b/rust/agama-dbus-server/src/network/dbus/interfaces/connections.rs index 9fa57d7e6f..c6a989aafb 100644 --- a/rust/agama-dbus-server/src/network/dbus/interfaces/connections.rs +++ b/rust/agama-dbus-server/src/network/dbus/interfaces/connections.rs @@ -63,27 +63,29 @@ impl Connections { /// Returns the D-Bus path of the network connection. /// /// * `id`: connection ID. - pub async fn get_connection(&self, id: &str) -> zbus::fdo::Result { + pub async fn get_connection(&self, uuid: &str) -> zbus::fdo::Result { + let uuid: Uuid = uuid + .parse() + .map_err(|_| NetworkStateError::InvalidUuid(uuid.to_string()))?; let actions = self.actions.lock().await; let (tx, rx) = oneshot::channel(); - actions - .send(Action::GetConnectionPath(id.to_string(), tx)) - .unwrap(); + actions.send(Action::GetConnectionPath(uuid, tx)).unwrap(); let path = rx .await .unwrap() - .ok_or(NetworkStateError::UnknownConnection(id.to_string()))?; + .ok_or(NetworkStateError::UnknownConnection(uuid.to_string()))?; Ok(path) } /// Removes a network connection. /// /// * `uuid`: connection UUID.. - pub async fn remove_connection(&mut self, id: &str) -> zbus::fdo::Result<()> { + pub async fn remove_connection(&mut self, uuid: &str) -> zbus::fdo::Result<()> { + let uuid = uuid + .parse() + .map_err(|_| NetworkStateError::InvalidUuid(uuid.to_string()))?; let actions = self.actions.lock().await; - actions - .send(Action::RemoveConnection(id.to_string())) - .unwrap(); + actions.send(Action::RemoveConnection(uuid)).unwrap(); Ok(()) } diff --git a/rust/agama-dbus-server/src/network/dbus/tree.rs b/rust/agama-dbus-server/src/network/dbus/tree.rs index 149d9ab1ef..1cfd5a5d47 100644 --- a/rust/agama-dbus-server/src/network/dbus/tree.rs +++ b/rust/agama-dbus-server/src/network/dbus/tree.rs @@ -1,4 +1,5 @@ use agama_lib::error::ServiceError; +use uuid::Uuid; use zbus::zvariant::{ObjectPath, OwnedObjectPath}; use crate::network::{action::Action, dbus::interfaces, model::*}; @@ -80,12 +81,13 @@ impl Tree { conn: &mut Connection, ) -> Result { let uuid = conn.uuid; - let (id, path) = self.objects.register_connection(conn); - if id != conn.id { - conn.id = id.clone(); - } + let (_, path) = self.objects.register_connection(conn); let path: OwnedObjectPath = path.into(); - log::info!("Publishing network connection '{}' on '{}'", id, &path); + log::info!( + "Publishing network connection '{}' on '{}'", + &conn.id, + &path + ); self.add_interface( &path, @@ -114,13 +116,13 @@ impl Tree { /// Removes a connection from the tree /// - /// * `id`: connection ID. - pub async fn remove_connection(&mut self, id: &str) -> Result<(), ServiceError> { - let Some(path) = self.objects.connection_path(id) else { + /// * `uuid`: connection UUID. + pub async fn remove_connection(&mut self, uuid: Uuid) -> Result<(), ServiceError> { + let Some(path) = self.objects.connection_path(uuid) else { return Ok(()); }; self.remove_connection_on(path.as_str()).await?; - self.objects.deregister_connection(id).unwrap(); + self.objects.deregister_connection(uuid).unwrap(); Ok(()) } @@ -134,8 +136,8 @@ impl Tree { self.objects.connections_paths() } - pub fn connection_path(&self, id: &str) -> Option { - self.objects.connection_path(id).map(|o| o.into()) + pub fn connection_path(&self, uuid: Uuid) -> Option { + self.objects.connection_path(uuid).map(|o| o.into()) } /// Adds connections to the D-Bus tree. @@ -210,7 +212,7 @@ struct ObjectsRegistry { /// device_name (eth0) -> object_path devices: HashMap, /// id -> object_path - connections: HashMap, + connections: HashMap, } impl ObjectsRegistry { @@ -228,29 +230,25 @@ impl ObjectsRegistry { /// in case the original one already existed. /// /// * `conn`: network connection. - pub fn register_connection(&mut self, conn: &Connection) -> (String, ObjectPath) { + pub fn register_connection(&mut self, conn: &Connection) -> (Uuid, ObjectPath) { let path = format!("{}/{}", CONNECTIONS_PATH, self.connections.len()); let path = ObjectPath::try_from(path).unwrap(); - let mut id = conn.id.clone(); - if self.connection_path(&id).is_some() { - id = self.propose_id(&id); - }; - self.connections.insert(id.clone(), path.clone().into()); - (id, path) + self.connections.insert(conn.uuid, path.clone().into()); + (conn.uuid, path) } /// Returns the path for a connection. /// - /// * `id`: connection ID. - pub fn connection_path(&self, id: &str) -> Option { - self.connections.get(id).map(|p| p.as_ref()) + /// * `uuid`: connection ID. + pub fn connection_path(&self, uuid: Uuid) -> Option { + self.connections.get(&uuid).map(|p| p.as_ref()) } /// Deregisters a network connection. /// /// * `id`: connection ID. - pub fn deregister_connection(&mut self, id: &str) -> Option { - self.connections.remove(id) + pub fn deregister_connection(&mut self, uuid: Uuid) -> Option { + self.connections.remove(&uuid) } /// Returns all devices paths. @@ -262,18 +260,4 @@ impl ObjectsRegistry { pub fn connections_paths(&self) -> Vec { self.connections.values().cloned().collect() } - - /// Proposes a connection ID. - /// - /// * `id`: original connection ID. - fn propose_id(&self, id: &str) -> String { - let prefix = format!("{}-", id); - let filtered: Vec<_> = self - .connections - .keys() - .filter_map(|i| i.strip_prefix(&prefix).and_then(|n| n.parse::().ok())) - .collect(); - let index = filtered.into_iter().max().unwrap_or(0); - format!("{}{}", prefix, index + 1) - } } diff --git a/rust/agama-dbus-server/src/network/model.rs b/rust/agama-dbus-server/src/network/model.rs index 3338d06c84..507f515dd7 100644 --- a/rust/agama-dbus-server/src/network/model.rs +++ b/rust/agama-dbus-server/src/network/model.rs @@ -43,11 +43,18 @@ impl NetworkState { /// Get connection by UUID /// - /// * `id`: connection UUID + /// * `uuid`: connection UUID pub fn get_connection_by_uuid(&self, uuid: Uuid) -> Option<&Connection> { self.connections.iter().find(|c| c.uuid == uuid) } + /// Get connection by UUID as mutable + /// + /// * `uuid`: connection UUID + pub fn get_connection_by_uuid_mut(&mut self, uuid: Uuid) -> Option<&mut Connection> { + self.connections.iter_mut().find(|c| c.uuid == uuid) + } + /// Get connection by interface /// /// * `name`: connection interface name @@ -109,9 +116,9 @@ impl NetworkState { /// Removes a connection from the state. /// /// Additionally, it registers the connection to be removed when the changes are applied. - pub fn remove_connection(&mut self, id: &str) -> Result<(), NetworkStateError> { - let Some(conn) = self.get_connection_mut(id) else { - return Err(NetworkStateError::UnknownConnection(id.to_string())); + pub fn remove_connection(&mut self, uuid: Uuid) -> Result<(), NetworkStateError> { + let Some(conn) = self.get_connection_by_uuid_mut(uuid) else { + return Err(NetworkStateError::UnknownConnection(uuid.to_string())); }; conn.remove(); @@ -217,10 +224,10 @@ mod tests { #[test] fn test_remove_connection() { let mut state = NetworkState::default(); - let id = "eth0".to_string(); - let conn0 = Connection::new(id, DeviceType::Ethernet); + let conn0 = Connection::new("eth0".to_string(), DeviceType::Ethernet); + let uuid = conn0.uuid; state.add_connection(conn0).unwrap(); - state.remove_connection("eth0").unwrap(); + state.remove_connection(uuid).unwrap(); let found = state.get_connection("eth0").unwrap(); assert!(found.is_removed()); } @@ -228,7 +235,7 @@ mod tests { #[test] fn test_remove_unknown_connection() { let mut state = NetworkState::default(); - let error = state.remove_connection("eth0").unwrap_err(); + let error = state.remove_connection(Uuid::new_v4()).unwrap_err(); assert!(matches!(error, NetworkStateError::UnknownConnection(_))); } diff --git a/rust/agama-dbus-server/src/network/system.rs b/rust/agama-dbus-server/src/network/system.rs index 5272d58596..c1883cfacb 100644 --- a/rust/agama-dbus-server/src/network/system.rs +++ b/rust/agama-dbus-server/src/network/system.rs @@ -79,9 +79,9 @@ impl NetworkSystem { let conn = self.state.get_connection_by_uuid(uuid); tx.send(conn.cloned()).unwrap(); } - Action::GetConnectionPath(id, tx) => { + Action::GetConnectionPath(uuid, tx) => { let tree = self.tree.lock().await; - let path = tree.connection_path(&id); + let path = tree.connection_path(uuid); tx.send(path).unwrap(); } Action::GetController(uuid, tx) => { @@ -103,10 +103,10 @@ impl NetworkSystem { Action::UpdateConnection(conn) => { self.state.update_connection(*conn)?; } - Action::RemoveConnection(id) => { + Action::RemoveConnection(uuid) => { let mut tree = self.tree.lock().await; - tree.remove_connection(&id).await?; - self.state.remove_connection(&id)?; + tree.remove_connection(uuid).await?; + self.state.remove_connection(uuid)?; } Action::Apply => { self.write().await?; From 61729301333d31ff6025c76481542062bc3b8bfa Mon Sep 17 00:00:00 2001 From: Knut Anderssen Date: Thu, 18 Jan 2024 12:35:38 +0000 Subject: [PATCH 15/40] Fix connection uuid, id and name references --- web/src/client/network/index.js | 17 +++++++------- web/src/client/network/model.js | 10 ++++---- web/src/client/network/network_manager.js | 23 +++++++++---------- .../components/network/ConnectionsTable.jsx | 16 ++++++------- web/src/components/network/NetworkPage.jsx | 18 ++++++--------- .../network/WifiNetworkListItem.jsx | 14 +++++------ web/src/components/network/WifiSelector.jsx | 4 ++-- .../components/overview/NetworkSection.jsx | 2 +- 8 files changed, 49 insertions(+), 55 deletions(-) diff --git a/web/src/client/network/index.js b/web/src/client/network/index.js index ed835b06bb..cf2a4a9de3 100644 --- a/web/src/client/network/index.js +++ b/web/src/client/network/index.js @@ -194,7 +194,7 @@ class NetworkClient { if (options.hidden) wireless.hidden = options.hidden; const connection = createConnection({ - name: ssid, + id: ssid, wireless }); @@ -209,11 +209,11 @@ class NetworkClient { * @return {Promise} the added connection */ async addConnection(connection) { - const { name } = connection; + const { id } = connection; const proxy = await this.client.proxy(CONNECTIONS_IFACE, CONNECTIONS_PATH); const ctype = (connection.wireless) ? DeviceType.WIRELESS : DeviceType.ETHERNET; - const path = await proxy.AddConnection(name, ctype); - await this.updateConnectionAt(path, { ...connection, id: name }); + const path = await proxy.AddConnection(id, ctype); + await this.updateConnectionAt(path, connection); return this.connectionFromPath(path); } @@ -243,7 +243,7 @@ class NetworkClient { const conn = { id: connection.Id, uuid: connection.Uuid, - name: connection.Interface, + iface: connection.Interface, ipv4: { method: ip.Method4, nameServers: ip.Nameservers, @@ -349,12 +349,11 @@ class NetworkClient { * * It uses the 'path' to match the connection in the backend. * - * @param {Connection} connection - Connection to delete + * @param {String} uuid - Connection uuid */ - async deleteConnection(connection) { + async deleteConnection(uuid) { const proxy = await this.client.proxy(CONNECTIONS_IFACE, CONNECTIONS_PATH); - const conn = await this.getConnection(connection.uuid); - await proxy.RemoveConnection(conn.id); + await proxy.RemoveConnection(uuid); return this.proxies.connectionsRoot.Apply(); } diff --git a/web/src/client/network/model.js b/web/src/client/network/model.js index f90e4e4918..1c224a05be 100644 --- a/web/src/client/network/model.js +++ b/web/src/client/network/model.js @@ -80,7 +80,7 @@ const SecurityProtocols = Object.freeze({ /** * @typedef {object} ActiveConnection * @property {string} id - * @property {string} name + * @property {string} uuid * @property {string} type * @property {number} state * @property {IPAddress[]} addresses @@ -89,8 +89,8 @@ const SecurityProtocols = Object.freeze({ /** * @typedef {object} Connection * @property {string} id - * @property {string} name * @property {string} uuid + * @property {string} iface * @property {IPv4} [ipv4] * @property {Wireless} [wireless] */ @@ -154,16 +154,16 @@ const createIPv4 = ({ method, addresses, nameServers, gateway }) => { * @param {object} options * @param {string} [options.id] - Connection ID * @param {string} [options.uuid] - Connection UUID - * @param {string} [options.name] - Connection name + * @param {string} [options.iface] - Connection interface * @param {object} [options.ipv4] IPv4 Settings * @param {object} [options.wireless] Wireless Settings * @return {Connection} */ -const createConnection = ({ id, uuid, name, ipv4, wireless }) => { +const createConnection = ({ id, uuid, iface, ipv4, wireless }) => { const connection = { id, - name, uuid, + iface, ipv4: createIPv4(ipv4 || {}), }; diff --git a/web/src/client/network/network_manager.js b/web/src/client/network/network_manager.js index c23d965ad3..5ee87a8197 100644 --- a/web/src/client/network/network_manager.js +++ b/web/src/client/network/network_manager.js @@ -102,7 +102,7 @@ const connectionToCockpit = (connection) => { const { ipv4, wireless } = connection; const settings = { connection: { - id: cockpit.variant("s", connection.name) + id: cockpit.variant("s", connection.id) }, ipv4: { "address-data": cockpit.variant("aa{sv}", ipv4.addresses.map(addr => ( @@ -257,9 +257,9 @@ class NetworkManagerAdapter { connectionFromSettings(settings) { const { connection, ipv4, "802-11-wireless": wireless, path } = settings; const conn = { - id: connection.uuid.v, + id: connection.id.v, uuid: connection.uuid.v, - name: connection.id.v, + iface: connection.interface?.v, type: connection.type.v }; @@ -292,7 +292,7 @@ class NetworkManagerAdapter { * @param {object} settings - connection options */ async connectTo(settings) { - const settingsProxy = await this.connectionSettingsObject(settings.id); + const settingsProxy = await this.connectionSettingsObject(settings.uuid); await this.activateConnection(settingsProxy.path); } @@ -304,7 +304,7 @@ class NetworkManagerAdapter { * @param {import("./index").Connection} connection - Connection to update */ async updateConnection(connection) { - const settingsProxy = await this.connectionSettingsObject(connection.id); + const settingsProxy = await this.connectionSettingsObject(connection.uuid); const settings = await settingsProxy.GetSettings(); const newSettings = mergeConnectionSettings(settings, connection); await settingsProxy.Update(newSettings); @@ -317,7 +317,7 @@ class NetworkManagerAdapter { * @param {import("./index").Connection} connection - Connection to delete */ async deleteConnection(connection) { - const settingsProxy = await this.connectionSettingsObject(connection.id); + const settingsProxy = await this.connectionSettingsObject(connection.uuid); await settingsProxy.Delete(); } @@ -346,7 +346,7 @@ class NetworkManagerAdapter { let connection; if (eventType === NetworkEventTypes.CONNECTION_REMOVED) { - connection = { id: proxy.uuid, path: proxy.path }; + connection = { id: proxy.id, path: proxy.path }; } else { connection = await this.connectionFromProxy(proxy); } @@ -417,9 +417,8 @@ class NetworkManagerAdapter { } return { - id: proxy.Uuid, + id: proxy.Id, uuid: proxy.Uuid, - name: proxy.Id, addresses, type: proxy.Type, state: proxy.State @@ -431,12 +430,12 @@ class NetworkManagerAdapter { * Returns connection settings for the given connection * * @private - * @param {string} id - Connection ID + * @param {string} uuid - Connection ID * @return {Promise} */ - async connectionSettingsObject(id) { + async connectionSettingsObject(uuid) { const proxy = await this.client.proxy(SETTINGS_IFACE); - const path = await proxy.GetConnectionByUuid(id); + const path = await proxy.GetConnectionByUuid(uuid); return await this.client.proxy(CONNECTION_IFACE, path); } diff --git a/web/src/components/network/ConnectionsTable.jsx b/web/src/components/network/ConnectionsTable.jsx index d2b03bbcfe..cbae048365 100644 --- a/web/src/components/network/ConnectionsTable.jsx +++ b/web/src/components/network/ConnectionsTable.jsx @@ -42,7 +42,7 @@ import { _ } from "~/i18n"; * @param {function} props.onEdit - function to be called for editing a connection * @param {function} props.onForget - function to be called for forgetting a connection */ -export default function ConnectionsTable ({ +export default function ConnectionsTable({ connections, onEdit, onForget @@ -61,20 +61,20 @@ export default function ConnectionsTable ({ - { connections.map(connection => { + {connections.map(connection => { const actions = [ { title: _("Edit"), "aria-label": // TRANSLATORS: %s is replaced by a network connection name - sprintf(_("Edit connection %s"), connection.name), + sprintf(_("Edit connection %s"), connection.id), onClick: () => onEdit(connection) }, typeof onForget === 'function' && { title: _("Forget"), "aria-label": // TRANSLATORS: %s is replaced by a network connection name - sprintf(_("Forget connection %s"), connection.name), + sprintf(_("Forget connection %s"), connection.id), icon: , onClick: () => onForget(connection), isDanger: true @@ -82,14 +82,14 @@ export default function ConnectionsTable ({ ].filter(Boolean); return ( - - {connection.name} + + {connection.id} {connection.addresses.map(formatIp).join(", ")} diff --git a/web/src/components/network/NetworkPage.jsx b/web/src/components/network/NetworkPage.jsx index d6b132e1d6..4292d0663b 100644 --- a/web/src/components/network/NetworkPage.jsx +++ b/web/src/components/network/NetworkPage.jsx @@ -139,12 +139,8 @@ export default function NetworkPage() { client.getConnection(id).then(setSelectedConnection); }; - const forgetConnection = async ({ id }) => { - const connection = await client.getConnection(id); - - if (connection) { - client.deleteConnection(connection); - } + const forgetConnection = async ({ uuid }) => { + await client.deleteConnection(uuid); }; const activeWiredConnections = connections.filter(c => c.type === ConnectionTypes.ETHERNET); @@ -171,14 +167,14 @@ export default function NetworkPage() { return ( // TRANSLATORS: page title - { /* TRANSLATORS: page section */ } + { /* TRANSLATORS: page section */}
- { ready ? : } + {ready ? : }
- { /* TRANSLATORS: page section */ } + { /* TRANSLATORS: page section */}
- { ready ? : } + {ready ? : }
@@ -188,7 +184,7 @@ export default function NetworkPage() { then={} /> - { /* TODO: improve the connections edition */ } + { /* TODO: improve the connections edition */} setSelectedConnection(null)} />} diff --git a/web/src/components/network/WifiNetworkListItem.jsx b/web/src/components/network/WifiNetworkListItem.jsx index 550b087526..63520a8194 100644 --- a/web/src/components/network/WifiNetworkListItem.jsx +++ b/web/src/components/network/WifiNetworkListItem.jsx @@ -68,7 +68,7 @@ const isStateChanging = (network) => { * @param {function} props.onSelect - function to execute when the network is selected * @param {function} props.onCancel - function to execute when the selection is cancelled */ -function WifiNetworkListItem ({ network, isSelected, isActive, onSelect, onCancel }) { +function WifiNetworkListItem({ network, isSelected, isActive, onSelect, onCancel }) { // Do not wait until receive the next D-Bus network event to have the connection object available // and display the spinner as soon as possible. I.e., renders it immediately when the user clicks // on an already configured network. @@ -94,16 +94,16 @@ function WifiNetworkListItem ({ network, isSelected, isActive, onSelect, onCance />
{/* TRANSLATORS: %s is replaced by a WiFi network name */} - {showSpinner && } + {showSpinner && } - { showSpinner && !network.connection && _("Connecting") } - { networkState(network.connection?.state)} + {showSpinner && !network.connection && _("Connecting")} + {networkState(network.connection?.state)} - { network.settings && - } + {network.settings && + }
- { isSelected && (!network.settings || network.settings.error) && + {isSelected && (!network.settings || network.settings.error) &&
} diff --git a/web/src/components/network/WifiSelector.jsx b/web/src/components/network/WifiSelector.jsx index a839504f79..9b68da5af6 100644 --- a/web/src/components/network/WifiSelector.jsx +++ b/web/src/components/network/WifiSelector.jsx @@ -63,7 +63,7 @@ function WifiSelector({ isOpen = false, onClose }) { const network = { ...ap, settings: connections.find(c => c.wireless?.ssid === ap.ssid), - connection: activeConnections.find(c => c.name === ap.ssid) + connection: activeConnections.find(c => c.id === ap.ssid) }; // Group networks @@ -145,7 +145,7 @@ function WifiSelector({ isOpen = false, onClose }) { client.network.connectTo(network.settings); } }} - onCancelSelectionCallback={() => switchSelectedNetwork(activeNetwork) } + onCancelSelectionCallback={() => switchSelectedNetwork(activeNetwork)} /> {_("Close")} diff --git a/web/src/components/overview/NetworkSection.jsx b/web/src/components/overview/NetworkSection.jsx index f43a9013d2..b8d4ccc6ab 100644 --- a/web/src/components/overview/NetworkSection.jsx +++ b/web/src/components/overview/NetworkSection.jsx @@ -60,7 +60,7 @@ export default function NetworkSection() { if (activeConnections.length === 0) return _("No network connections detected"); const summary = activeConnections.map(connection => ( - {connection.name} - {connection.addresses.map(formatIp)} + {connection.id} - {connection.addresses.map(formatIp)} )); const msg = sprintf( From fb579ccfd8e9854d8d1ddce03330fcf578d43d1b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Thu, 18 Jan 2024 14:53:48 +0000 Subject: [PATCH 16/40] [web] Fix the activation, removal and selection of wifi devices --- web/src/client/network/index.js | 4 ++-- web/src/components/network/NetworkPage.jsx | 4 ++-- web/src/components/network/WifiNetworkMenu.jsx | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/web/src/client/network/index.js b/web/src/client/network/index.js index cf2a4a9de3..b6901a3346 100644 --- a/web/src/client/network/index.js +++ b/web/src/client/network/index.js @@ -198,8 +198,8 @@ class NetworkClient { wireless }); - const added = await this.addConnection(connection); - return this.connectTo(added); + // the connection is automatically activated when written + return this.addConnection(connection); } /** diff --git a/web/src/components/network/NetworkPage.jsx b/web/src/components/network/NetworkPage.jsx index 4292d0663b..e61f48dbd9 100644 --- a/web/src/components/network/NetworkPage.jsx +++ b/web/src/components/network/NetworkPage.jsx @@ -135,8 +135,8 @@ export default function NetworkPage() { }); }); - const selectConnection = ({ id }) => { - client.getConnection(id).then(setSelectedConnection); + const selectConnection = ({ uuid }) => { + client.getConnection(uuid).then(setSelectedConnection); }; const forgetConnection = async ({ uuid }) => { diff --git a/web/src/components/network/WifiNetworkMenu.jsx b/web/src/components/network/WifiNetworkMenu.jsx index 5b85a7057b..354ae84b69 100644 --- a/web/src/components/network/WifiNetworkMenu.jsx +++ b/web/src/components/network/WifiNetworkMenu.jsx @@ -48,7 +48,7 @@ export default function WifiNetworkMenu({ settings, position = "right" }) { client.network.deleteConnection(settings)} + onClick={() => client.network.deleteConnection(settings.uuid)} icon={} > {/* TRANSLATORS: menu label, remove the selected WiFi network settings */} From 83fce96e15dcb4d0ab074a9904e1e3703aa13054 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Thu, 18 Jan 2024 15:42:35 +0000 Subject: [PATCH 17/40] [web] Clean-up unused NetworkManager code --- web/src/client/network/network_manager.js | 25 ----- .../client/network/network_manager.test.js | 103 ++---------------- 2 files changed, 8 insertions(+), 120 deletions(-) diff --git a/web/src/client/network/network_manager.js b/web/src/client/network/network_manager.js index 5ee87a8197..5b480126d3 100644 --- a/web/src/client/network/network_manager.js +++ b/web/src/client/network/network_manager.js @@ -296,31 +296,6 @@ class NetworkManagerAdapter { await this.activateConnection(settingsProxy.path); } - /** - * Updates the connection - * - * @fixme improve it. - * - * @param {import("./index").Connection} connection - Connection to update - */ - async updateConnection(connection) { - const settingsProxy = await this.connectionSettingsObject(connection.uuid); - const settings = await settingsProxy.GetSettings(); - const newSettings = mergeConnectionSettings(settings, connection); - await settingsProxy.Update(newSettings); - await this.activateConnection(settingsProxy.path); - } - - /** - * Deletes the given connection - * - * @param {import("./index").Connection} connection - Connection to delete - */ - async deleteConnection(connection) { - const settingsProxy = await this.connectionSettingsObject(connection.uuid); - await settingsProxy.Delete(); - } - /** * Subscribes to network events * diff --git a/web/src/client/network/network_manager.test.js b/web/src/client/network/network_manager.test.js index 6663cffda0..fbc1248cd8 100644 --- a/web/src/client/network/network_manager.test.js +++ b/web/src/client/network/network_manager.test.js @@ -81,7 +81,7 @@ const defaultDevices = { }; const accessPoints = { - "/org/freedesktop/NetworkManager/AccessPoint/11" : { + "/org/freedesktop/NetworkManager/AccessPoint/11": { Flags: 3, WpaFlags: 0, RsnFlags: 392, @@ -276,16 +276,16 @@ describe("NetworkManagerAdapter", () => { expect(availableConnections.length).toEqual(2); const [wireless, ethernet] = availableConnections; expect(wireless).toEqual({ - name: "active-wifi-connection", - id: "uuid-wifi-1", + id: "active-wifi-connection", + uuid: "uuid-wifi-1", state: ConnectionState.ACTIVATED, type: ConnectionTypes.WIFI, addresses: [{ address: "10.0.0.2", prefix: 22 }] }); expect(ethernet).toEqual({ - name: "active-wired-connection", - id: "uuid-wired-1", + id: "active-wired-connection", + uuid: "uuid-wired-1", state: ConnectionState.ACTIVATED, type: ConnectionTypes.ETHERNET, addresses: [{ address: "10.0.0.1", prefix: 22 }] @@ -302,8 +302,8 @@ describe("NetworkManagerAdapter", () => { const [wifi] = connections; expect(wifi).toEqual({ - name: "Testing", - id: "1f40ddb0-e6e8-4af8-8b7a-0b3898f0f57a", + id: "Testing", + uuid: "1f40ddb0-e6e8-4af8-8b7a-0b3898f0f57a", path: "/org/freedesktop/NetworkManager/Settings/1", type: ConnectionTypes.WIFI, ipv4: { method: 'auto', addresses: [], nameServers: [] }, @@ -312,66 +312,6 @@ describe("NetworkManagerAdapter", () => { }); }); - describe("#getConnection", () => { - it("returns the connection with the given ID", async () => { - const client = new NetworkManagerAdapter(); - const connection = await client.getConnection("uuid-wifi-1"); - expect(connection).toEqual({ - id: "uuid-wifi-1", - name: "active-wifi-connection", - type: "802-11-wireless", - ipv4: { - addresses: [{ address: "192.168.122.200", prefix: 24 }], - gateway: "192.168.122.1", - method: "auto", - nameServers: ["192.168.122.1", "1.1.1.1"] - } - }); - }); - }); - - describe("#addConnection", () => { - it("adds a connection and activates it", async () => { - const client = new NetworkManagerAdapter(); - const connection = createConnection({ name: "Wired connection 1" }); - await client.addConnection(connection); - expect(AddConnectionFn).toHaveBeenCalledWith( - expect.objectContaining({ - connection: expect.objectContaining({ id: cockpit.variant("s", connection.name) }) - }) - ); - }); - }); - - describe("#updateConnection", () => { - it("updates the connection", async () => { - const client = new NetworkManagerAdapter(); - const connection = await client.getConnection("uuid-wifi-1"); - connection.ipv4 = { - ...connection.ipv4, - addresses: [{ address: "192.168.1.2", prefix: "255.255.255.0" }], - gateway: "192.168.1.1", - nameServers: ["1.2.3.4"] - }; - - await client.updateConnection(connection); - expect(connectionSettingsMock.Update).toHaveBeenCalledWith(expect.objectContaining( - { - connection: expect.objectContaining({ - id: cockpit.variant("s", "active-wifi-connection") - }), - ipv4: expect.objectContaining({ - "address-data": cockpit.variant("aa{sv}", [ - { address: cockpit.variant("s", "192.168.1.2"), prefix: cockpit.variant("u", 24) } - ]), - gateway: cockpit.variant("s", "192.168.1.1") - }) - } - )); - expect(ActivateConnectionFn).toHaveBeenCalled(); - }); - }); - describe("#connectTo", () => { it("activates the given connection", async () => { const client = new NetworkManagerAdapter(); @@ -382,33 +322,6 @@ describe("NetworkManagerAdapter", () => { }); }); - describe("#addAndConnectTo", () => { - it("activates the given connection", async () => { - const client = new NetworkManagerAdapter(); - await client.setUp(); - client.addConnection = jest.fn(); - await client.addAndConnectTo("Testing", { security: "wpa-psk", password: "testing.1234" }); - - expect(client.addConnection).toHaveBeenCalledWith( - createConnection({ - name: "Testing", - wireless: { ssid: "Testing", security: "wpa-psk", password: "testing.1234" } - }) - ); - }); - }); - - describe("#deleteConnection", () => { - it("deletes the given connection", async () => { - const client = new NetworkManagerAdapter(); - await client.setUp(); - const [wifi] = await client.connections(); - await client.deleteConnection(wifi); - - expect(connectionSettingsMock.Delete).toHaveBeenCalled(); - }); - }); - describe("#availableWifiDevices", () => { it("returns the list of WiFi devices", async () => { const client = new NetworkManagerAdapter(); @@ -459,7 +372,7 @@ describe("NetworkManagerAdapter", () => { }); describe("#settings", () => { - it("returns the Network Manager settings", async() => { + it("returns the Network Manager settings", async () => { const client = new NetworkManagerAdapter(); await client.setUp(); expect(client.settings().hostname).toEqual("testing-machine"); From e67709dd5a2235c14238d19d5fd0e33ffaeab551 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Thu, 18 Jan 2024 15:59:20 +0000 Subject: [PATCH 18/40] [web] Update network.test.js --- web/src/client/network.test.js | 44 ++++++++++++++++++---------------- 1 file changed, 24 insertions(+), 20 deletions(-) diff --git a/web/src/client/network.test.js b/web/src/client/network.test.js index d4a98f6e56..1841530439 100644 --- a/web/src/client/network.test.js +++ b/web/src/client/network.test.js @@ -22,8 +22,9 @@ // @ts-check import { NetworkClient, ConnectionTypes, ConnectionState } from "./network"; +const ADDRESS = "unix:path=/run/agama/bus"; -const active_conn = { +const mockActiveConnection = { id: "uuid-wired", name: "Wired connection 1", type: ConnectionTypes.ETHERNET, @@ -31,7 +32,7 @@ const active_conn = { addresses: [{ address: "192.168.122.1", prefix: 24 }] }; -const conn = { +const mockConnection = { id: "uuid-wired", name: "Wired connection 1", type: ConnectionTypes.ETHERNET, @@ -43,40 +44,43 @@ const settings = { hostname: "localhost.localdomain" }; -const adapter = { - setUp: jest.fn(), - activeConnections: jest.fn().mockReturnValue([active_conn]), - connections: jest.fn().mockReturnValue([conn]), - subscribe: jest.fn(), - getConnection: jest.fn(), - addConnection: jest.fn(), - updateConnection: jest.fn(), - deleteConnection: jest.fn(), - accessPoints: jest.fn(), - connectTo: jest.fn(), - addAndConnectTo: jest.fn(), - settings: jest.fn().mockReturnValue(settings), -}; +jest.mock("./network/network_manager", () => { + return { + NetworkManagerAdapter: jest.fn().mockImplementation(() => { + return { + setUp: jest.fn(), + activeConnections: jest.fn().mockReturnValue([mockActiveConnection]), + connections: jest.fn().mockReturnValue([mockConnection]), + subscribe: jest.fn(), + getConnection: jest.fn(), + accessPoints: jest.fn(), + connectTo: jest.fn(), + addAndConnectTo: jest.fn(), + settings: jest.fn().mockReturnValue(settings), + }; + }), + }; +}); describe("NetworkClient", () => { describe("#activeConnections", () => { it("returns the list of active connections from the adapter", () => { - const client = new NetworkClient(adapter); + const client = new NetworkClient(ADDRESS); const connections = client.activeConnections(); - expect(connections).toEqual([active_conn]); + expect(connections).toEqual([mockActiveConnection]); }); }); describe("#addresses", () => { it("returns the list of addresses", () => { - const client = new NetworkClient(adapter); + const client = new NetworkClient(ADDRESS); expect(client.addresses()).toEqual([{ address: "192.168.122.1", prefix: 24 }]); }); }); describe("#settings", () => { it("returns network general settings", () => { - const client = new NetworkClient(adapter); + const client = new NetworkClient(ADDRESS); expect(client.settings().hostname).toEqual("localhost.localdomain"); expect(client.settings().wifiScanSupported).toEqual(true); }); From 74c65a4df0fb6f7b1078cc5962e45071dd581729 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Thu, 18 Jan 2024 16:23:18 +0000 Subject: [PATCH 19/40] [web] Adapt network/model.test.js --- web/src/client/network/index.js | 4 ++-- web/src/client/network/model.test.js | 11 +++++++---- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/web/src/client/network/index.js b/web/src/client/network/index.js index b6901a3346..400462d16b 100644 --- a/web/src/client/network/index.js +++ b/web/src/client/network/index.js @@ -211,8 +211,8 @@ class NetworkClient { async addConnection(connection) { const { id } = connection; const proxy = await this.client.proxy(CONNECTIONS_IFACE, CONNECTIONS_PATH); - const ctype = (connection.wireless) ? DeviceType.WIRELESS : DeviceType.ETHERNET; - const path = await proxy.AddConnection(id, ctype); + const deviceType = (connection.wireless) ? DeviceType.WIRELESS : DeviceType.ETHERNET; + const path = await proxy.AddConnection(id, deviceType); await this.updateConnectionAt(path, connection); return this.connectionFromPath(path); } diff --git a/web/src/client/network/model.test.js b/web/src/client/network/model.test.js index 1eef2f8cb5..babfb5c72c 100644 --- a/web/src/client/network/model.test.js +++ b/web/src/client/network/model.test.js @@ -25,10 +25,13 @@ import { createConnection, createAccessPoint, connectionHumanState } from "./mod describe("createConnection", () => { it("creates a connection with the default values", () => { - const connection = createConnection({ name: "Wired connection" }); + const connection = createConnection({ + id: "Wired connection 1", + }); expect(connection).toEqual({ - id: undefined, - name: "Wired connection", + id: "Wired connection 1", + iface: undefined, + uuid: undefined, ipv4: { method: "auto", addresses: [], @@ -52,7 +55,7 @@ describe("createConnection", () => { it("adds a wireless key when given", () => { const wireless = { ssid: "MY_WIRELESS" }; - const connection = createConnection({ name: "Wireless 1", wireless }); + const connection = createConnection({ id: "Wireless connection 1", wireless }); expect(connection.wireless).toEqual(wireless); }); }); From 33f1c86c6a9114f27d5c33d0f8e72e0fddc9c14e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Thu, 18 Jan 2024 16:23:58 +0000 Subject: [PATCH 20/40] [web] Remove a useless call to console.log --- web/src/components/overview/NetworkSection.jsx | 1 - 1 file changed, 1 deletion(-) diff --git a/web/src/components/overview/NetworkSection.jsx b/web/src/components/overview/NetworkSection.jsx index b8d4ccc6ab..9a530c03a7 100644 --- a/web/src/components/overview/NetworkSection.jsx +++ b/web/src/components/overview/NetworkSection.jsx @@ -54,7 +54,6 @@ export default function NetworkSection() { const Content = () => { if (!initialized) return ; - console.log("Connetions", connections); const activeConnections = connections.filter(c => [ConnectionTypes.WIFI, ConnectionTypes.ETHERNET].includes(c.type)); if (activeConnections.length === 0) return _("No network connections detected"); From f82b169eb7f2d221ad4cb9d0b3cd78c89d7b5bfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Thu, 25 Jan 2024 22:25:40 +0000 Subject: [PATCH 21/40] [rust] Handles errors when writing the network configuration --- rust/agama-dbus-server/src/network.rs | 2 +- rust/agama-dbus-server/src/network/action.rs | 4 +- rust/agama-dbus-server/src/network/adapter.rs | 23 ++++++++++-- .../network/dbus/interfaces/connections.rs | 4 +- .../src/network/nm/adapter.rs | 37 ++++++++++++++----- .../src/network/nm/client.rs | 28 ++++++++++++++ rust/agama-dbus-server/src/network/system.rs | 14 +++++-- rust/agama-dbus-server/tests/network.rs | 9 ++--- 8 files changed, 95 insertions(+), 26 deletions(-) diff --git a/rust/agama-dbus-server/src/network.rs b/rust/agama-dbus-server/src/network.rs index 35158a10a8..536fdfdbf8 100644 --- a/rust/agama-dbus-server/src/network.rs +++ b/rust/agama-dbus-server/src/network.rs @@ -47,7 +47,7 @@ mod nm; pub mod system; pub use action::Action; -pub use adapter::Adapter; +pub use adapter::{Adapter, NetworkAdapterError}; pub use dbus::NetworkService; pub use model::NetworkState; pub use nm::NetworkManagerAdapter; diff --git a/rust/agama-dbus-server/src/network/action.rs b/rust/agama-dbus-server/src/network/action.rs index 3059493b6d..dd33622e1c 100644 --- a/rust/agama-dbus-server/src/network/action.rs +++ b/rust/agama-dbus-server/src/network/action.rs @@ -4,7 +4,7 @@ use tokio::sync::oneshot; use uuid::Uuid; use zbus::zvariant::OwnedObjectPath; -use super::error::NetworkStateError; +use super::{error::NetworkStateError, NetworkAdapterError}; pub type Responder = oneshot::Sender; pub type ControllerConnection = (Connection, Vec); @@ -46,5 +46,5 @@ pub enum Action { /// Remove the connection with the given Uuid. RemoveConnection(Uuid), /// Apply the current configuration. - Apply, + Apply(Responder>), } diff --git a/rust/agama-dbus-server/src/network/adapter.rs b/rust/agama-dbus-server/src/network/adapter.rs index c96ce8e649..8aba3251db 100644 --- a/rust/agama-dbus-server/src/network/adapter.rs +++ b/rust/agama-dbus-server/src/network/adapter.rs @@ -1,10 +1,27 @@ use crate::network::NetworkState; +use agama_lib::error::ServiceError; use async_trait::async_trait; -use std::error::Error; +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum NetworkAdapterError { + #[error("Could not read the network configuration: {0}")] + Read(ServiceError), + #[error("Could not update the network configuration: {0}")] + Write(ServiceError), + #[error("Checkpoint handling error: {0}")] + Checkpoint(ServiceError), // only relevant for adapters that implement a checkpoint mechanism +} /// A trait for the ability to read/write from/to a network service #[async_trait] pub trait Adapter { - async fn read(&self) -> Result>; - async fn write(&self, network: &NetworkState) -> Result<(), Box>; + async fn read(&self) -> Result; + async fn write(&self, network: &NetworkState) -> Result<(), NetworkAdapterError>; +} + +impl From for zbus::fdo::Error { + fn from(value: NetworkAdapterError) -> zbus::fdo::Error { + zbus::fdo::Error::Failed(value.to_string()) + } } diff --git a/rust/agama-dbus-server/src/network/dbus/interfaces/connections.rs b/rust/agama-dbus-server/src/network/dbus/interfaces/connections.rs index c6a989aafb..7dc0df2e83 100644 --- a/rust/agama-dbus-server/src/network/dbus/interfaces/connections.rs +++ b/rust/agama-dbus-server/src/network/dbus/interfaces/connections.rs @@ -94,7 +94,9 @@ impl Connections { /// It includes adding, updating and removing connections as needed. pub async fn apply(&self) -> zbus::fdo::Result<()> { let actions = self.actions.lock().await; - actions.send(Action::Apply).unwrap(); + let (tx, rx) = oneshot::channel(); + actions.send(Action::Apply(tx)).unwrap(); + rx.await.unwrap()?; Ok(()) } diff --git a/rust/agama-dbus-server/src/network/nm/adapter.rs b/rust/agama-dbus-server/src/network/nm/adapter.rs index 65bbcfa4ed..0df720e5ba 100644 --- a/rust/agama-dbus-server/src/network/nm/adapter.rs +++ b/rust/agama-dbus-server/src/network/nm/adapter.rs @@ -1,7 +1,7 @@ use crate::network::{ model::{Connection, NetworkState}, nm::NetworkManagerClient, - Adapter, + Adapter, NetworkAdapterError, }; use agama_lib::error::ServiceError; use async_trait::async_trait; @@ -29,9 +29,17 @@ impl<'a> NetworkManagerAdapter<'a> { #[async_trait] impl<'a> Adapter for NetworkManagerAdapter<'a> { - async fn read(&self) -> Result> { - let devices = self.client.devices().await?; - let connections = self.client.connections().await?; + async fn read(&self) -> Result { + let devices = self + .client + .devices() + .await + .map_err(NetworkAdapterError::Read)?; + let connections = self + .client + .connections() + .await + .map_err(NetworkAdapterError::Read)?; Ok(NetworkState::new(devices, connections)) } @@ -42,11 +50,14 @@ impl<'a> Adapter for NetworkManagerAdapter<'a> { /// simpler approach. /// /// * `network`: network model. - async fn write(&self, network: &NetworkState) -> Result<(), Box> { + async fn write(&self, network: &NetworkState) -> Result<(), NetworkAdapterError> { let old_state = self.read().await?; + let checkpoint = self + .client + .create_checkpoint() + .await + .map_err(NetworkAdapterError::Checkpoint)?; - // By now, traits do not support async functions. Using `task::block_on` allows - // to use 'await'. for conn in ordered_connections(network) { if !Self::is_writable(conn) { continue; @@ -69,10 +80,18 @@ impl<'a> Adapter for NetworkManagerAdapter<'a> { }; if let Err(e) = result { - log::error!("Could not process the connection {}: {}", conn.id, e); + self.client + .rollback_checkpoint(&checkpoint.as_ref()) + .await + .map_err(NetworkAdapterError::Checkpoint)?; + log::error!("Could not process the connection {}: {}", conn.id, &e); + return Err(NetworkAdapterError::Write(e)); } } - // FIXME: indicate which connections could not be written. + self.client + .destroy_checkpoint(&checkpoint.as_ref()) + .await + .map_err(NetworkAdapterError::Checkpoint)?; Ok(()) } } diff --git a/rust/agama-dbus-server/src/network/nm/client.rs b/rust/agama-dbus-server/src/network/nm/client.rs index 9f3a5990ed..8396d9d649 100644 --- a/rust/agama-dbus-server/src/network/nm/client.rs +++ b/rust/agama-dbus-server/src/network/nm/client.rs @@ -152,6 +152,34 @@ impl<'a> NetworkManagerClient<'a> { Ok(()) } + /// Creates a checkpoint. + pub async fn create_checkpoint(&self) -> Result { + let path = self.nm_proxy.checkpoint_create(&[], 0, 0).await?; + Ok(path) + } + + /// Destroys a checkpoint. + /// + /// * `checkpoint`: checkpoint's D-Bus path. + pub async fn destroy_checkpoint( + &self, + checkpoint: &ObjectPath<'_>, + ) -> Result<(), ServiceError> { + self.nm_proxy.checkpoint_destroy(checkpoint).await?; + Ok(()) + } + + /// Rolls the configuration back to the given checkpoint. + /// + /// * `checkpoint`: checkpoint's D-Bus path. + pub async fn rollback_checkpoint( + &self, + checkpoint: &ObjectPath<'_>, + ) -> Result<(), ServiceError> { + self.nm_proxy.checkpoint_rollback(checkpoint).await?; + Ok(()) + } + /// Activates a NetworkManager connection. /// /// * `path`: D-Bus patch of the connection. diff --git a/rust/agama-dbus-server/src/network/system.rs b/rust/agama-dbus-server/src/network/system.rs index c1883cfacb..ebf155ac34 100644 --- a/rust/agama-dbus-server/src/network/system.rs +++ b/rust/agama-dbus-server/src/network/system.rs @@ -1,4 +1,4 @@ -use super::error::NetworkStateError; +use super::{error::NetworkStateError, NetworkAdapterError}; use crate::network::{dbus::Tree, model::Connection, Action, Adapter, NetworkState}; use agama_lib::network::types::DeviceType; use std::{error::Error, sync::Arc}; @@ -35,7 +35,7 @@ impl NetworkSystem { } /// Writes the network configuration. - pub async fn write(&mut self) -> Result<(), Box> { + pub async fn write(&mut self) -> Result<(), NetworkAdapterError> { self.adapter.write(&self.state).await?; self.state = self.adapter.read().await?; Ok(()) @@ -108,8 +108,14 @@ impl NetworkSystem { tree.remove_connection(uuid).await?; self.state.remove_connection(uuid)?; } - Action::Apply => { - self.write().await?; + Action::Apply(tx) => { + let result = self.write().await; + let failed = result.is_err(); + tx.send(result).unwrap(); + if failed { + return Ok(()); + } + // TODO: re-creating the tree is kind of brute-force and it sends signals about // adding/removing interfaces. We should add/update/delete objects as needed. // NOTE updating the tree at the same time than dispatching actions can cause a diff --git a/rust/agama-dbus-server/tests/network.rs b/rust/agama-dbus-server/tests/network.rs index 616067d651..2261746325 100644 --- a/rust/agama-dbus-server/tests/network.rs +++ b/rust/agama-dbus-server/tests/network.rs @@ -4,7 +4,7 @@ use self::common::{async_retry, DBusServer}; use agama_dbus_server::network::{ self, model::{self, Ipv4Method, Ipv6Method}, - Adapter, NetworkService, NetworkState, + Adapter, NetworkAdapterError, NetworkService, NetworkState, }; use agama_lib::network::{ settings::{self}, @@ -21,14 +21,11 @@ pub struct NetworkTestAdapter(network::NetworkState); #[async_trait] impl Adapter for NetworkTestAdapter { - async fn read(&self) -> Result> { + async fn read(&self) -> Result { Ok(self.0.clone()) } - async fn write( - &self, - _network: &network::NetworkState, - ) -> Result<(), Box> { + async fn write(&self, _network: &network::NetworkState) -> Result<(), NetworkAdapterError> { unimplemented!("Not used in tests"); } } From 94f44ef61bf8d69b1e8c2b43149315d41cb7257e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Thu, 25 Jan 2024 22:51:06 +0000 Subject: [PATCH 22/40] [web] Display D-Bus errors from the network service --- web/src/components/network/IpSettingsForm.jsx | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/web/src/components/network/IpSettingsForm.jsx b/web/src/components/network/IpSettingsForm.jsx index 0984e3143b..565932e50f 100644 --- a/web/src/components/network/IpSettingsForm.jsx +++ b/web/src/components/network/IpSettingsForm.jsx @@ -111,8 +111,10 @@ export default function IpSettingsForm({ connection, onClose }) { } }; - client.network.updateConnection(updatedConnection); - onClose(); + client.network.updateConnection(updatedConnection) + .then(onClose) + // TODO: better error reporting. By now, it sets an error for the whole connection. + .catch(({ message }) => setErrors({ object: message })); }; const renderError = (field) => { @@ -129,6 +131,7 @@ export default function IpSettingsForm({ connection, onClose }) { // %s is replaced by the connection name return ( + {renderError("object")}
Date: Thu, 25 Jan 2024 22:52:06 +0000 Subject: [PATCH 23/40] [web] Fix the title of the edit connection popup --- web/src/components/network/IpSettingsForm.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/components/network/IpSettingsForm.jsx b/web/src/components/network/IpSettingsForm.jsx index 565932e50f..8244590143 100644 --- a/web/src/components/network/IpSettingsForm.jsx +++ b/web/src/components/network/IpSettingsForm.jsx @@ -130,7 +130,7 @@ export default function IpSettingsForm({ connection, onClose }) { // TRANSLATORS: manual network configuration mode with a static IP address // %s is replaced by the connection name return ( - + {renderError("object")} From 20cd8d203880857cda3699741f6dd4038a014eab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Thu, 25 Jan 2024 23:28:43 +0000 Subject: [PATCH 24/40] [rust] Do not set the gateway when there are not addresses --- rust/agama-dbus-server/src/network/nm/dbus.rs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/rust/agama-dbus-server/src/network/nm/dbus.rs b/rust/agama-dbus-server/src/network/nm/dbus.rs index 3dc7846a38..b6466a4da2 100644 --- a/rust/agama-dbus-server/src/network/nm/dbus.rs +++ b/rust/agama-dbus-server/src/network/nm/dbus.rs @@ -169,11 +169,17 @@ pub fn cleanup_dbus_connection(conn: &mut NestedHash) { if let Some(ipv4) = conn.get_mut("ipv4") { ipv4.remove("addresses"); ipv4.remove("dns"); + if ipv4.get("address-data").is_some_and(is_empty_value) { + ipv4.remove("gateway"); + } } if let Some(ipv6) = conn.get_mut("ipv6") { ipv6.remove("addresses"); ipv6.remove("dns"); + if ipv6.get("address-data").is_some_and(is_empty_value) { + ipv6.remove("gateway"); + } } } @@ -577,6 +583,10 @@ fn is_empty_value(value: &zvariant::Value) -> bool { return value.is_empty(); } + if let Some(value) = value.downcast_ref::() { + return value.is_empty(); + } + false } From 0c0eb6e172b60e665f75956e50d59fc9bbea3c59 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Fri, 26 Jan 2024 11:01:58 +0000 Subject: [PATCH 25/40] [rust] Properly report duplicated connections --- rust/agama-dbus-server/src/network/error.rs | 3 +-- rust/agama-dbus-server/src/network/model.rs | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/rust/agama-dbus-server/src/network/error.rs b/rust/agama-dbus-server/src/network/error.rs index 6b674b7efb..11111cac0d 100644 --- a/rust/agama-dbus-server/src/network/error.rs +++ b/rust/agama-dbus-server/src/network/error.rs @@ -1,6 +1,5 @@ //! Error types. use thiserror::Error; -use uuid::Uuid; /// Errors that are related to the network configuration. #[derive(Error, Debug)] @@ -16,7 +15,7 @@ pub enum NetworkStateError { #[error("Invalid wireless mode: '{0}'")] InvalidWirelessMode(String), #[error("Connection '{0}' already exists")] - ConnectionExists(Uuid), + ConnectionExists(String), #[error("Invalid security wireless protocol: '{0}'")] InvalidSecurityProtocol(String), #[error("Adapter error: '{0}'")] diff --git a/rust/agama-dbus-server/src/network/model.rs b/rust/agama-dbus-server/src/network/model.rs index 507f515dd7..466e3bb8d3 100644 --- a/rust/agama-dbus-server/src/network/model.rs +++ b/rust/agama-dbus-server/src/network/model.rs @@ -92,7 +92,7 @@ impl NetworkState { /// It uses the `id` to decide whether the connection already exists. pub fn add_connection(&mut self, conn: Connection) -> Result<(), NetworkStateError> { if self.get_connection(&conn.id).is_some() { - return Err(NetworkStateError::ConnectionExists(conn.uuid)); + return Err(NetworkStateError::ConnectionExists(conn.id.to_string())); } self.connections.push(conn); From 91fca81719c6da1b1cf42d76a0ecfe1ac3f2b158 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Fri, 26 Jan 2024 13:14:57 +0000 Subject: [PATCH 26/40] [rust] Fix the network client to find the connections --- rust/agama-dbus-server/src/network/action.rs | 2 ++ .../network/dbus/interfaces/connections.rs | 20 +++++++++++++++++-- rust/agama-dbus-server/src/network/model.rs | 2 +- rust/agama-dbus-server/src/network/system.rs | 10 ++++++++++ rust/agama-lib/src/network/client.rs | 9 ++++++--- rust/agama-lib/src/network/proxies.rs | 18 ++++++++--------- 6 files changed, 45 insertions(+), 16 deletions(-) diff --git a/rust/agama-dbus-server/src/network/action.rs b/rust/agama-dbus-server/src/network/action.rs index dd33622e1c..3b98ee912f 100644 --- a/rust/agama-dbus-server/src/network/action.rs +++ b/rust/agama-dbus-server/src/network/action.rs @@ -25,6 +25,8 @@ pub enum Action { GetConnection(Uuid, Responder>), /// Gets a connection GetConnectionPath(Uuid, Responder>), + /// Gets a connection + GetConnectionPathById(String, Responder>), /// Get connections paths GetConnectionsPaths(Responder>), /// Gets a controller connection diff --git a/rust/agama-dbus-server/src/network/dbus/interfaces/connections.rs b/rust/agama-dbus-server/src/network/dbus/interfaces/connections.rs index 7dc0df2e83..885aefb042 100644 --- a/rust/agama-dbus-server/src/network/dbus/interfaces/connections.rs +++ b/rust/agama-dbus-server/src/network/dbus/interfaces/connections.rs @@ -60,9 +60,9 @@ impl Connections { Ok(path) } - /// Returns the D-Bus path of the network connection. + /// Returns the D-Bus path of the network connection by its UUID. /// - /// * `id`: connection ID. + /// * `uuid`: connection UUID. pub async fn get_connection(&self, uuid: &str) -> zbus::fdo::Result { let uuid: Uuid = uuid .parse() @@ -77,6 +77,22 @@ impl Connections { Ok(path) } + /// Returns the D-Bus path of the network connection by its ID. + /// + /// * `id`: connection ID. + pub async fn get_connection_by_id(&self, id: &str) -> zbus::fdo::Result { + let actions = self.actions.lock().await; + let (tx, rx) = oneshot::channel(); + actions + .send(Action::GetConnectionPathById(id.to_string(), tx)) + .unwrap(); + let path = rx + .await + .unwrap() + .ok_or(NetworkStateError::UnknownConnection(id.to_string()))?; + Ok(path) + } + /// Removes a network connection. /// /// * `uuid`: connection UUID.. diff --git a/rust/agama-dbus-server/src/network/model.rs b/rust/agama-dbus-server/src/network/model.rs index 466e3bb8d3..c8dc21e1d5 100644 --- a/rust/agama-dbus-server/src/network/model.rs +++ b/rust/agama-dbus-server/src/network/model.rs @@ -92,7 +92,7 @@ impl NetworkState { /// It uses the `id` to decide whether the connection already exists. pub fn add_connection(&mut self, conn: Connection) -> Result<(), NetworkStateError> { if self.get_connection(&conn.id).is_some() { - return Err(NetworkStateError::ConnectionExists(conn.id.to_string())); + return Err(NetworkStateError::ConnectionExists(conn.id)); } self.connections.push(conn); diff --git a/rust/agama-dbus-server/src/network/system.rs b/rust/agama-dbus-server/src/network/system.rs index ebf155ac34..d125810848 100644 --- a/rust/agama-dbus-server/src/network/system.rs +++ b/rust/agama-dbus-server/src/network/system.rs @@ -84,6 +84,10 @@ impl NetworkSystem { let path = tree.connection_path(uuid); tx.send(path).unwrap(); } + Action::GetConnectionPathById(id, tx) => { + let path = self.get_connection_path_by_id_action(&id).await; + tx.send(path).unwrap(); + } Action::GetController(uuid, tx) => { let result = self.get_controller_action(uuid); tx.send(result).unwrap() @@ -182,4 +186,10 @@ impl NetworkSystem { Ok((conn, controlled)) } + + async fn get_connection_path_by_id_action(&mut self, id: &str) -> Option { + let conn = self.state.get_connection(id)?; + let tree = self.tree.lock().await; + tree.connection_path(conn.uuid) + } } diff --git a/rust/agama-lib/src/network/client.rs b/rust/agama-lib/src/network/client.rs index 79d9e4466b..b8a6b427a4 100644 --- a/rust/agama-lib/src/network/client.rs +++ b/rust/agama-lib/src/network/client.rs @@ -26,7 +26,7 @@ impl<'a> NetworkClient<'a> { } pub async fn get_connection(&self, id: &str) -> Result { - let path = self.connections_proxy.get_connection(id).await?; + let path = self.connections_proxy.get_connection_by_id(id).await?; self.connection_from(path.as_str()).await } @@ -200,7 +200,7 @@ impl<'a> NetworkClient<'a> { &self, conn: &NetworkConnection, ) -> Result<(), ServiceError> { - let path = match self.connections_proxy.get_connection(&conn.id).await { + let path = match self.connections_proxy.get_connection_by_id(&conn.id).await { Ok(path) => path, Err(_) => self.add_connection(conn).await?, }; @@ -230,7 +230,10 @@ impl<'a> NetworkClient<'a> { }; } - Ok(self.connections_proxy.get_connection(&conn.id).await?) + Ok(self + .connections_proxy + .get_connection_by_id(&conn.id) + .await?) } /// Updates a network connection. diff --git a/rust/agama-lib/src/network/proxies.rs b/rust/agama-lib/src/network/proxies.rs index 77aed57feb..9710980885 100644 --- a/rust/agama-lib/src/network/proxies.rs +++ b/rust/agama-lib/src/network/proxies.rs @@ -33,19 +33,17 @@ trait Device { default_path = "/org/opensuse/Agama1/Network/connections" )] trait Connections { - /// Add a new network connection. - /// - /// `name`: connection name. - /// `ty`: connection type. - fn add_connection(&self, name: &str, ty: u8) -> zbus::Result; + /// AddConnection method + fn add_connection(&self, id: &str, ty: u8) -> zbus::Result; /// Apply method fn apply(&self) -> zbus::Result<()>; - /// Gets a connection D-Bus path by its ID - /// - /// * `id`: connection ID. - fn get_connection(&self, id: &str) -> zbus::Result; + /// GetConnection method + fn get_connection(&self, uuid: &str) -> zbus::Result; + + /// GetConnectionById method + fn get_connection_by_id(&self, id: &str) -> zbus::Result; /// GetConnections method fn get_connections(&self) -> zbus::Result>; @@ -55,7 +53,7 @@ trait Connections { /// ConnectionAdded signal #[dbus_proxy(signal)] - fn connection_added(&self, id: &str, path: &str) -> zbus::Result<()>; + fn connection_added(&self, id: &str, path: zbus::zvariant::ObjectPath<'_>) -> zbus::Result<()>; } #[dbus_proxy( From 0f32ba32a576b77b94b19f78858eeff6e8b43e0e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Fri, 26 Jan 2024 13:17:38 +0000 Subject: [PATCH 27/40] [rust] Do not publish a connection when failing --- rust/agama-dbus-server/src/network/dbus/tree.rs | 13 ++++++------- rust/agama-dbus-server/src/network/system.rs | 6 +++--- 2 files changed, 9 insertions(+), 10 deletions(-) diff --git a/rust/agama-dbus-server/src/network/dbus/tree.rs b/rust/agama-dbus-server/src/network/dbus/tree.rs index 1cfd5a5d47..4cf98c0b93 100644 --- a/rust/agama-dbus-server/src/network/dbus/tree.rs +++ b/rust/agama-dbus-server/src/network/dbus/tree.rs @@ -78,11 +78,10 @@ impl Tree { /// * `notify`: whether to notify the added connection pub async fn add_connection( &mut self, - conn: &mut Connection, + conn: &Connection, ) -> Result { let uuid = conn.uuid; - let (_, path) = self.objects.register_connection(conn); - let path: OwnedObjectPath = path.into(); + let path: OwnedObjectPath = self.objects.register_connection(conn.uuid).into(); log::info!( "Publishing network connection '{}' on '{}'", &conn.id, @@ -229,12 +228,12 @@ impl ObjectsRegistry { /// It returns the connection Id and the D-Bus path. Bear in mind that the Id can be different /// in case the original one already existed. /// - /// * `conn`: network connection. - pub fn register_connection(&mut self, conn: &Connection) -> (Uuid, ObjectPath) { + /// * `uuid`: network connection's UUID. + pub fn register_connection(&mut self, uuid: Uuid) -> ObjectPath { let path = format!("{}/{}", CONNECTIONS_PATH, self.connections.len()); let path = ObjectPath::try_from(path).unwrap(); - self.connections.insert(conn.uuid, path.clone().into()); - (conn.uuid, path) + self.connections.insert(uuid, path.clone().into()); + path } /// Returns the path for a connection. diff --git a/rust/agama-dbus-server/src/network/system.rs b/rust/agama-dbus-server/src/network/system.rs index d125810848..a347caca6a 100644 --- a/rust/agama-dbus-server/src/network/system.rs +++ b/rust/agama-dbus-server/src/network/system.rs @@ -144,14 +144,14 @@ impl NetworkSystem { name: String, ty: DeviceType, ) -> Result { - let mut conn = Connection::new(name, ty); + let conn = Connection::new(name, ty); // TODO: handle tree handling problems + self.state.add_connection(conn.clone())?; let mut tree = self.tree.lock().await; let path = tree - .add_connection(&mut conn) + .add_connection(&conn) .await .expect("Could not update the D-Bus tree"); - self.state.add_connection(conn)?; Ok(path) } From aae5ca3c98c72d78fe44cf8874651ddc244ca270 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Fri, 26 Jan 2024 13:19:16 +0000 Subject: [PATCH 28/40] [rust] Fix unit tests --- rust/agama-dbus-server/src/network/nm/dbus.rs | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/rust/agama-dbus-server/src/network/nm/dbus.rs b/rust/agama-dbus-server/src/network/nm/dbus.rs index b6466a4da2..498843d9d5 100644 --- a/rust/agama-dbus-server/src/network/nm/dbus.rs +++ b/rust/agama-dbus-server/src/network/nm/dbus.rs @@ -853,6 +853,7 @@ mod test { Value::new(ETHERNET_KEY.to_string()).to_owned(), ), ]); + let ipv4 = HashMap::from([ ( "method".to_string(), @@ -911,10 +912,8 @@ mod test { *ipv4.get("method").unwrap(), Value::new("disabled".to_string()) ); - assert_eq!( - *ipv4.get("gateway").unwrap(), - Value::new("192.168.1.1".to_string()) - ); + // there are not addresses ("address-data"), so no gateway is allowed + assert!(ipv4.get("gateway").is_none()); assert!(ipv4.get("addresses").is_none()); let ipv6 = merged.get("ipv6").unwrap(); @@ -922,10 +921,8 @@ mod test { *ipv6.get("method").unwrap(), Value::new("disabled".to_string()) ); - assert_eq!( - *ipv6.get("gateway").unwrap(), - Value::new("::ffff:c0a8:101".to_string()) - ); + // there are not addresses ("address-data"), so no gateway is allowed + assert!(ipv6.get("gateway").is_none()); } #[test] From 9470cd2d1d09d1f3043bed0be6b5cf00d4b08e65 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Fri, 26 Jan 2024 15:32:44 +0000 Subject: [PATCH 29/40] [web] Fix the ConnectionsTable test --- web/src/components/network/ConnectionsTable.test.jsx | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/web/src/components/network/ConnectionsTable.test.jsx b/web/src/components/network/ConnectionsTable.test.jsx index e0812175d5..51bb90e54d 100644 --- a/web/src/components/network/ConnectionsTable.test.jsx +++ b/web/src/components/network/ConnectionsTable.test.jsx @@ -29,15 +29,15 @@ import ConnectionsTable from "~/components/network/ConnectionsTable"; jest.mock("~/client"); const firstConnection = { - id: "wifi-1", - name: "WiFi 1", + id: "WiFi 1", + uuid: "89e7d438-8143-4318-9e35-cdedfdf6c98a", type: ConnectionTypes.WIFI, addresses: [{ address: "192.168.69.200", prefix: 24 }] }; const secondConnection = { - id: "wifi-2", - name: "WiFi 2", + id: "WiFi 2", + uuid: "ddc46599-db7f-4306-aee4-2ab0938fd7d6", type: ConnectionTypes.WIFI, addresses: [{ address: "192.168.69.201", prefix: 24 }] }; @@ -71,7 +71,7 @@ describe("ConnectionsTable", () => { const { user } = plainRender(); const connectionActions = screen.getByRole("button", { name: "Actions for connection WiFi 1" }); const actionsColumn = connectionActions.parentNode; - const menu = await within(actionsColumn).queryByRole("menu"); + const menu = within(actionsColumn).queryByRole("menu"); expect(menu).toBeNull(); await user.click(connectionActions); await screen.findByRole("menu"); From 0330f104811cba6cd7a77267a302f9b771675fc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Fri, 26 Jan 2024 16:17:06 +0000 Subject: [PATCH 30/40] [web] Fix network tests --- .../components/network/NetworkPage.test.jsx | 8 ++--- .../overview/NetworkSection.test.jsx | 35 ++++++++++++------- 2 files changed, 26 insertions(+), 17 deletions(-) diff --git a/web/src/components/network/NetworkPage.test.jsx b/web/src/components/network/NetworkPage.test.jsx index 75a4bf813a..82405398da 100644 --- a/web/src/components/network/NetworkPage.test.jsx +++ b/web/src/components/network/NetworkPage.test.jsx @@ -30,14 +30,14 @@ jest.mock("~/client"); jest.mock("~/components/core/Sidebar", () => () =>
Agama sidebar
); const wiredConnection = { - id: "wired-1", - name: "Wired 1", + id: "Wired 1", + uuid: "e1ce3e83-f57c-4649-97d5-ecc468b74f97", type: ConnectionTypes.ETHERNET, addresses: [{ address: "192.168.122.20", prefix: 24 }] }; const wiFiConnection = { - id: "wifi-1", - name: "WiFi 1", + id: "WiFi 1", + uuid: "a4cf03c5-cb87-469d-824e-26b855a6bcfc", type: ConnectionTypes.WIFI, addresses: [{ address: "192.168.69.200", prefix: 24 }] }; diff --git a/web/src/components/overview/NetworkSection.test.jsx b/web/src/components/overview/NetworkSection.test.jsx index 264e4ac606..c1eaad1366 100644 --- a/web/src/components/overview/NetworkSection.test.jsx +++ b/web/src/components/overview/NetworkSection.test.jsx @@ -31,14 +31,14 @@ jest.mock("~/client"); jest.mock('~/components/core/SectionSkeleton', () => () =>
Section Skeleton
); const wiredConnection = { - id: "wired-1", - name: "Wired 1", + id: "Wired 1", + uuid: "d59200d4-838d-4051-99a0-fde8121a242c", type: ConnectionTypes.ETHERNET, addresses: [{ address: "192.168.122.20", prefix: 24 }] }; const wifiConnection = { - id: "wifi-1", - name: "WiFi 1", + id: "WiFi 1", + uuid: "0c00dccb-a6ae-40b2-8495-0de721006bc0", type: ConnectionTypes.WIFI, addresses: [{ address: "192.168.69.200", prefix: 24 }] }; @@ -110,16 +110,20 @@ describe("when connection is added", () => { await screen.findByText(/Wired 1/); await screen.findByText(/WiFi 1/); + // add a new connection + const addedConnection = { + id: "New Wired Network", + uuid: "b143c0a6-ee61-49dc-94aa-e62230afc199", + type: ConnectionTypes.ETHERNET, + addresses: [{ address: "192.168.168.192", prefix: 24 }] + }; + activeConnectionsFn.mockReturnValue([wiredConnection, wifiConnection, addedConnection]); + const [cb] = callbacks; act(() => { cb({ type: NetworkEventTypes.ACTIVE_CONNECTION_ADDED, - payload: { - id: "wired-2", - name: "New Wired Network", - type: ConnectionTypes.ETHERNET, - addresses: [{ address: "192.168.168.192", prefix: 24 }] - } + payload: addedConnection }); }); @@ -138,6 +142,8 @@ describe("when connection is removed", () => { await screen.findByText(/Wired 1/); await screen.findByText(/WiFi 1/); + // remove a connection + activeConnectionsFn.mockReturnValue([wifiConnection]); const [cb] = callbacks; act(() => { cb({ @@ -147,7 +153,7 @@ describe("when connection is removed", () => { }); await screen.findByText(/WiFi 1/); - const removedNetwork = await screen.queryByText(/Wired 1/); + const removedNetwork = screen.queryByText(/Wired 1/); expect(removedNetwork).toBeNull(); }); }); @@ -160,18 +166,21 @@ describe("when connection is updated", () => { await screen.findByText(/Wired 1/); await screen.findByText(/WiFi 1/); + // update a connection + const updatedConnection = { ...wiredConnection, id: "My Wired Connection" }; + activeConnectionsFn.mockReturnValue([updatedConnection, wifiConnection]); const [cb] = callbacks; act(() => { cb({ type: NetworkEventTypes.ACTIVE_CONNECTION_UPDATED, - payload: { ...wiredConnection, name: "My Wired Connection" } + payload: { ...wiredConnection, id: "My Wired Connection" } }); }); await screen.findByText(/My Wired Connection/); await screen.findByText(/WiFi 1/); - const formerWiredName = await screen.queryByText(/Wired 1/); + const formerWiredName = screen.queryByText(/Wired 1/); expect(formerWiredName).toBeNull(); }); }); From 27700688cea9bfdf1d9b977f61d65d97ad7e324e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Mon, 29 Jan 2024 11:10:20 +0000 Subject: [PATCH 31/40] [web] Refactor the updateConnectionAt method --- web/src/client/network/index.js | 41 +++++++++++++++++++++++++-------- 1 file changed, 31 insertions(+), 10 deletions(-) diff --git a/web/src/client/network/index.js b/web/src/client/network/index.js index 400462d16b..5eae2ca9dd 100644 --- a/web/src/client/network/index.js +++ b/web/src/client/network/index.js @@ -268,6 +268,20 @@ class NetworkClient { return createConnection(conn); } + /** + * Sets a property for a given path + * + * @param {string} path - Object path. + * @param {string} iface - Interface name. + * @param {object} values - Properties values (indexed by names). The value + * should be created by using the cockpit.variant() function. + */ + async setProperties(path, iface, values) { + for (const [prop, value] of Object.entries(values)) { + await this.setProperty(path, iface, prop, value); + } + } + /** * Sets a property for a given path * @@ -275,7 +289,7 @@ class NetworkClient { * @param {string} iface - Interface name. * @param {string} property - Property name. * @param {object} value - Property value. The value should be created by - * using the cockpit.variant() function. + * using the cockpit.variant() function. */ async setProperty(path, iface, property, value) { return this.client.call(path, "org.freedesktop.DBus.Properties", "Set", [iface, property, value]); @@ -324,20 +338,27 @@ class NetworkClient { */ async updateConnectionAt(path, connection) { const { ipv4, wireless } = connection; - await this.setProperty(path, IP_IFACE, "Method4", cockpit.variant("s", ipv4.method)); - await this.setProperty(path, IP_IFACE, "Gateway4", cockpit.variant("s", ipv4.gateway)); const addresses = ipv4.addresses.map(a => `${a.address}/${a.prefix}`); - await this.setProperty(path, IP_IFACE, "Addresses", cockpit.variant("as", addresses)); - await this.setProperty(path, IP_IFACE, "Nameservers", cockpit.variant("as", ipv4.nameServers)); + const ipv4_props = { + Method4: cockpit.variant("s", ipv4.method), + Gateway4: cockpit.variant("s", ipv4.gateway), + Addresses: cockpit.variant("as", addresses), + Nameservers: cockpit.variant("as", ipv4.nameServers) + }; + await this.setProperties(path, IP_IFACE, ipv4_props); if (wireless) { - await this.setProperty(path, WIRELESS_IFACE, "Mode", cockpit.variant("s", "infrastructure")); + const wireless_props = { + Mode: cockpit.variant("s", "infrastructure"), + Security: cockpit.variant("s", wireless.security), + SSID: cockpit.variant("ay", cockpit.byte_array(wireless.ssid)) + }; + if (wireless.password) { - await this.setProperty(path, WIRELESS_IFACE, "Password", cockpit.variant("s", wireless.password)); + wireless_props.Password = cockpit.variant("s", wireless.password); } - await this.setProperty(path, WIRELESS_IFACE, "Security", cockpit.variant("s", wireless.security)); - const ssid = cockpit.byte_array(wireless.ssid); - await this.setProperty(path, WIRELESS_IFACE, "SSID", cockpit.variant("ay", ssid)); + + await this.setProperties(path, WIRELESS_IFACE, wireless_props); } // TODO: apply the changes only in this connection From f7d3c597a2da816b6ac4b5ad238dc88ede5a66fc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Mon, 29 Jan 2024 11:10:56 +0000 Subject: [PATCH 32/40] [web] Bring back support for hidden networks --- .../network/dbus/interfaces/connection_configs.rs | 14 ++++++++++++++ rust/agama-dbus-server/src/network/model.rs | 1 + rust/agama-dbus-server/src/network/nm/dbus.rs | 7 +++++++ web/src/client/network/index.js | 5 +++-- 4 files changed, 25 insertions(+), 2 deletions(-) diff --git a/rust/agama-dbus-server/src/network/dbus/interfaces/connection_configs.rs b/rust/agama-dbus-server/src/network/dbus/interfaces/connection_configs.rs index c0967bb93e..f35d82f489 100644 --- a/rust/agama-dbus-server/src/network/dbus/interfaces/connection_configs.rs +++ b/rust/agama-dbus-server/src/network/dbus/interfaces/connection_configs.rs @@ -198,6 +198,20 @@ impl Wireless { .await?; Ok(()) } + + /// Whether the network is hidden or not. + #[dbus_interface(property)] + pub async fn hidden(&self) -> zbus::fdo::Result { + let config = self.get_config::().await?; + Ok(config.hidden) + } + + #[dbus_interface(property)] + pub async fn set_hidden(&mut self, hidden: bool) -> zbus::fdo::Result<()> { + self.update_config::(|c| c.hidden = hidden) + .await?; + Ok(()) + } } #[async_trait] diff --git a/rust/agama-dbus-server/src/network/model.rs b/rust/agama-dbus-server/src/network/model.rs index 159e208610..d700fc3f2a 100644 --- a/rust/agama-dbus-server/src/network/model.rs +++ b/rust/agama-dbus-server/src/network/model.rs @@ -739,6 +739,7 @@ pub struct VlanConfig { pub struct WirelessConfig { pub mode: WirelessMode, pub ssid: SSID, + pub hidden: bool, pub password: Option, pub security: SecurityProtocol, pub band: Option, diff --git a/rust/agama-dbus-server/src/network/nm/dbus.rs b/rust/agama-dbus-server/src/network/nm/dbus.rs index e8d448c88f..b8bfb6b260 100644 --- a/rust/agama-dbus-server/src/network/nm/dbus.rs +++ b/rust/agama-dbus-server/src/network/nm/dbus.rs @@ -332,6 +332,7 @@ fn wireless_config_to_dbus<'a>( ("mode", Value::new(config.mode.to_string())), ("ssid", Value::new(config.ssid.to_vec())), ("assigned-mac-address", Value::new(mac_address.to_string())), + ("hidden", Value::new(config.hidden)), ]); if let Some(band) = &config.band { @@ -710,6 +711,10 @@ fn wireless_config_from_dbus(conn: &OwnedNestedHash) -> Option { ..Default::default() }; + if let Some(hidden) = wireless.get("hidden") { + wireless_config.hidden = hidden.downcast_ref::().cloned().unwrap_or(false); + } + if let Some(band) = wireless.get("band") { wireless_config.band = Some(band.downcast_ref::()?.try_into().ok()?) } @@ -1011,6 +1016,7 @@ mod test { "bssid".to_string(), Value::new(vec![18_u8, 52_u8, 86_u8, 120_u8, 154_u8, 188_u8]).to_owned(), ), + ("hidden".to_string(), Value::new(true).to_owned()), ]); let security_section = HashMap::from([ @@ -1046,6 +1052,7 @@ mod test { assert_eq!(wep_security.wep_key_type, WEPKeyType::Key); assert_eq!(wep_security.auth_alg, WEPAuthAlg::Open); assert_eq!(wep_security.wep_key_index, 1); + assert!(wireless.hidden); } } diff --git a/web/src/client/network/index.js b/web/src/client/network/index.js index 5eae2ca9dd..18daf10b03 100644 --- a/web/src/client/network/index.js +++ b/web/src/client/network/index.js @@ -259,7 +259,7 @@ class NetworkClient { if (wireless) { conn.wireless = { ssid: window.atob(wireless.SSID), - hidden: false, // TODO implement the 'hidden' property + hidden: wireless.Hidden, mode: wireless.Mode, security: wireless.Security // see AgamaSecurityProtocols }; @@ -351,7 +351,8 @@ class NetworkClient { const wireless_props = { Mode: cockpit.variant("s", "infrastructure"), Security: cockpit.variant("s", wireless.security), - SSID: cockpit.variant("ay", cockpit.byte_array(wireless.ssid)) + SSID: cockpit.variant("ay", cockpit.byte_array(wireless.ssid)), + Hidden: cockpit.variant("b", wireless.hidden) }; if (wireless.password) { From 6da39c09d1a05160e69e18241164886281c9b4b6 Mon Sep 17 00:00:00 2001 From: Knut Anderssen Date: Mon, 29 Jan 2024 13:06:34 +0000 Subject: [PATCH 33/40] Remove match interface when removing a connection --- rust/agama-dbus-server/src/network/dbus/tree.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/rust/agama-dbus-server/src/network/dbus/tree.rs b/rust/agama-dbus-server/src/network/dbus/tree.rs index 4cf98c0b93..a1872c2da9 100644 --- a/rust/agama-dbus-server/src/network/dbus/tree.rs +++ b/rust/agama-dbus-server/src/network/dbus/tree.rs @@ -188,6 +188,7 @@ impl Tree { _ = object_server.remove::(path).await; _ = object_server.remove::(path).await; object_server.remove::(path).await?; + object_server.remove::(path).await?; object_server .remove::(path) .await?; From 6270468cd77d3d85eb52aa7d7e33299597682d45 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Mon, 29 Jan 2024 13:05:43 +0000 Subject: [PATCH 34/40] [web] Fix updating the hidden property --- web/src/client/network/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/client/network/index.js b/web/src/client/network/index.js index 18daf10b03..dcaa7410c3 100644 --- a/web/src/client/network/index.js +++ b/web/src/client/network/index.js @@ -352,7 +352,7 @@ class NetworkClient { Mode: cockpit.variant("s", "infrastructure"), Security: cockpit.variant("s", wireless.security), SSID: cockpit.variant("ay", cockpit.byte_array(wireless.ssid)), - Hidden: cockpit.variant("b", wireless.hidden) + Hidden: cockpit.variant("b", !!wireless.hidden) }; if (wireless.password) { From 7983c8663e6d28f6792e7c13521e0991366e5d88 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Mon, 29 Jan 2024 13:34:24 +0000 Subject: [PATCH 35/40] [web] Update a connection if it already exists --- web/src/client/network/index.js | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/web/src/client/network/index.js b/web/src/client/network/index.js index dcaa7410c3..0e02c0bced 100644 --- a/web/src/client/network/index.js +++ b/web/src/client/network/index.js @@ -205,6 +205,9 @@ class NetworkClient { /** * Adds a new connection * + * If a connection with the given ID already exists, it updates such a + * connection. + * * @param {Connection} connection - Connection to add * @return {Promise} the added connection */ @@ -212,7 +215,12 @@ class NetworkClient { const { id } = connection; const proxy = await this.client.proxy(CONNECTIONS_IFACE, CONNECTIONS_PATH); const deviceType = (connection.wireless) ? DeviceType.WIRELESS : DeviceType.ETHERNET; - const path = await proxy.AddConnection(id, deviceType); + let path; + try { + path = await proxy.GetConnectionById(id); + } catch { + path = await proxy.AddConnection(id, deviceType); + } await this.updateConnectionAt(path, connection); return this.connectionFromPath(path); } From 035566ac6b2c54232d4d96ab10216c95e06632e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Mon, 29 Jan 2024 14:28:15 +0000 Subject: [PATCH 36/40] [rust] Update the changes file --- rust/package/agama-cli.changes | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/rust/package/agama-cli.changes b/rust/package/agama-cli.changes index 1a2521ca72..e1c49fec24 100644 --- a/rust/package/agama-cli.changes +++ b/rust/package/agama-cli.changes @@ -1,3 +1,15 @@ +------------------------------------------------------------------- +Mon Jan 29 14:27:32 UTC 2024 - Imobach Gonzalez Sosa + +- Better network configuration handling (gh#openSUSE/agama#1006): + * Write only changed connections. + * Roll back when updating the NetworkManager configuration + failed. + * Improved error handling when reading or writing the changes. + * Add support for hidden wireless networks. + * Properly remove deleted connections from the D-Bus tree. + * Do not support multiple connections with the same ID. + ------------------------------------------------------------------- Mon Jan 29 10:22:49 UTC 2024 - Jorik Cronenberg From 8ff5b69e4e44daf7fe972d7d1e5e59441eecfb89 Mon Sep 17 00:00:00 2001 From: Knut Anderssen Date: Mon, 29 Jan 2024 14:37:49 +0000 Subject: [PATCH 37/40] Added cockpit-agama changes --- web/package/cockpit-agama.changes | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/web/package/cockpit-agama.changes b/web/package/cockpit-agama.changes index 17696a219b..11aa800032 100644 --- a/web/package/cockpit-agama.changes +++ b/web/package/cockpit-agama.changes @@ -1,3 +1,9 @@ +------------------------------------------------------------------- +Mon Jan 29 14:34:37 UTC 2024 - Knut Anderssen + +- Partly replacing the NetworkManager client by the agama one + (gh#openSUSE/agama#1006). + ------------------------------------------------------------------- Fri Jan 19 09:34:26 UTC 2024 - Nagu From febefe291067121b09d46dcc1f6fc0d2910767b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Mon, 29 Jan 2024 14:42:16 +0000 Subject: [PATCH 38/40] [rust] Update the changes file --- rust/package/agama-cli.changes | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rust/package/agama-cli.changes b/rust/package/agama-cli.changes index e1c49fec24..b59e03e49f 100644 --- a/rust/package/agama-cli.changes +++ b/rust/package/agama-cli.changes @@ -6,8 +6,9 @@ Mon Jan 29 14:27:32 UTC 2024 - Imobach Gonzalez Sosa * Roll back when updating the NetworkManager configuration failed. * Improved error handling when reading or writing the changes. - * Add support for hidden wireless networks. * Properly remove deleted connections from the D-Bus tree. + * Add support for hidden wireless networks. + * Use the UUID to identify connections. * Do not support multiple connections with the same ID. ------------------------------------------------------------------- From b05bfa60481072f368d38b0c9e62c489999f55b0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Mon, 29 Jan 2024 15:21:29 +0000 Subject: [PATCH 39/40] [web] Apply agama/list styles to the WiFi list * More work on that matter is still pending. --- web/src/components/network/WifiNetworksList.jsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/web/src/components/network/WifiNetworksList.jsx b/web/src/components/network/WifiNetworksList.jsx index ae7c29ce42..aa21c80dcc 100644 --- a/web/src/components/network/WifiNetworksList.jsx +++ b/web/src/components/network/WifiNetworksList.jsx @@ -63,7 +63,7 @@ function WifiNetworksList({ }; return ( -
    +
      {renderElements()}
    • From 1022b04b2dee846cb20cdad230ce2f3311e0abb7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Imobach=20Gonz=C3=A1lez=20Sosa?= Date: Mon, 29 Jan 2024 22:42:16 +0000 Subject: [PATCH 40/40] [rust] Fix unit tests --- rust/agama-dbus-server/src/network/nm/dbus.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/rust/agama-dbus-server/src/network/nm/dbus.rs b/rust/agama-dbus-server/src/network/nm/dbus.rs index d2570dad69..75876ad163 100644 --- a/rust/agama-dbus-server/src/network/nm/dbus.rs +++ b/rust/agama-dbus-server/src/network/nm/dbus.rs @@ -1053,7 +1053,6 @@ mod test { assert_eq!(wep_security.wep_key_type, WEPKeyType::Key); assert_eq!(wep_security.auth_alg, WEPAuthAlg::Open); assert_eq!(wep_security.wep_key_index, 1); - assert!(wireless.hidden); } }