diff --git a/libnymea-app/configuration/networkmanager.cpp b/libnymea-app/configuration/networkmanager.cpp index 0312f916..5109e390 100644 --- a/libnymea-app/configuration/networkmanager.cpp +++ b/libnymea-app/configuration/networkmanager.cpp @@ -40,6 +40,10 @@ #include #include +#include "logging.h" + +NYMEA_LOGGING_CATEGORY(dcNetworkManagement, "NetworkManagement") + NetworkManager::NetworkManager(QObject *parent): QObject(parent), m_wiredNetworkDevices(new WiredNetworkDevices(this)), @@ -167,6 +171,41 @@ int NetworkManager::startAccessPoint(const QString &interface, const QString &ss return m_engine->jsonRpcClient()->sendCommand("NetworkManager.StartAccessPoint", params, this, "startAccessPointResponse"); } +int NetworkManager::createWiredAutoConnection(const QString &interface) +{ + QVariantMap params { + {"interface", interface}, + {"type", "WiredNetworkConnectionTypeDHCP"} + }; + return m_engine->jsonRpcClient()->sendCommand("NetworkManager.CreateWiredConnection", params, this, "createWiredAutoConnectionResponse"); +} + +int NetworkManager::createWiredManualConnection(const QString &interface, const QString &ip, quint8 prefix, const QString &gateway, const QString &dns) +{ + qCDebug(dcNetworkManagement()) << "Creating manual connection" << interface << ip << prefix << gateway << dns; + QVariantMap params { + {"interface", interface}, + {"type", "WiredNetworkConnectionTypeManual"}, + {"ip", ip}, + {"prefix", prefix}, + {"gateway", gateway}, + {"dns", dns} + }; + return m_engine->jsonRpcClient()->sendCommand("NetworkManager.CreateWiredConnection", params, this, "createWiredManualConnectionResponse"); +} + +int NetworkManager::createWiredSharedConnection(const QString &interface, const QString &ip, quint8 prefix) +{ + qCDebug(dcNetworkManagement()) << "Creating shared connection" << interface << ip << prefix; + QVariantMap params { + {"interface", interface}, + {"type", "WiredNetworkConnectionTypeShared"}, + {"ip", ip}, + {"prefix", prefix} + }; + return m_engine->jsonRpcClient()->sendCommand("NetworkManager.CreateWiredConnection", params, this, "createWiredSharedConnectionResponse"); +} + int NetworkManager::disconnectInterface(const QString &interface) { QVariantMap params; @@ -212,7 +251,7 @@ void NetworkManager::getStatusResponse(int /*commandId*/, const QVariantMap &par void NetworkManager::getDevicesResponse(int /*commandId*/, const QVariantMap ¶ms) { -// qDebug() << "Devices reply" << commandId << qUtf8Printable(QJsonDocument::fromVariant(params).toJson(QJsonDocument::Indented)); + qCDebug(dcNetworkManagement) << "Devices reply" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson(QJsonDocument::Indented)); foreach (const QVariant &deviceVariant, params.value("wiredNetworkDevices").toList()) { QVariantMap deviceMap = deviceVariant.toMap(); @@ -248,7 +287,7 @@ void NetworkManager::getDevicesResponse(int /*commandId*/, const QVariantMap &pa void NetworkManager::getAccessPointsResponse(int commandId, const QVariantMap ¶ms) { - qDebug() << "Access points reply" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson(QJsonDocument::Indented)); + qCDebug(dcNetworkManagement) << "Access points reply" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson(QJsonDocument::Indented)); if (!m_apRequests.contains(commandId)) { qWarning() << "NetworkManager received a reply for a request we don't know!"; @@ -279,43 +318,87 @@ void NetworkManager::getAccessPointsResponse(int commandId, const QVariantMap &p void NetworkManager::connectToWiFiResponse(int commandId, const QVariantMap ¶ms) { - qDebug() << "connect to wifi reply" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson(QJsonDocument::Indented)); + qCDebug(dcNetworkManagement) << "connect to wifi reply" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson(QJsonDocument::Indented)); QString status = params.value("networkManagerError").toString(); emit connectToWiFiReply(commandId, status); } void NetworkManager::disconnectResponse(int commandId, const QVariantMap ¶ms) { - qDebug() << "disconnect reply" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson(QJsonDocument::Indented)); + qCDebug(dcNetworkManagement) << "disconnect reply" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson(QJsonDocument::Indented)); QString status = params.value("networkManagerError").toString(); emit disconnectReply(commandId, status); } void NetworkManager::enableNetworkingResponse(int commandId, const QVariantMap ¶ms) { - qDebug() << "enable networking reply" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson(QJsonDocument::Indented)); + qCDebug(dcNetworkManagement) << "enable networking reply" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson(QJsonDocument::Indented)); QString status = params.value("networkManagerError").toString(); emit enableNetworkingReply(commandId, status); } void NetworkManager::enableWirelessNetworkingResponse(int commandId, const QVariantMap ¶ms) { - qDebug() << "enable wireless networking reply" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson(QJsonDocument::Indented)); + qCDebug(dcNetworkManagement) << "enable wireless networking reply" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson(QJsonDocument::Indented)); QString status = params.value("networkManagerError").toString(); emit enableWirelessNetworkingReply(commandId, status); } void NetworkManager::startAccessPointResponse(int commandId, const QVariantMap ¶ms) { - qDebug() << "Start access point reply" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson(QJsonDocument::Indented)); + qCDebug(dcNetworkManagement) << "Start access point reply" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson(QJsonDocument::Indented)); QString status = params.value("networkManagerError").toString(); emit startAccessPointReply(commandId, status); } +void NetworkManager::createWiredAutoConnectionResponse(int commandId, const QVariantMap ¶ms) +{ + QString status = params.value("networkManagerError").toString(); + emit createWiredAutoConnectionReply(commandId, status); +} + +void NetworkManager::createWiredManualConnectionResponse(int commandId, const QVariantMap ¶ms) +{ + QString status = params.value("networkManagerError").toString(); + emit createWiredManualConnectionReply(commandId, status); +} + +void NetworkManager::createWiredSharedConnectionResponse(int commandId, const QVariantMap ¶ms) +{ + QString status = params.value("networkManagerError").toString(); + emit createWiredSharedConnectionReply(commandId, status); +} + void NetworkManager::notificationReceived(const QVariantMap ¶ms) { + qCDebug(dcNetworkManagement) << "Network management Notification:" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson()); + QString notification = params.value("notification").toString(); - if (notification == "NetworkManager.WirelessNetworkDeviceChanged") { + if (notification == "NetworkManager.WirelessNetworkDeviceAdded") { + QVariantMap deviceMap = params.value("params").toMap().value("wirelessNetworkDevice").toMap(); + + WirelessNetworkDevice *device = new WirelessNetworkDevice(deviceMap.value("macAddress").toString(), deviceMap.value("interface").toString(), this); + device->setIpv4Addresses(deviceMap.value("ipv4Addresses").toStringList()); + device->setIpv6Addresses(deviceMap.value("ipv6Addresses").toStringList()); + device->setBitRate(deviceMap.value("bitRate").toString()); + QMetaEnum stateEnum = QMetaEnum::fromType(); + device->setState(static_cast(stateEnum.keyToValue(deviceMap.value("state").toString().toUtf8()))); + QMetaEnum modeEnum = QMetaEnum::fromType(); + device->setWirelessMode(static_cast(modeEnum.keyToValue(deviceMap.value("mode").toString().toUtf8()))); + + QVariantMap currentApMap = deviceMap.value("currentAccessPoint").toMap(); + device->currentAccessPoint()->setSsid(currentApMap.value("ssid").toString()); + device->currentAccessPoint()->setMacAddress(currentApMap.value("macAddress").toString()); + device->currentAccessPoint()->setProtected(currentApMap.value("protected").toBool()); + device->currentAccessPoint()->setSignalStrength(currentApMap.value("signalStrength").toInt()); + device->currentAccessPoint()->setFrequency(currentApMap.value("frequency").toDouble()); + m_wirelessNetworkDevices->addNetworkDevice(device); + + } else if (notification == "NetworkManager.WirelessNetworkDeviceRemoved") { + QString interface = params.value("params").toMap().value("interface").toString(); + m_wirelessNetworkDevices->removeNetworkDevice(interface); + + } else if (notification == "NetworkManager.WirelessNetworkDeviceChanged") { QVariantMap deviceMap = params.value("params").toMap().value("wirelessNetworkDevice").toMap(); WirelessNetworkDevice* device = m_wirelessNetworkDevices->getWirelessNetworkDevice(deviceMap.value("interface").toString()); if (!device) { @@ -335,16 +418,33 @@ void NetworkManager::notificationReceived(const QVariantMap ¶ms) device->currentAccessPoint()->setMacAddress(currentApMap.value("macAddress").toString()); device->currentAccessPoint()->setProtected(currentApMap.value("protected").toBool()); device->currentAccessPoint()->setSignalStrength(currentApMap.value("signalStrength").toInt()); + + } else if (notification == "NetworkManager.WiredNetworkDeviceAdded") { + QVariantMap deviceMap = params.value("params").toMap().value("wiredNetworkDevice").toMap(); + WiredNetworkDevice *device = new WiredNetworkDevice(deviceMap.value("macAddress").toString(), deviceMap.value("interface").toString(), this); + device->setIpv4Addresses(deviceMap.value("ipv4Addresses").toStringList()); + device->setIpv6Addresses(deviceMap.value("ipv6Addresses").toStringList()); + device->setBitRate(deviceMap.value("bitRate").toString()); + device->setPluggedIn(deviceMap.value("pluggedIn").toBool()); + QMetaEnum stateEnum = QMetaEnum::fromType(); + device->setState(static_cast(stateEnum.keyToValue(deviceMap.value("state").toString().toUtf8()))); + m_wiredNetworkDevices->addWiredNetworkDevice(device); + + } else if (notification == "NetworkManager.WiredNetworkDeviceRemoved") { + QString interface = params.value("params").toMap().value("interface").toString(); + m_wiredNetworkDevices->removeNetworkDevice(interface); + } else if (notification == "NetworkManager.WiredNetworkDeviceChanged") { QVariantMap deviceMap = params.value("params").toMap().value("wiredNetworkDevice").toMap(); WiredNetworkDevice* device = m_wiredNetworkDevices->getWiredNetworkDevice(deviceMap.value("interface").toString()); if (!device) { - qWarning() << "Received a notification for a network device we don't know" << deviceMap; + qCWarning(dcNetworkManagement) << "Received a notification for a network device we don't know" << deviceMap; return; } - device->setBitRate(deviceMap.value("bitRate").toString()); device->setIpv4Addresses(deviceMap.value("ipv4Addresses").toStringList()); device->setIpv6Addresses(deviceMap.value("ipv6Addresses").toStringList()); + device->setBitRate(deviceMap.value("bitRate").toString()); + device->setPluggedIn(deviceMap.value("pluggedIn").toBool()); QMetaEnum stateEnum = QMetaEnum::fromType(); device->setState(static_cast(stateEnum.keyToValue(deviceMap.value("state").toString().toUtf8()))); } else if (notification == "NetworkManager.NetworkStatusChanged") { @@ -366,6 +466,6 @@ void NetworkManager::notificationReceived(const QVariantMap ¶ms) } } else { - qDebug() << "notification received" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson(QJsonDocument::Indented)); + qCWarning(dcNetworkManagement) << "Unhandled notification received" << qUtf8Printable(QJsonDocument::fromVariant(params).toJson(QJsonDocument::Indented)); } } diff --git a/libnymea-app/configuration/networkmanager.h b/libnymea-app/configuration/networkmanager.h index 29e9661b..1e77d202 100644 --- a/libnymea-app/configuration/networkmanager.h +++ b/libnymea-app/configuration/networkmanager.h @@ -90,6 +90,9 @@ public: Q_INVOKABLE int connectToWiFi(const QString &interface, const QString &ssid, const QString &passphrase); Q_INVOKABLE int startAccessPoint(const QString &interface, const QString &ssid, const QString &passphrase); + Q_INVOKABLE int createWiredAutoConnection(const QString &interface); + Q_INVOKABLE int createWiredManualConnection(const QString &interface, const QString &ip, quint8 prefix, const QString &gateway, const QString &dns); + Q_INVOKABLE int createWiredSharedConnection(const QString &interface, const QString &ip = QString(), quint8 prefix = 24); Q_INVOKABLE int disconnectInterface(const QString &interface); signals: @@ -105,6 +108,9 @@ signals: void connectToWiFiReply(int id, const QString &status); void disconnectReply(int id, const QString &status); void startAccessPointReply(int id, const QString &status); + void createWiredAutoConnectionReply(int id, const QString &status); + void createWiredManualConnectionReply(int id, const QString &status); + void createWiredSharedConnectionReply(int id, const QString &status); private slots: void init(); @@ -117,6 +123,9 @@ private slots: void enableNetworkingResponse(int commandId, const QVariantMap ¶ms); void enableWirelessNetworkingResponse(int commandId, const QVariantMap ¶ms); void startAccessPointResponse(int commandId, const QVariantMap ¶ms); + void createWiredAutoConnectionResponse(int commandId, const QVariantMap ¶ms); + void createWiredManualConnectionResponse(int commandId, const QVariantMap ¶ms); + void createWiredSharedConnectionResponse(int commandId, const QVariantMap ¶ms); void notificationReceived(const QVariantMap ¶ms); diff --git a/libnymea-app/types/networkdevices.cpp b/libnymea-app/types/networkdevices.cpp index 9ab562a2..5ca3ddda 100644 --- a/libnymea-app/types/networkdevices.cpp +++ b/libnymea-app/types/networkdevices.cpp @@ -151,16 +151,13 @@ QVariant WiredNetworkDevices::data(const QModelIndex &index, int role) const return NetworkDevices::data(index, role); } -void WiredNetworkDevices::addNetworkDevice(NetworkDevice *device) +void WiredNetworkDevices::addWiredNetworkDevice(WiredNetworkDevice *device) { NetworkDevices::addNetworkDevice(device); - WiredNetworkDevice *wiredDev = qobject_cast(device); - if (wiredDev) { - connect(wiredDev, &WiredNetworkDevice::pluggedInChanged, [this, wiredDev](){ - emit dataChanged(index(m_list.indexOf(wiredDev)), index(m_list.indexOf(wiredDev)), {RolePluggedIn}); - emit countChanged(); - }); - } + connect(device, &WiredNetworkDevice::pluggedInChanged, [this, device](){ + emit dataChanged(index(m_list.indexOf(device)), index(m_list.indexOf(device)), {RolePluggedIn}); + emit countChanged(); + }); } QHash WiredNetworkDevices::roleNames() const diff --git a/libnymea-app/types/networkdevices.h b/libnymea-app/types/networkdevices.h index d86fcb80..12cb5d6e 100644 --- a/libnymea-app/types/networkdevices.h +++ b/libnymea-app/types/networkdevices.h @@ -48,7 +48,8 @@ public: RoleBitRate, RoleState, RoleIpv4Addresses, - RoleIpv6Addresses + RoleIpv6Addresses, + RolePluggedIn }; Q_ENUM(Roles) @@ -86,7 +87,7 @@ public: QVariant data(const QModelIndex &index, int role) const override; QHash roleNames() const override; - void addNetworkDevice(NetworkDevice *device) override; + void addWiredNetworkDevice(WiredNetworkDevice *device); Q_INVOKABLE WiredNetworkDevice* getWiredNetworkDevice(const QString &interface); diff --git a/nymea-app/ui/system/NetworkSettingsPage.qml b/nymea-app/ui/system/NetworkSettingsPage.qml index e5b3524a..535d2527 100644 --- a/nymea-app/ui/system/NetworkSettingsPage.qml +++ b/nymea-app/ui/system/NetworkSettingsPage.qml @@ -48,6 +48,9 @@ SettingsPageBase { onConnectToWiFiReply: handleReply(id, status) onStartAccessPointReply: handleReply(id, status) onDisconnectReply: handleReply(id, status) + onCreateWiredAutoConnectionReply: handleReply(id, status) + onCreateWiredManualConnectionReply: handleReply(id, status) + onCreateWiredSharedConnectionReply: handleReply(id, status) function handleReply(id, status) { if (id === d.pendingCallId) { @@ -86,7 +89,7 @@ SettingsPageBase { } var component = Qt.createComponent(Qt.resolvedUrl("../components/ErrorDialog.qml")) - var popup = component.createObject(root, {text: errorMessage, errorCode: stats}) + var popup = component.createObject(root, {text: errorMessage, errorCode: status}) } } @@ -154,7 +157,7 @@ SettingsPageBase { visible: networkManager.available } - NymeaSwipeDelegate { + NymeaItemDelegate { Layout.fillWidth: true text: qsTr("Current connection state") prominentSubText: false @@ -203,7 +206,7 @@ SettingsPageBase { } } - NymeaSwipeDelegate { + NymeaItemDelegate { Layout.fillWidth: true text: qsTr("Networking enabled") subText: qsTr("Enable or disable networking altogether") @@ -257,7 +260,7 @@ SettingsPageBase { Repeater { model: networkManager.wiredNetworkDevices - NymeaSwipeDelegate { + NymeaItemDelegate { Layout.fillWidth: true iconName: model.pluggedIn ? "../images/connections/network-wired.svg" : "../images/connections/network-wired-offline.svg" text: model.interface + " (" + model.macAddress + ")" @@ -268,7 +271,19 @@ SettingsPageBase { ret += networkStateToString(model.state) return ret; } - progressive: false + progressive: engine.jsonRpcClient.ensureServerVersion("6.2") + onClicked: { + if (!engine.jsonRpcClient.ensureServerVersion("6.2")) { + return; + } + + var wiredNetworkDevice = networkManager.wiredNetworkDevices.getWiredNetworkDevice(model.interface); + if (wiredNetworkDevice.state === NetworkDevice.NetworkDeviceStateDisconnected) { + pageStack.push(createWiredConnectionPageComponent, {wiredNetworkDevice: wiredNetworkDevice}) + } else { + pageStack.push(currentEthernetConnectionPageComponent, {wiredNetworkDevice: wiredNetworkDevice}) + } + } } } @@ -277,7 +292,7 @@ SettingsPageBase { visible: networkManager.available && networkManager.networkingEnabled } - NymeaSwipeDelegate { + NymeaItemDelegate { Layout.fillWidth: true text: qsTr("Enabled") subText: qsTr("Enable or disable WiFi") @@ -464,6 +479,138 @@ SettingsPageBase { } } + Component { + id: createWiredConnectionPageComponent + SettingsPageBase { + id: createWiredConnectionPage + title: qsTr("New wired connection") + + property WiredNetworkDevice wiredNetworkDevice: null + + SettingsPageSectionHeader { + text: qsTr("Method") + } + + ColumnLayout { + Layout.fillWidth: true + Layout.leftMargin: app.margins + Layout.rightMargin: app.margins + spacing: 0 + + RadioButton { + id: dhcpClientRadioButton + Layout.fillWidth: true + checked: true + text: qsTr("Automatic (DHCP client)") + } + RadioButton { + id: manualClientRadioButton + Layout.fillWidth: true + text: qsTr("Manual") + } + RadioButton { + id: dhcpServerRadioButton + Layout.fillWidth: true + text: qsTr("Shared (DHCP server)") + } + } + + SettingsPageSectionHeader { + text: qsTr("Address settings") + visible: manualClientRadioButton.checked + } + + GridLayout { + Layout.fillWidth: true + Layout.leftMargin: app.margins + Layout.rightMargin: app.margins + columns: 2 + visible: manualClientRadioButton.checked + + + Label { + text: qsTr("IP Address") + } + + RowLayout { + TextField { + id: ipTextField + maximumLength: 32 + Layout.fillWidth: true + horizontalAlignment: Text.AlignRight + validator: RegExpValidator { + regExp: /^((?:[0-1]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.){0,3}(?:[0-1]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])$/ + } + } + + Label { + text: "/" + } + TextField { + id: prefixTextField + text: "24" + Layout.fillWidth: false + validator: IntValidator { + bottom: 8 + top: 32 + } + } + } + + Label { + text: qsTr("Gateway") + } + + TextField { + id: defaultGwTextField + maximumLength: 32 + Layout.fillWidth: true + validator: RegExpValidator { + regExp: /^((?:[0-1]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.){0,3}(?:[0-1]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])$/ + } + } + + Label { + text: qsTr("DNS") + } + + TextField { + id: dnsTextField + maximumLength: 32 + Layout.fillWidth: true + validator: RegExpValidator { + regExp: /^((?:[0-1]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])\.){0,3}(?:[0-1]?[0-9]?[0-9]|2[0-4][0-9]|25[0-5])$/ + } + } + } + + Button { + Layout.fillWidth: true + Layout.margins: app.margins + Layout.leftMargin: app.margins + Layout.rightMargin: app.margins + text: qsTr("Create connection") + enabled: { + if (dhcpClientRadioButton.checked || dhcpServerRadioButton.checked) { + return true; + } + return ipTextField.acceptableInput && prefixTextField.acceptableInput + } + + onClicked: { + if (dhcpClientRadioButton.checked) { + d.pendingCallId = networkManager.createWiredAutoConnection(createWiredConnectionPage.wiredNetworkDevice.interface) + } else if (manualClientRadioButton.checked) { + d.pendingCallId = networkManager.createWiredManualConnection(createWiredConnectionPage.wiredNetworkDevice.interface, ipTextField.text, prefixTextField.text, defaultGwTextField.text, dnsTextField.text) + } else if (dhcpServerRadioButton.checked) { + d.pendingCallId = networkManager.createWiredSharedConnection(createWiredConnectionPage.wiredNetworkDevice.interface) + } + + pageStack.pop(root); + } + } + } + } Component { id: authPageComponent @@ -515,6 +662,50 @@ SettingsPageBase { } } + Component { + id: currentEthernetConnectionPageComponent + SettingsPageBase { + id: currentEthernetConnectionPage + title: qsTr("Current connection") + + property WiredNetworkDevice wiredNetworkDevice: null + + SettingsPageSectionHeader { + text: qsTr("Connected to") + } + + NymeaItemDelegate { + Layout.fillWidth: true + text: qsTr("IPv4 Address") + subText: currentEthernetConnectionPage.wiredNetworkDevice.ipv4Addresses.join(", ") + progressive: false + } + NymeaItemDelegate { + Layout.fillWidth: true + text: qsTr("IPv6 Address") + subText: currentEthernetConnectionPage.wiredNetworkDevice.ipv6Addresses.join(", ") + visible: subText.length > 0 + progressive: false + } + NymeaItemDelegate { + Layout.fillWidth: true + text: qsTr("MAC Address") + subText: currentEthernetConnectionPage.wiredNetworkDevice.macAddress + progressive: false + } + + Button { + Layout.fillWidth: true + Layout.margins: app.margins + text: qsTr("Disconnect") + onClicked: { + d.pendingCallId = networkManager.disconnectInterface(currentEthernetConnectionPage.wiredNetworkDevice.interface) + pageStack.pop(root); + } + } + } + } + Component { id: currentApPageComponent SettingsPageBase {