From 6b1cef92358c27e17218ee0ec5d0e90b208d559e Mon Sep 17 00:00:00 2001 From: Richard Ramos Date: Fri, 8 Jan 2021 18:10:31 -0400 Subject: [PATCH] List custom networks and allow selecting them --- src/app/profile/views/custom_networks.nim | 50 +++ src/app/profile/views/network.nim | 14 +- src/app/utilsView/view.nim | 5 + src/status/accounts.nim | 8 +- src/status/libstatus/accounts.nim | 6 +- src/status/network.nim | 2 +- .../Profile/Sections/AdvancedContainer.qml | 2 +- .../Profile/Sections/NetworkRadioSelector.qml | 3 +- .../Profile/Sections/NetworksModal.qml | 403 +++++++++--------- 9 files changed, 291 insertions(+), 202 deletions(-) create mode 100644 src/app/profile/views/custom_networks.nim diff --git a/src/app/profile/views/custom_networks.nim b/src/app/profile/views/custom_networks.nim new file mode 100644 index 0000000000..422cdaec27 --- /dev/null +++ b/src/app/profile/views/custom_networks.nim @@ -0,0 +1,50 @@ +import NimQml +import Tables +import json, sequtils, sugar +import ../../../status/libstatus/settings +import ../../../status/libstatus/types + +type + CustomNetworkRoles {.pure.} = enum + Id = UserRole + 1, + Name = UserRole + 2 + +const defaultNetworks = @["mainnet_rpc", "testnet_rpc", "rinkeby_rpc", "goerli_rpc", "xdai_rpc", "poa_rpc" ] + +QtObject: + type CustomNetworkList* = ref object of QAbstractListModel + + proc setup(self: CustomNetworkList) = self.QAbstractListModel.setup + + proc delete(self: CustomNetworkList) = + self.QAbstractListModel.delete + + proc newCustomNetworkList*(): CustomNetworkList = + new(result, delete) + result.setup + + method rowCount(self: CustomNetworkList, index: QModelIndex = nil): int = + let networks = getSetting[JsonNode](Setting.Networks_Networks) + return networks.getElems().filterIt(it["id"].getStr() notin defaultNetworks).len + + method data(self: CustomNetworkList, index: QModelIndex, role: int): QVariant = + if not index.isValid: + return + + let networks = getSetting[JsonNode](Setting.Networks_Networks).getElems().filterIt(it["id"].getStr() notin defaultNetworks) + if index.row < 0 or index.row >= networks.len: + return + let network = networks[index.row] + case role.CustomNetworkRoles: + of CustomNetworkRoles.Id: result = newQVariant(network["id"].getStr) + of CustomNetworkRoles.Name: result = newQVariant(network["name"].getStr) + + method roleNames(self: CustomNetworkList): Table[int, string] = + { + CustomNetworkRoles.Id.int:"customNetworkId", + CustomNetworkRoles.Name.int:"name", + }.toTable + + proc forceReload*(self: CustomNetworkList) = + self.beginResetModel() + self.endResetModel() diff --git a/src/app/profile/views/network.nim b/src/app/profile/views/network.nim index 7fd338e5ac..0c0b297746 100644 --- a/src/app/profile/views/network.nim +++ b/src/app/profile/views/network.nim @@ -1,6 +1,7 @@ import NimQml, chronicles import ../../../status/status import ../../../status/network +import custom_networks logScope: topics = "network-view" @@ -9,16 +10,19 @@ QtObject: type NetworkView* = ref object of QObject status: Status network: string + customNetworkList*: CustomNetworkList proc setup(self: NetworkView) = self.QObject.setup proc delete*(self: NetworkView) = + self.customNetworkList.delete self.QObject.delete proc newNetworkView*(status: Status): NetworkView = new(result, delete) result.status = status + result.customNetworkList = newCustomNetworkList() result.setup proc networkChanged*(self: NetworkView) {.signal.} @@ -36,7 +40,7 @@ QtObject: proc setNetworkAndPersist*(self: NetworkView, network: string) {.slot.} = self.network = network self.networkChanged() - self.status.accounts.changeNetwork(self.status.fleet.config, network) + self.status.accounts.changeNetwork(self.status.fleet.config, network) ############################### quit(QuitSuccess) # quits the app TODO: change this to logout instead when supported QtProperty[QVariant] current: @@ -47,3 +51,11 @@ QtObject: proc add*(self: NetworkView, name: string, endpoint: string, networkId: int, networkType: string) {.slot.} = self.status.network.addNetwork(name, endpoint, networkId, networkType) + proc getCustomNetworkList(self: NetworkView): QVariant {.slot.} = + return newQVariant(self.customNetworkList) + + QtProperty[QVariant] customNetworkList: + read = getCustomNetworkList + + proc reloadCustomNetworks(self: NetworkView) {.slot.} = + self.customNetworkList.forceReload() \ No newline at end of file diff --git a/src/app/utilsView/view.nim b/src/app/utilsView/view.nim index a169c465fc..3e44401b6b 100644 --- a/src/app/utilsView/view.nim +++ b/src/app/utilsView/view.nim @@ -5,6 +5,8 @@ import ../../status/accounts as status_accounts import ../../status/stickers import ../../status/libstatus/accounts/constants as accountConstants import ../../status/libstatus/tokens +import ../../status/libstatus/types +import ../../status/libstatus/settings import ../../status/libstatus/wallet as status_wallet import ../../status/libstatus/utils as status_utils import ../../status/ens as status_ens @@ -91,3 +93,6 @@ QtObject: proc generateIdenticon*(self: UtilsView, pk: string): string {.slot.} = result = status_accounts.generateIdenticon(pk) + + proc getNetworkName*(self: UtilsView): string {.slot.} = + getCurrentNetworkDetails().name \ No newline at end of file diff --git a/src/status/accounts.nim b/src/status/accounts.nim index 85afe313ab..0565de38a2 100644 --- a/src/status/accounts.nim +++ b/src/status/accounts.nim @@ -1,4 +1,4 @@ -import options, chronicles, json +import options, chronicles, json, json_serialization, sequtils, sugar import libstatus/accounts as status_accounts import libstatus/settings as status_settings import libstatus/types @@ -63,7 +63,11 @@ proc changeNetwork*(self: AccountModel, fleetConfig: FleetConfig, network: strin # 2. update node config setting let installationId = status_settings.getSetting[string](Setting.InstallationId) - let updatedNodeConfig = status_accounts.getNodeConfig(fleetConfig, installationId, network) + + let networks = getSetting[JsonNode](Setting.Networks_Networks) + let networkData = networks.getElems().find((n:JsonNode) => n["id"].getStr() == network) + + let updatedNodeConfig = status_accounts.getNodeConfig(fleetConfig, installationId, networkData) statusGoResult = status_settings.saveSetting(Setting.NodeConfig, updatedNodeConfig) if statusGoResult.error != "": error "Error saving updated node config", msg=statusGoResult.error diff --git a/src/status/libstatus/accounts.nim b/src/status/libstatus/accounts.nim index e125e546ba..fa59095878 100644 --- a/src/status/libstatus/accounts.nim +++ b/src/status/libstatus/accounts.nim @@ -12,8 +12,7 @@ proc getNetworkConfig(currentNetwork: string): JsonNode = result = constants.DEFAULT_NETWORKS.first("id", currentNetwork) -proc getNodeConfig*(fleetConfig: FleetConfig, installationId: string, currentNetwork: string = constants.DEFAULT_NETWORK_NAME, fleet: Fleet = Fleet.PROD): JsonNode = - let networkConfig = getNetworkConfig(currentNetwork) +proc getNodeConfig*(fleetConfig: FleetConfig, installationId: string, networkConfig: JsonNode, fleet: Fleet = Fleet.PROD): JsonNode = let upstreamUrl = networkConfig["config"]["UpstreamConfig"]["URL"] var newDataDir = networkConfig["config"]["DataDir"].getStr newDataDir.removeSuffix("_rpc") @@ -32,6 +31,9 @@ proc getNodeConfig*(fleetConfig: FleetConfig, installationId: string, currentNet result["ShhextConfig"]["InstallationID"] = newJString(installationId) result["ListenAddr"] = if existsEnv("STATUS_PORT"): newJString("0.0.0.0:" & $getEnv("STATUS_PORT")) else: newJString("0.0.0.0:30305") +proc getNodeConfig*(fleetConfig: FleetConfig, installationId: string, currentNetwork: string = constants.DEFAULT_NETWORK_NAME, fleet: Fleet = Fleet.PROD): JsonNode = + let networkConfig = getNetworkConfig(currentNetwork) + result = getNodeConfig(fleetConfig, installationId, networkConfig, fleet) proc hashPassword*(password: string): string = result = "0x" & $keccak_256.digest(password) diff --git a/src/status/network.nim b/src/status/network.nim index fe66ebc07f..c229f1efba 100644 --- a/src/status/network.nim +++ b/src/status/network.nim @@ -51,4 +51,4 @@ proc addNetwork*(self: NetworkModel, name: string, endpoint: string, networkId: } } }) - discard saveSetting(Setting.Networks_Networks, $networks) + discard saveSetting(Setting.Networks_Networks, networks) diff --git a/ui/app/AppLayouts/Profile/Sections/AdvancedContainer.qml b/ui/app/AppLayouts/Profile/Sections/AdvancedContainer.qml index 0175f4d4fd..36d7ad5c01 100644 --- a/ui/app/AppLayouts/Profile/Sections/AdvancedContainer.qml +++ b/ui/app/AppLayouts/Profile/Sections/AdvancedContainer.qml @@ -127,7 +127,7 @@ Item { } StyledText { - text: Utils.getNetworkName(profileModel.network.current) + text: utilsModel.getNetworkName() font.pixelSize: 15 anchors.right: caret3.left anchors.rightMargin: Style.current.padding diff --git a/ui/app/AppLayouts/Profile/Sections/NetworkRadioSelector.qml b/ui/app/AppLayouts/Profile/Sections/NetworkRadioSelector.qml index 1c39b6a2b3..48e27db778 100644 --- a/ui/app/AppLayouts/Profile/Sections/NetworkRadioSelector.qml +++ b/ui/app/AppLayouts/Profile/Sections/NetworkRadioSelector.qml @@ -7,6 +7,7 @@ import "../../../../shared/status" RowLayout { property string network: "" + property string networkName: "" property string newNetwork: "" ConfirmationDialog { @@ -21,7 +22,7 @@ RowLayout { width: parent.width StyledText { - text: Utils.getNetworkName(network) + text: networkName == "" ? Utils.getNetworkName(network) : networkName font.pixelSize: 15 } StatusRadioButton { diff --git a/ui/app/AppLayouts/Profile/Sections/NetworksModal.qml b/ui/app/AppLayouts/Profile/Sections/NetworksModal.qml index 5ab6efb49c..06819574ce 100644 --- a/ui/app/AppLayouts/Profile/Sections/NetworksModal.qml +++ b/ui/app/AppLayouts/Profile/Sections/NetworksModal.qml @@ -11,248 +11,263 @@ ModalPopup { property string newNetwork: ""; - Column { - id: column - spacing: Style.current.padding + ScrollView { + id: svNetworks width: parent.width + height: 300 + clip: true - ButtonGroup { id: networkSettings } + ScrollBar.horizontal.policy: ScrollBar.AlwaysOff + ScrollBar.vertical.policy: ScrollBar.AlwaysOn - Item { - id: addNetwork + Column { + id: column + spacing: Style.current.padding width: parent.width - height: addButton.height - StatusRoundButton { - id: addButton - icon.name: "plusSign" - size: "medium" - anchors.verticalCenter: parent.verticalCenter - } + ButtonGroup { id: networkSettings } - ButtonGroup { - id: networkChainGroup - } + Item { + id: addNetwork + width: parent.width + height: addButton.height - StyledText { - id: usernameText - text: qsTr("Add network") - color: Style.current.blue - anchors.left: addButton.right - anchors.leftMargin: Style.current.padding - anchors.verticalCenter: addButton.verticalCenter - font.pixelSize: 15 - } - - MouseArea { - anchors.fill: parent - cursorShape: Qt.PointingHandCursor - onClicked: addNetworkPopup.open() - } - - ModalPopup { - id: addNetworkPopup - title: qsTr("Add network") - height: 600 - - property string nameValidationError: "" - property string rpcValidationError: "" - property int networkId: 1; - property string networkType: Constants.networkMainnet - - function validate() { - nameValidationError = "" - rpcValidationError = "" - - if (nameInput.text === "") { - nameValidationError = qsTr("You need to enter a name") - } - - if (rpcInput.text === "") { - rpcValidationError = qsTr("You need to enter the RPC endpoint URL") - } else if(!Utils.isURL(rpcInput.text)) { - rpcValidationError = qsTr("Invalid URL") - } - - return !nameValidationError && !rpcValidationError + StatusRoundButton { + id: addButton + icon.name: "plusSign" + size: "medium" + anchors.verticalCenter: parent.verticalCenter } - onOpened: { - nameInput.text = ""; - rpcInput.text = ""; - mainnetRadioBtn.checked = true; - addNetworkPopup.networkId = 1; - addNetworkPopup.networkType = Constants.networkMainnet; - - nameValidationError = ""; - rpcValidationError = ""; + ButtonGroup { + id: networkChainGroup } - footer: StyledButton { - anchors.right: parent.right - anchors.rightMargin: Style.current.smallPadding - label: qsTr("Save") - anchors.bottom: parent.bottom - disabled: nameInput.text == "" || rpcInput.text == "" - onClicked: { - if (!addNetworkPopup.validate()) { - return; - } - profileModel.network.add(nameInput.text, rpcInput.text, addNetworkPopup.networkId, addNetworkPopup.networkType) - addNetworkPopup.close() - } + StyledText { + id: usernameText + text: qsTr("Add network") + color: Style.current.blue + anchors.left: addButton.right + anchors.leftMargin: Style.current.padding + anchors.verticalCenter: addButton.verticalCenter + font.pixelSize: 15 } - Input { - id: nameInput - label: qsTr("Name") - placeholderText: qsTr("Specify a name") - validationError: addNetworkPopup.nameValidationError + MouseArea { + anchors.fill: parent + cursorShape: Qt.PointingHandCursor + onClicked: addNetworkPopup.open() } - Input { - id: rpcInput - label: qsTr("RPC URL") - placeholderText: qsTr("Specify a RPC URL") - validationError: addNetworkPopup.rpcValidationError - anchors.top: nameInput.bottom - anchors.topMargin: Style.current.bigPadding - } + ModalPopup { + id: addNetworkPopup + title: qsTr("Add network") + height: 600 - StatusSectionHeadline { - id: networkChainHeadline - text: qsTr("Network chain") - anchors.top: rpcInput.bottom - anchors.topMargin: Style.current.bigPadding - } + property string nameValidationError: "" + property string rpcValidationError: "" + property int networkId: 1; + property string networkType: Constants.networkMainnet - Column { - spacing: Style.current.padding - anchors.top: networkChainHeadline.bottom - anchors.topMargin: Style.current.smallPadding - anchors.left: parent.left - anchors.right: parent.right + function validate() { + nameValidationError = "" + rpcValidationError = "" - RowLayout { - width: parent.width - StyledText { - text: qsTr("Main network") - font.pixelSize: 15 + if (nameInput.text === "") { + nameValidationError = qsTr("You need to enter a name") } - StatusRadioButton { - id: mainnetRadioBtn - Layout.alignment: Qt.AlignRight - ButtonGroup.group: networkChainGroup - rightPadding: 0 - checked: true - onClicked: { - addNetworkPopup.networkId = 1; - addNetworkPopup.networkType = Constants.networkMainnet; + if (rpcInput.text === "") { + rpcValidationError = qsTr("You need to enter the RPC endpoint URL") + } else if(!Utils.isURL(rpcInput.text)) { + rpcValidationError = qsTr("Invalid URL") + } + + return !nameValidationError && !rpcValidationError + } + + onOpened: { + nameInput.text = ""; + rpcInput.text = ""; + mainnetRadioBtn.checked = true; + addNetworkPopup.networkId = 1; + addNetworkPopup.networkType = Constants.networkMainnet; + + nameValidationError = ""; + rpcValidationError = ""; + } + + footer: StyledButton { + anchors.right: parent.right + anchors.rightMargin: Style.current.smallPadding + label: qsTr("Save") + anchors.bottom: parent.bottom + disabled: nameInput.text == "" || rpcInput.text == "" + onClicked: { + if (!addNetworkPopup.validate()) { + return; + } + profileModel.network.add(nameInput.text, rpcInput.text, addNetworkPopup.networkId, addNetworkPopup.networkType) + profileModel.network.reloadCustomNetworks(); + addNetworkPopup.close() + } + } + + Input { + id: nameInput + label: qsTr("Name") + placeholderText: qsTr("Specify a name") + validationError: addNetworkPopup.nameValidationError + } + + Input { + id: rpcInput + label: qsTr("RPC URL") + placeholderText: qsTr("Specify a RPC URL") + validationError: addNetworkPopup.rpcValidationError + anchors.top: nameInput.bottom + anchors.topMargin: Style.current.bigPadding + } + + StatusSectionHeadline { + id: networkChainHeadline + text: qsTr("Network chain") + anchors.top: rpcInput.bottom + anchors.topMargin: Style.current.bigPadding + } + + Column { + spacing: Style.current.padding + anchors.top: networkChainHeadline.bottom + anchors.topMargin: Style.current.smallPadding + anchors.left: parent.left + anchors.right: parent.right + + RowLayout { + width: parent.width + StyledText { + text: qsTr("Main network") + font.pixelSize: 15 + } + + StatusRadioButton { + id: mainnetRadioBtn + Layout.alignment: Qt.AlignRight + ButtonGroup.group: networkChainGroup + rightPadding: 0 + checked: true + onClicked: { + addNetworkPopup.networkId = 1; + addNetworkPopup.networkType = Constants.networkMainnet; + } } } - } - RowLayout { - width: parent.width - StyledText { - text: qsTr("Ropsten test network") - font.pixelSize: 15 - } - StatusRadioButton { - id: ropstenRadioBtn - Layout.alignment: Qt.AlignRight - ButtonGroup.group: networkChainGroup - rightPadding: 0 - onClicked: { - addNetworkPopup.networkId = 3; - addNetworkPopup.networkType = Constants.networkRopsten; + RowLayout { + width: parent.width + StyledText { + text: qsTr("Ropsten test network") + font.pixelSize: 15 + } + StatusRadioButton { + id: ropstenRadioBtn + Layout.alignment: Qt.AlignRight + ButtonGroup.group: networkChainGroup + rightPadding: 0 + onClicked: { + addNetworkPopup.networkId = 3; + addNetworkPopup.networkType = Constants.networkRopsten; + } } } - } - RowLayout { - width: parent.width - StyledText { - text: qsTr("Rinkeby test network") - font.pixelSize: 15 - } - StatusRadioButton { - id: rinkebyRadioBtn - Layout.alignment: Qt.AlignRight - ButtonGroup.group: networkChainGroup - rightPadding: 0 - onClicked: { - addNetworkPopup.networkId = 4; - addNetworkPopup.networkType = Constants.networkRinkeby; + RowLayout { + width: parent.width + StyledText { + text: qsTr("Rinkeby test network") + font.pixelSize: 15 + } + StatusRadioButton { + id: rinkebyRadioBtn + Layout.alignment: Qt.AlignRight + ButtonGroup.group: networkChainGroup + rightPadding: 0 + onClicked: { + addNetworkPopup.networkId = 4; + addNetworkPopup.networkType = Constants.networkRinkeby; + } } } - } - RowLayout { - width: parent.width - StyledText { - text: qsTr("Custom") - font.pixelSize: 15 - } - StatusRadioButton { - id: customRadioBtn - Layout.alignment: Qt.AlignRight - ButtonGroup.group: networkChainGroup - rightPadding: 0 - onClicked: { - addNetworkPopup.networkId = 55; // TODO - addNetworkPopup.networkType = ""; + RowLayout { + width: parent.width + StyledText { + text: qsTr("Custom") + font.pixelSize: 15 + } + StatusRadioButton { + id: customRadioBtn + Layout.alignment: Qt.AlignRight + ButtonGroup.group: networkChainGroup + rightPadding: 0 + onClicked: { + addNetworkPopup.networkId = 55; // TODO + addNetworkPopup.networkType = ""; + } } } } } } - } - StatusSectionHeadline { - text: qsTr("Main networks") - } + StatusSectionHeadline { + text: qsTr("Main networks") + } - NetworkRadioSelector { - network: Constants.networkMainnet - } + NetworkRadioSelector { + network: Constants.networkMainnet + } - NetworkRadioSelector { - network: Constants.networkPOA - } + NetworkRadioSelector { + network: Constants.networkPOA + } - NetworkRadioSelector { - network: Constants.networkXDai - } + NetworkRadioSelector { + network: Constants.networkXDai + } - StatusSectionHeadline { - text: qsTr("Test networks") - } + StatusSectionHeadline { + text: qsTr("Test networks") + } - NetworkRadioSelector { - network: Constants.networkGoerli - } + NetworkRadioSelector { + network: Constants.networkGoerli + } - NetworkRadioSelector { - network: Constants.networkRinkeby - } + NetworkRadioSelector { + network: Constants.networkRinkeby + } - NetworkRadioSelector { - network: Constants.networkRopsten - } + NetworkRadioSelector { + network: Constants.networkRopsten + } - StatusSectionHeadline { - text: qsTr("Custom Networks") - } + StatusSectionHeadline { + text: qsTr("Custom Networks") + } - NetworkRadioSelector { - network: "SOME NETWORK" + Repeater { + model: profileModel.network.customNetworkList + delegate: NetworkRadioSelector { + networkName: name + network: customNetworkId + } + } } } StyledText { - anchors.top: column.bottom + anchors.top: svNetworks.bottom anchors.topMargin: Style.current.padding //% "Under development\nNOTE: You will be logged out and all installed\nsticker packs will be removed and will\nneed to be reinstalled. Purchased sticker\npacks will not need to be re-purchased." text: qsTrId("under-development-nnote--you-will-be-logged-out-and-all-installed-nsticker-packs-will-be-removed-and-will-nneed-to-be-reinstalled--purchased-sticker-npacks-will-not-need-to-be-re-purchased-")