feat: waku2 store

- add toggle to enable store functionality
- output messages to csv
- add custom waku2 nodes
This commit is contained in:
Richard Ramos 2022-08-02 14:37:27 -04:00 committed by Jonathan Rainville
parent 810a6bb5f5
commit fb526840a4
26 changed files with 648 additions and 91 deletions

View File

@ -64,7 +64,7 @@ proc getMailservers*(self: FleetConfiguration, fleet: Fleet, isWakuV2: bool): Ta
# TODO: If using wakuV2, this assumes that Waku nodes in fleet.status.json are also store nodes. # TODO: If using wakuV2, this assumes that Waku nodes in fleet.status.json are also store nodes.
# Maybe it make senses to add a "waku-store" section in case we want to have separate node types? # Maybe it make senses to add a "waku-store" section in case we want to have separate node types?
# Discuss with @iurimatias, @cammellos and Vac team # Discuss with @iurimatias, @cammellos and Vac team
let fleetKey = if isWakuV2: $FleetNodes.Waku else: $FleetNodes.Mailservers let fleetKey = if isWakuV2: $FleetNodes.TCP_P2P_Waku else: $FleetNodes.Mailservers
if not self.fleet[$fleet].hasKey(fleetKey) : if not self.fleet[$fleet].hasKey(fleetKey) :
result = initTable[string,string]() result = initTable[string,string]()
return return

View File

@ -58,6 +58,18 @@ proc setBloomLevel*(self: Controller, bloomLevel: string) =
self.delegate.onBloomLevelSet() self.delegate.onBloomLevelSet()
method toggleWakuV2Store*(self: Controller) =
let enabled = self.nodeConfigurationService.isWakuV2StoreEnabled()
if (not self.nodeConfigurationService.setWakuV2StoreEnabled(not enabled)):
# in the future we may do a call from here to show a popup about this error
error "an error occurred, we couldn't enable community history archive support"
return
self.delegate.onWakuV2StoreToggled()
method isWakuV2StoreEnabled*(self: Controller): bool =
return self.nodeConfigurationService.isWakuV2StoreEnabled()
proc getWakuV2LightClientEnabled*(self: Controller): bool = proc getWakuV2LightClientEnabled*(self: Controller): bool =
return self.nodeConfigurationService.getV2LightMode() return self.nodeConfigurationService.getV2LightMode()

View File

@ -21,6 +21,9 @@ method onFleetSet*(self: AccessInterface) {.base.} =
method onBloomLevelSet*(self: AccessInterface) {.base.} = method onBloomLevelSet*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method onWakuV2StoreToggled*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method onWakuV2LightClientSet*(self: AccessInterface) {.base.} = method onWakuV2LightClientSet*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
@ -92,3 +95,9 @@ method toggleNodeManagementSection*(self: AccessInterface) {.base.} =
method enableDeveloperFeatures*(self: AccessInterface) {.base.} = method enableDeveloperFeatures*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method isWakuV2StoreEnabled*(self: AccessInterface): bool {.base.} =
raise newException(ValueError, "No implementation available")
method toggleWakuV2Store*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -130,3 +130,12 @@ method toggleCommunitiesPortalSection*(self: Module) =
method toggleNodeManagementSection*(self: Module) = method toggleNodeManagementSection*(self: Module) =
self.controller.toggleNodeManagementSection() self.controller.toggleNodeManagementSection()
method onWakuV2StoreToggled*(self: Module) =
self.view.emitWakuV2StoreEnabledSignal()
method toggleWakuV2Store*(self: Module) =
self.controller.toggleWakuV2Store()
method isWakuV2StoreEnabled*(self: Module): bool =
self.controller.isWakuV2StoreEnabled()

View File

@ -119,3 +119,16 @@ QtObject:
proc toggleCommunitiesPortalSection*(self: View) {.slot.} = proc toggleCommunitiesPortalSection*(self: View) {.slot.} =
self.delegate.toggleCommunitiesPortalSection() self.delegate.toggleCommunitiesPortalSection()
proc isWakuV2StoreEnabledChanged*(self: View) {.signal.}
proc getIsWakuV2StoreEnabled*(self: View): bool {.slot.} =
return self.delegate.isWakuV2StoreEnabled()
QtProperty[bool] isWakuV2StoreEnabled:
read = getIsWakuV2StoreEnabled
notify = isWakuV2StoreEnabledChanged
proc emitWakuV2StoreEnabledSignal*(self: View) =
self.isWakuV2StoreEnabledChanged()
proc toggleWakuV2Store*(self: View) {.slot.} =
self.delegate.toggleWakuV2Store()

View File

@ -65,9 +65,15 @@ method getDevicesModule*(self: AccessInterface): QVariant {.base.} =
method syncModuleDidLoad*(self: AccessInterface) {.base.} = method syncModuleDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method wakuModuleDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method getSyncModule*(self: AccessInterface): QVariant {.base.} = method getSyncModule*(self: AccessInterface): QVariant {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")
method getWakuModule*(self: AccessInterface): QVariant {.base.} =
raise newException(ValueError, "No implementation available")
method notificationsModuleDidLoad*(self: AccessInterface) {.base.} = method notificationsModuleDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available") raise newException(ValueError, "No implementation available")

View File

@ -32,6 +32,7 @@ import ./about/module as about_module
import ./advanced/module as advanced_module import ./advanced/module as advanced_module
import ./devices/module as devices_module import ./devices/module as devices_module
import ./sync/module as sync_module import ./sync/module as sync_module
import ./waku/module as waku_module
import ./notifications/module as notifications_module import ./notifications/module as notifications_module
import ./ens_usernames/module as ens_usernames_module import ./ens_usernames/module as ens_usernames_module
import ./communities/module as communities_module import ./communities/module as communities_module
@ -55,6 +56,7 @@ type
advancedModule: advanced_module.AccessInterface advancedModule: advanced_module.AccessInterface
devicesModule: devices_module.AccessInterface devicesModule: devices_module.AccessInterface
syncModule: sync_module.AccessInterface syncModule: sync_module.AccessInterface
wakuModule: waku_module.AccessInterface
notificationsModule: notifications_module.AccessInterface notificationsModule: notifications_module.AccessInterface
ensUsernamesModule: ens_usernames_module.AccessInterface ensUsernamesModule: ens_usernames_module.AccessInterface
communitiesModule: communities_module.AccessInterface communitiesModule: communities_module.AccessInterface
@ -98,6 +100,7 @@ proc newModule*(delegate: delegate_interface.AccessInterface,
result.advancedModule = advanced_module.newModule(result, events, settingsService, stickersService, nodeConfigurationService) result.advancedModule = advanced_module.newModule(result, events, settingsService, stickersService, nodeConfigurationService)
result.devicesModule = devices_module.newModule(result, events, settingsService, devicesService) result.devicesModule = devices_module.newModule(result, events, settingsService, devicesService)
result.syncModule = sync_module.newModule(result, events, settingsService, nodeConfigurationService, mailserversService) result.syncModule = sync_module.newModule(result, events, settingsService, nodeConfigurationService, mailserversService)
result.wakuModule = waku_module.newModule(result, events, settingsService, nodeConfigurationService)
result.notificationsModule = notifications_module.newModule(result, events, settingsService, chatService, contactsService) result.notificationsModule = notifications_module.newModule(result, events, settingsService, chatService, contactsService)
result.ensUsernamesModule = ens_usernames_module.newModule( result.ensUsernamesModule = ens_usernames_module.newModule(
result, events, settingsService, ensService, walletAccountService, networkService, tokenService result, events, settingsService, ensService, walletAccountService, networkService, tokenService
@ -117,6 +120,7 @@ method delete*(self: Module) =
self.advancedModule.delete self.advancedModule.delete
self.devicesModule.delete self.devicesModule.delete
self.syncModule.delete self.syncModule.delete
self.wakuModule.delete
self.communitiesModule.delete self.communitiesModule.delete
self.keycardModule.delete self.keycardModule.delete
@ -134,6 +138,7 @@ method load*(self: Module) =
self.advancedModule.load() self.advancedModule.load()
self.devicesModule.load() self.devicesModule.load()
self.syncModule.load() self.syncModule.load()
self.wakuModule.load()
self.notificationsModule.load() self.notificationsModule.load()
self.ensUsernamesModule.load() self.ensUsernamesModule.load()
self.communitiesModule.load() self.communitiesModule.load()
@ -167,6 +172,9 @@ proc checkIfModuleDidLoad(self: Module) =
if(not self.syncModule.isLoaded()): if(not self.syncModule.isLoaded()):
return return
if(not self.wakuModule.isLoaded()):
return
if(not self.notificationsModule.isLoaded()): if(not self.notificationsModule.isLoaded()):
return return
@ -227,9 +235,15 @@ method getDevicesModule*(self: Module): QVariant =
method syncModuleDidLoad*(self: Module) = method syncModuleDidLoad*(self: Module) =
self.checkIfModuleDidLoad() self.checkIfModuleDidLoad()
method wakuModuleDidLoad*(self: Module) =
self.checkIfModuleDidLoad()
method getSyncModule*(self: Module): QVariant = method getSyncModule*(self: Module): QVariant =
self.syncModule.getModuleAsVariant() self.syncModule.getModuleAsVariant()
method getWakuModule*(self: Module): QVariant =
self.wakuModule.getModuleAsVariant()
method notificationsModuleDidLoad*(self: Module) = method notificationsModuleDidLoad*(self: Module) =
self.checkIfModuleDidLoad() self.checkIfModuleDidLoad()

View File

@ -51,6 +51,11 @@ QtObject:
QtProperty[QVariant] syncModule: QtProperty[QVariant] syncModule:
read = getSyncModule read = getSyncModule
proc getWakuModule(self: View): QVariant {.slot.} =
return self.delegate.getWakuModule()
QtProperty[QVariant] wakuModule:
read = getWakuModule
proc getNotificationsModule(self: View): QVariant {.slot.} = proc getNotificationsModule(self: View): QVariant {.slot.} =
return self.delegate.getNotificationsModule() return self.delegate.getNotificationsModule()
QtProperty[QVariant] notificationsModule: QtProperty[QVariant] notificationsModule:

View File

@ -0,0 +1,39 @@
import Tables, chronicles
import io_interface
import ../../../../core/eventemitter
import ../../../../core/fleets/fleet_configuration
import ../../../../../app_service/service/settings/service as settings_service
import ../../../../../app_service/service/node_configuration/service as node_configuration_service
logScope:
topics = "profile-section-waku-module-controller"
type
Controller* = ref object of RootObj
delegate: io_interface.AccessInterface
events: EventEmitter
settingsService: settings_service.Service
nodeConfigurationService: node_configuration_service.Service
proc newController*(delegate: io_interface.AccessInterface,
events: EventEmitter,
settingsService: settings_service.Service,
nodeConfigurationService: node_configuration_service.Service): Controller =
result = Controller()
result.delegate = delegate
result.events = events
result.settingsService = settingsService
result.nodeConfigurationService = nodeConfigurationService
proc delete*(self: Controller) =
discard
proc init*(self: Controller) =
discard
proc getAllWakuNodes*(self: Controller): seq[string] =
return self.nodeConfigurationService.getAllWakuNodes()
proc saveNewWakuNode*(self: Controller, nodeAddress: string) =
self.nodeConfigurationService.saveNewWakuNode(nodeAddress)

View File

@ -0,0 +1,25 @@
import NimQml
type
AccessInterface* {.pure inheritable.} = ref object of RootObj
method delete*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method load*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method isLoaded*(self: AccessInterface): bool {.base.} =
raise newException(ValueError, "No implementation available")
method getModuleAsVariant*(self: AccessInterface): QVariant {.base.} =
raise newException(ValueError, "No implementation available")
method viewDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method isAutomaticSelection*(self: AccessInterface): bool {.base.} =
raise newException(ValueError, "No implementation available")
method saveNewWakuNode*(self: AccessInterface, nodeAddress: string) {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -0,0 +1,11 @@
type
Item* = ref object
nodeAddress: string
proc initItem*(nodeAddress: string): Item =
result = Item()
result.nodeAddress = nodeAddress
proc nodeAddress*(self: Item): string =
self.nodeAddress

View File

@ -0,0 +1,62 @@
import NimQml, Tables
import item
type
ModelRole {.pure.} = enum
NodeAddress = UserRole + 1
QtObject:
type Model* = ref object of QAbstractListModel
items*: seq[Item]
proc setup(self: Model) =
self.QAbstractListModel.setup
proc delete(self: Model) =
self.items = @[]
self.QAbstractListModel.delete
proc newModel*(): Model =
new(result, delete)
result.setup
method rowCount(self: Model, index: QModelIndex = nil): int =
return self.items.len
method roleNames(self: Model): Table[int, string] =
{
ModelRole.NodeAddress.int:"nodeAddress"
}.toTable
method data(self: Model, index: QModelIndex, role: int): QVariant =
if not index.isValid:
return
if index.row < 0 or index.row >= self.items.len:
return
let item = self.items[index.row]
let enumRole = role.ModelRole
case enumRole:
of ModelRole.NodeAddress:
result = newQVariant(item.nodeAddress)
proc addItem*(self: Model, item: Item) =
let parentModelIndex = newQModelIndex()
defer: parentModelIndex.delete
self.beginInsertRows(parentModelIndex, self.items.len, self.items.len)
self.items.add(item)
self.endInsertRows()
proc addItems*(self: Model, items: seq[Item]) =
if(items.len == 0):
return
let parentModelIndex = newQModelIndex()
defer: parentModelIndex.delete
let first = self.items.len
let last = first + items.len - 1
self.beginInsertRows(parentModelIndex, first, last)
self.items.add(items)
self.endInsertRows()

View File

@ -0,0 +1,66 @@
import NimQml, chronicles
import io_interface
import ../io_interface as delegate_interface
import view, controller, model, item
import ../../../../core/eventemitter
import ../../../../../app_service/service/settings/service as settings_service
import ../../../../../app_service/service/node_configuration/service as node_configuration_service
export io_interface
logScope:
topics = "profile-section-waku-module"
type
Module* = ref object of io_interface.AccessInterface
delegate: delegate_interface.AccessInterface
controller: Controller
view: View
viewVariant: QVariant
moduleLoaded: bool
proc newModule*(delegate: delegate_interface.AccessInterface,
events: EventEmitter,
settingsService: settings_service.Service,
nodeConfigurationService: node_configuration_service.Service): Module =
result = Module()
result.delegate = delegate
result.view = view.newView(result)
result.viewVariant = newQVariant(result.view)
result.controller = controller.newController(result, events, settingsService, nodeConfigurationService)
result.moduleLoaded = false
method delete*(self: Module) =
self.view.delete
self.viewVariant.delete
self.controller.delete
method load*(self: Module) =
self.controller.init()
self.view.load()
method isLoaded*(self: Module): bool =
return self.moduleLoaded
proc initModel(self: Module) =
var items: seq[Item]
let allWakuNodes = self.controller.getAllWakuNodes()
for w in allWakuNodes:
let item = initItem(w)
items.add(item)
self.view.model().addItems(items)
method viewDidLoad*(self: Module) =
self.initModel()
self.moduleLoaded = true
self.delegate.wakuModuleDidLoad()
method getModuleAsVariant*(self: Module): QVariant =
return self.viewVariant
method saveNewWakuNode*(self: Module, nodeAddress: string) =
self.controller.saveNewWakuNode(nodeAddress)
let item = initItem(nodeAddress)
self.view.model().addItem(item)

View File

@ -0,0 +1,39 @@
import NimQml
import io_interface, model
QtObject:
type
View* = ref object of QObject
delegate: io_interface.AccessInterface
activeMailserver: string
model: Model
modelVariant: QVariant
proc delete*(self: View) =
self.model.delete
self.modelVariant.delete
self.QObject.delete
proc newView*(delegate: io_interface.AccessInterface): View =
new(result, delete)
result.QObject.setup
result.delegate = delegate
result.activeMailserver = ""
result.model = newModel()
result.modelVariant = newQVariant(result.model)
proc load*(self: View) =
self.delegate.viewDidLoad()
proc model*(self: View): Model =
return self.model
proc modelChanged*(self: View) {.signal.}
proc getModel(self: View): QVariant {.slot.} =
return self.modelVariant
QtProperty[QVariant] model:
read = getModel
notify = modelChanged
proc saveNewWakuNode(self: View, address: string) {.slot.} =
self.delegate.saveNewWakuNode(address)

View File

@ -100,6 +100,9 @@ type
EnableDiscV5*: bool EnableDiscV5*: bool
UDPPort*: int UDPPort*: int
AutoUpdate*: bool AutoUpdate*: bool
EnableStore*: bool
StoreCapacity*: int
StoreSeconds*: int
ShhextConfig* = object ShhextConfig* = object
PFSEnabled*: bool PFSEnabled*: bool
@ -322,6 +325,9 @@ proc toWaku2Config*(jsonObj: JsonNode): Waku2Config =
discard jsonObj.getProp("EnableDiscV5", result.EnableDiscV5) discard jsonObj.getProp("EnableDiscV5", result.EnableDiscV5)
discard jsonObj.getProp("UDPPort", result.UDPPort) discard jsonObj.getProp("UDPPort", result.UDPPort)
discard jsonObj.getProp("AutoUpdate", result.AutoUpdate) discard jsonObj.getProp("AutoUpdate", result.AutoUpdate)
discard jsonObj.getProp("EnableStore", result.EnableStore)
discard jsonObj.getProp("StoreCapacity", result.StoreCapacity)
discard jsonObj.getProp("StoreSeconds", result.StoreSeconds)
proc toWakuConfig*(jsonObj: JsonNode): WakuConfig = proc toWakuConfig*(jsonObj: JsonNode): WakuConfig =
discard jsonObj.getProp("Enabled", result.Enabled) discard jsonObj.getProp("Enabled", result.Enabled)

View File

@ -33,6 +33,7 @@ type
configuration: NodeConfigDto configuration: NodeConfigDto
fleetConfiguration: FleetConfiguration fleetConfiguration: FleetConfiguration
settingsService: settings_service.Service settingsService: settings_service.Service
wakuNodes: seq[string]
events: EventEmitter events: EventEmitter
# Forward declarations # Forward declarations
@ -48,6 +49,7 @@ proc newService*(fleetConfiguration: FleetConfiguration, settingsService: settin
result.fleetConfiguration = fleetConfiguration result.fleetConfiguration = fleetConfiguration
result.settingsService = settingsService result.settingsService = settingsService
result.events = events result.events = events
result.wakuNodes = @[]
proc adaptNodeSettingsForTheAppNeed(self: Service) = proc adaptNodeSettingsForTheAppNeed(self: Service) =
self.configuration.DataDir = "./ethereum" self.configuration.DataDir = "./ethereum"
@ -68,6 +70,10 @@ proc init*(self: Service) =
let response = status_node_config.getNodeConfig() let response = status_node_config.getNodeConfig()
self.configuration = response.result.toNodeConfigDto() self.configuration = response.result.toNodeConfigDto()
let wakuNodes = self.configuration.ClusterConfig.WakuNodes
for nodeAddress in wakuNodes:
self.wakuNodes.add(nodeAddress)
self.adaptNodeSettingsForTheAppNeed() self.adaptNodeSettingsForTheAppNeed()
except Exception as e: except Exception as e:
let errDesription = e.msg let errDesription = e.msg
@ -202,6 +208,15 @@ proc getFleet*(self: Service): Fleet =
proc getFleetAsString*(self: Service): string = proc getFleetAsString*(self: Service): string =
result = $self.getFleet() result = $self.getFleet()
proc getAllWakuNodes*(self: Service): seq[string] =
return self.wakuNodes
proc saveNewWakuNode*(self: Service, nodeAddress: string) =
var newConfiguration = self.configuration
newConfiguration.ClusterConfig.WakuNodes.add(nodeAddress)
self.configuration = newConfiguration
discard self.saveConfiguration(newConfiguration)
proc setFleet*(self: Service, fleet: string): bool = proc setFleet*(self: Service, fleet: string): bool =
if(not self.settingsService.saveFleet(fleet)): if(not self.settingsService.saveFleet(fleet)):
error "error saving fleet ", procName="setFleet" error "error saving fleet ", procName="setFleet"
@ -270,3 +285,13 @@ proc isV2LightMode*(self: Service): bool =
proc isFullNode*(self: Service): bool = proc isFullNode*(self: Service): bool =
return self.configuration.WakuConfig.FullNode return self.configuration.WakuConfig.FullNode
method isWakuV2StoreEnabled*(self: Service): bool =
return self.configuration.WakuV2Config.EnableStore
proc setWakuV2StoreEnabled*(self: Service, enabled: bool, storeCapacity: int = 0, storeSeconds: int = 0): bool =
var newConfiguration = self.configuration
newConfiguration.WakuV2Config.EnableStore = enabled
newConfiguration.WakuV2Config.StoreCapacity = storeCapacity
newConfiguration.WakuV2Config.StoreSeconds = storeSeconds
return self.saveConfiguration(newConfiguration)

View File

@ -145,6 +145,8 @@ QtObject {
property var walletSectionTransactionsInst: walletSectionTransactions property var walletSectionTransactionsInst: walletSectionTransactions
property bool isWakuV2StoreEnabled: advancedModule ? advancedModule.isWakuV2StoreEnabled : false
property string communityTags: communitiesModule.tags property string communityTags: communitiesModule.tags
property var stickersModuleInst: stickersModule property var stickersModuleInst: stickersModule

View File

@ -26,8 +26,7 @@ StatusModal {
} }
onOpened: { onOpened: {
nameInput.text = ""; addrInput.text = "";
enodeInput.text = "";
} }
contentItem: StatusScrollView { contentItem: StatusScrollView {
@ -37,29 +36,19 @@ StatusModal {
Column { Column {
id: nodesColumn id: nodesColumn
width: parent.width width: parent.width
StatusInput {
id: nameInput
label: qsTr("Name")
placeholderText: qsTr("Specify a name")
validators: [StatusMinLengthValidator {
minLength: 1
errorMessage: qsTr("You need to enter a name")
}]
validationMode: StatusInput.ValidationMode.Always
}
StatusInput { StatusInput {
id: enodeInput id: addrInput
label: popup.advancedStore.isWakuV2 ? qsTr("Storenode multiaddress") : qsTr("History node address") label: qsTr("Node multiaddress or DNS Discovery address")
placeholderText: popup.advancedStore.isWakuV2 ? "/ip4/0.0.0.0/tcp/123/..." : "enode://{enode-id}:{password}@{ip-address}:{port-number}" placeholderText: "/ipv4/0.0.0.0/tcp/123/..."
validators: [ validators: [
StatusMinLengthValidator { StatusMinLengthValidator {
minLength: 1 minLength: 1
errorMessage: popup.advancedStore.isWakuV2 ? qsTr("You need to enter the storenode multiaddress") : qsTr("You need to enter the enode address") errorMessage: qsTr("You need to enter a value")
}, },
StatusRegularExpressionValidator { StatusRegularExpressionValidator {
errorMessage: popup.advancedStore.isWakuV2 ? qsTr('Multiaddress must start with a "/"') : qsTr("The format must be: enode://{enode-id}:{password}@{ip-address}:{port}") errorMessage: qsTr("Value should start with '/' or 'enr:'")
regularExpression: popup.advancedStore.isWakuV2 ? /\/.+/ : /enode:\/\/[a-z0-9]+:[a-z0-9]+@(\b25[0-5]|\b2[0-4][0-9]|\b[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}:[0-9]+/ regularExpression: /(\/|enr:).+/
}] }]
validationMode: StatusInput.ValidationMode.Always validationMode: StatusInput.ValidationMode.Always
} }
@ -69,10 +58,9 @@ StatusModal {
rightButtons: [ rightButtons: [
StatusButton { StatusButton {
text: qsTr("Save") text: qsTr("Save")
enabled: nameInput.valid && enodeInput.valid enabled: addrInput.valid
// enabled: nameInput.text !== "" && enodeInput.text !== ""
onClicked: { onClicked: {
root.messagingStore.saveNewMailserver(nameInput.text, enodeInput.text) root.messagingStore.saveNewWakuNode(addrInput.text)
popup.close() popup.close()
} }
} }

View File

@ -0,0 +1,80 @@
import QtQuick 2.12
import QtQuick.Controls 2.3
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import StatusQ.Controls.Validators 0.1
import StatusQ.Popups 0.1
import utils 1.0
StatusModal {
id: popup
anchors.centerIn: parent
height: 560
header.title: qsTr("Waku nodes")
property var messagingStore
property var advancedStore
onClosed: {
destroy()
}
onOpened: {
nameInput.text = "";
enodeInput.text = "";
}
contentItem: StatusScrollView {
height: parent.height
width: parent.width
Column {
id: nodesColumn
width: parent.width
StatusInput {
id: nameInput
label: qsTr("Name")
placeholderText: qsTr("Specify a name")
validators: [StatusMinLengthValidator {
minLength: 1
errorMessage: qsTr("You need to enter a name")
}]
validationMode: StatusInput.ValidationMode.Always
}
StatusInput {
id: enodeInput
label: popup.advancedStore.isWakuV2 ? qsTr("Storenode multiaddress") : qsTr("History node address")
placeholderText: popup.advancedStore.isWakuV2 ? "/ip4/0.0.0.0/tcp/123/..." : "enode://{enode-id}:{password}@{ip-address}:{port-number}"
validators: [
StatusMinLengthValidator {
minLength: 1
errorMessage: popup.advancedStore.isWakuV2 ? qsTr("You need to enter the storenode multiaddress") : qsTr("You need to enter the enode address")
},
StatusRegularExpressionValidator {
errorMessage: popup.advancedStore.isWakuV2 ? qsTr('Multiaddress must start with a "/"') : qsTr("The format must be: enode://{enode-id}:{password}@{ip-address}:{port}")
regularExpression: popup.advancedStore.isWakuV2 ? /\/.+/ : /enode:\/\/[a-z0-9]+:[a-z0-9]+@(\b25[0-5]|\b2[0-4][0-9]|\b[01]?[0-9][0-9]?)(\.(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)){3}:[0-9]+/
}]
validationMode: StatusInput.ValidationMode.Always
}
}
}
rightButtons: [
StatusButton {
text: qsTr("Save")
enabled: nameInput.valid && enodeInput.valid
// enabled: nameInput.text !== "" && enodeInput.text !== ""
onClicked: {
root.messagingStore.saveNewMailserver(nameInput.text, enodeInput.text)
popup.close()
}
}
]
}

View File

@ -26,9 +26,7 @@ StatusModal {
property var messagingStore property var messagingStore
property var advancedStore property var advancedStore
property string nameValidationError: ""
property string enodeValidationError: ""
onClosed: { onClosed: {
destroy() destroy()
} }
@ -41,74 +39,16 @@ StatusModal {
id: nodesColumn id: nodesColumn
width: parent.width width: parent.width
StatusListItem {
width: parent.width
title: qsTr("Use Waku nodes")
components: [
StatusSwitch {
checked: root.messagingStore.useMailservers
onCheckedChanged: root.messagingStore.toggleUseMailservers(checked)
}
]
onClicked: {
root.messagingStore.toggleUseMailservers(!root.messagingStore.useMailservers)
}
}
Separator {
width: parent.width
}
StatusListItem {
width: parent.width
title: qsTr("Select node automatically")
components: [
StatusSwitch {
id: automaticSelectionSwitch
checked: root.messagingStore.automaticMailserverSelection
onCheckedChanged: root.messagingStore.enableAutomaticMailserverSelection(checked)
}
]
onClicked: {
automaticSelectionSwitch.checked = !automaticSelectionSwitch.checked
}
}
StatusSectionHeadline {
text: qsTr("Waku Nodes")
visible: !automaticSelectionSwitch.checked
width: parent.width
height: visible ? implicitHeight : 0
}
ButtonGroup {
id: nodesButtonGroup
}
Repeater { Repeater {
id: mailServersListView id: wakunodesListView
model: root.messagingStore.mailservers model: root.messagingStore.wakunodes
delegate: Component { delegate: Component {
StatusListItem { StatusListItem {
title: qsTr("Node %1").arg(index + 1) title: qsTr("Node %1").arg(index + 1)
subTitle: model.name subTitle: model.nodeAddress
visible: !automaticSelectionSwitch.checked
height: visible ? implicitHeight : 0
components: [ components: [
StatusRadioButton { // TODO: add a button to delete nodes and restore default fleet nodes if necessary
id: nodeRadioBtn
ButtonGroup.group: nodesButtonGroup
checked: model.nodeAddress === root.messagingStore.activeMailserver
onCheckedChanged: {
if (checked) {
root.messagingStore.setActiveMailserver(model.name)
}
}
}
] ]
onClicked: {
nodeRadioBtn.checked = true
}
} }
} }
} }

View File

@ -0,0 +1,136 @@
import QtQuick 2.12
import QtQuick.Controls 2.3
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import StatusQ.Controls.Validators 0.1
import StatusQ.Popups 0.1
import "."
import utils 1.0
import shared 1.0
import shared.panels 1.0
import shared.popups 1.0
import shared.status 1.0
import shared.controls 1.0
StatusModal {
id: root
anchors.centerIn: parent
height: 560
header.title: qsTr("History Nodes")
property var messagingStore
property var advancedStore
property string nameValidationError: ""
property string enodeValidationError: ""
onClosed: {
destroy()
}
contentItem: StatusScrollView {
height: parent.height
width: parent.width
Column {
id: nodesColumn
width: parent.width
StatusListItem {
width: parent.width
title: qsTr("Use Waku nodes")
components: [
StatusSwitch {
checked: root.messagingStore.useMailservers
onCheckedChanged: root.messagingStore.toggleUseMailservers(checked)
}
]
onClicked: {
root.messagingStore.toggleUseMailservers(!root.messagingStore.useMailservers)
}
}
Separator {
width: parent.width
}
StatusListItem {
width: parent.width
title: qsTr("Select node automatically")
components: [
StatusSwitch {
id: automaticSelectionSwitch
checked: root.messagingStore.automaticMailserverSelection
onCheckedChanged: root.messagingStore.enableAutomaticMailserverSelection(checked)
}
]
onClicked: {
automaticSelectionSwitch.checked = !automaticSelectionSwitch.checked
}
}
StatusSectionHeadline {
text: qsTr("Waku Nodes")
visible: !automaticSelectionSwitch.checked
width: parent.width
height: visible ? implicitHeight : 0
}
ButtonGroup {
id: nodesButtonGroup
}
Repeater {
id: mailServersListView
model: root.messagingStore.mailservers
delegate: Component {
StatusListItem {
title: qsTr("Node %1").arg(index + 1)
subTitle: model.name
visible: !automaticSelectionSwitch.checked
height: visible ? implicitHeight : 0
components: [
StatusRadioButton {
id: nodeRadioBtn
ButtonGroup.group: nodesButtonGroup
checked: model.nodeAddress === root.messagingStore.activeMailserver
onCheckedChanged: {
if (checked) {
root.messagingStore.setActiveMailserver(model.name)
}
}
}
]
onClicked: {
nodeRadioBtn.checked = true
}
}
}
}
StatusBaseText {
text: qsTr("Add a new node")
color: Theme.palette.primaryColor1
width: parent.width
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: Global.openPopup(wakuNodeModalComponent)
}
}
}
}
Component {
id: wakuNodeModalComponent
AddWakuNodeModal {
messagingStore: root.messagingStore
advancedStore: root.advancedStore
}
}
}

View File

@ -13,6 +13,8 @@ QtObject {
property bool isTelemetryEnabled: advancedModule? advancedModule.isTelemetryEnabled : false property bool isTelemetryEnabled: advancedModule? advancedModule.isTelemetryEnabled : false
property bool isAutoMessageEnabled: advancedModule? advancedModule.isAutoMessageEnabled : false property bool isAutoMessageEnabled: advancedModule? advancedModule.isAutoMessageEnabled : false
property bool isDebugEnabled: advancedModule? advancedModule.isDebugEnabled : false property bool isDebugEnabled: advancedModule? advancedModule.isDebugEnabled : false
property bool isCommunityHistoryArchiveSupportEnabled: advancedModule? advancedModule.isCommunityHistoryArchiveSupportEnabled : false
property bool isWakuV2StoreEnabled: advancedModule ? advancedModule.isWakuV2StoreEnabled : false
property var customNetworksModel: advancedModule? advancedModule.customNetworksModel : [] property var customNetworksModel: advancedModule? advancedModule.customNetworksModel : []
@ -32,6 +34,7 @@ QtObject {
readonly property string communitiesPortal: "communitiesPortal" readonly property string communitiesPortal: "communitiesPortal"
readonly property string communityPermissions: "communityPermissions" readonly property string communityPermissions: "communityPermissions"
readonly property string discordImportTool: "discordImportTool" readonly property string discordImportTool: "discordImportTool"
readonly property string wakuV2StoreEnabled: "wakuV2StoreEnabled"
} }
function logDir() { function logDir() {
@ -113,6 +116,10 @@ QtObject {
else if (feature === experimentalFeatures.communitiesPortal) { else if (feature === experimentalFeatures.communitiesPortal) {
advancedModule.toggleCommunitiesPortalSection() advancedModule.toggleCommunitiesPortalSection()
} }
else if (feature === experimentalFeatures.wakuV2StoreEnabled) {
// toggle history archive support
advancedModule.toggleWakuV2Store()
}
else if (feature === experimentalFeatures.activityCenter) { else if (feature === experimentalFeatures.activityCenter) {
localAccountSensitiveSettings.isActivityCenterEnabled = !localAccountSensitiveSettings.isActivityCenterEnabled localAccountSensitiveSettings.isActivityCenterEnabled = !localAccountSensitiveSettings.isActivityCenterEnabled
} }

View File

@ -6,8 +6,10 @@ QtObject {
property var privacyModule property var privacyModule
property var syncModule property var syncModule
property var wakuModule
property var mailservers: syncModule.model property var mailservers: syncModule.model
property var wakunodes: wakuModule.model
property bool useMailservers: syncModule.useMailservers property bool useMailservers: syncModule.useMailservers
@ -31,6 +33,10 @@ QtObject {
root.syncModule.saveNewMailserver(name, nodeAddress) root.syncModule.saveNewMailserver(name, nodeAddress)
} }
function saveNewWakuNode(nodeAddress) {
root.wakuModule.saveNewWakuNode(nodeAddress)
}
function enableAutomaticMailserverSelection(checked) { function enableAutomaticMailserverSelection(checked) {
if (automaticMailserverSelection === checked) { if (automaticMailserverSelection === checked) {
return return

View File

@ -25,6 +25,7 @@ QtObject {
property MessagingStore messagingStore: MessagingStore { property MessagingStore messagingStore: MessagingStore {
privacyModule: profileSectionModuleInst.privacyModule privacyModule: profileSectionModuleInst.privacyModule
syncModule: profileSectionModuleInst.syncModule syncModule: profileSectionModuleInst.syncModule
wakuModule: profileSectionModuleInst.wakuModule
} }
property DevicesStore devicesStore: DevicesStore { property DevicesStore devicesStore: DevicesStore {

View File

@ -185,6 +185,19 @@ SettingsContentBase {
} }
} }
// TODO: replace with StatusQ component
StatusSettingsLineButton {
anchors.leftMargin: 0
anchors.rightMargin: 0
text: qsTr("WakuV2 Store")
isSwitch: true
visible: root.advancedStore.isWakuV2
switchChecked: root.advancedStore.isWakuV2StoreEnabled
onClicked: {
Global.openPopup(enableWakuV2StoreComponent)
}
}
StatusSectionHeadline { StatusSectionHeadline {
anchors.left: parent.left anchors.left: parent.left
anchors.right: parent.right anchors.right: parent.right
@ -278,7 +291,7 @@ SettingsContentBase {
anchors.right: parent.right anchors.right: parent.right
anchors.leftMargin: Style.current.padding anchors.leftMargin: Style.current.padding
anchors.rightMargin: Style.current.padding anchors.rightMargin: Style.current.padding
visible: root.advancedStore.isWakuV2 visible: root.advancedStore.isWakuV2 && root.advancedStore.fleet != Constants.status_prod
text: qsTr("WakuV2 mode") text: qsTr("WakuV2 mode")
topPadding: Style.current.bigPadding topPadding: Style.current.bigPadding
bottomPadding: Style.current.padding bottomPadding: Style.current.padding
@ -487,6 +500,27 @@ SettingsContentBase {
} }
} }
Component {
id: enableWakuV2StoreComponent
ConfirmationDialog {
property bool mode: false
id: confirmDialog
showCancelButton: true
confirmationText: qsTr("Are you sure you want to %1 WakuV2 Store? You need to restart the app for this change to take effect.")
.arg(root.advancedStore.isWakuV2StoreEnabled ?
qsTr("disable") :
qsTr("enable"))
onConfirmButtonClicked: {
root.advancedStore.toggleExperimentalFeature(root.advancedStore.experimentalFeatures.wakuV2StoreEnabled)
close()
}
onCancelButtonClicked: {
close()
}
}
}
Component { Component {
id: enableDebugComponent id: enableDebugComponent
ConfirmationDialog { ConfirmationDialog {

View File

@ -337,7 +337,7 @@ SettingsContentBase {
StatusListItem { StatusListItem {
Layout.fillWidth: true Layout.fillWidth: true
title: qsTr("Waku nodes") title: qsTr("History nodes")
label: root.messagingStore.getMailserverNameForNodeAddress(root.messagingStore.activeMailserver) label: root.messagingStore.getMailserverNameForNodeAddress(root.messagingStore.activeMailserver)
components: [ components: [
StatusIcon { StatusIcon {
@ -346,11 +346,33 @@ SettingsContentBase {
color: Theme.palette.baseColor1 color: Theme.palette.baseColor1
} }
] ]
onClicked: Global.openPopup(wakuNodeModalComponent) onClicked: Global.openPopup(wakuStoreModalComponent)
} }
Component { Component {
id: wakuNodeModalComponent id: wakuStoreModalComponent
WakuStoreModal {
messagingStore: root.messagingStore
advancedStore: root.advancedStore
}
}
StatusListItem {
Layout.fillWidth: true
title: qsTr("Waku Nodes")
visible: root.advancedStore.isWakuV2
components: [
StatusIcon {
icon: "chevron-down"
rotation: 270
color: Theme.palette.baseColor1
}
]
onClicked: Global.openPopup(wakuNodesModalComponent)
}
Component {
id: wakuNodesModalComponent
WakuNodesModal { WakuNodesModal {
messagingStore: root.messagingStore messagingStore: root.messagingStore
advancedStore: root.advancedStore advancedStore: root.advancedStore