diff --git a/README.md b/README.md
index b1ccea3..8fe864b 100644
--- a/README.md
+++ b/README.md
@@ -78,6 +78,41 @@ To restart the onboarding process, simply delete the prefences file and relaunch
The application also provides a JSON editor in the debug panel for runtime configuration tweaks. To apply changes, restart the Storage Module.
+## Troubleshooting
+
+### Node has no peers
+
+**Symptom:**
+The node starts successfully but never connects to any peer.
+
+**Cause:**
+Logos Storage uses a discovery port (default `8090`) to announce itself to the DHT and find peers. If this port is already use by another process, the DHT cannot work properly.
+
+**Fix:**
+Ensure that no process is running on `8090` process or change the default port value in the advanced configuration.
+
+### UPnP not working
+
+**Symptom:**
+You selected UPnP during setup but the node remains unreachable.
+
+**Cause:**
+UPnP relies on your router supporting and enabling the UPnP protocol. Many routers have it disabled by default for security reasons.
+
+**Fix:**
+Make sure UPnP is enabled on your router or switch to port forwarding config.
+
+### Manual port forwarding
+
+**Symptom:**
+You configure the port forwarding with a TCP port but the node remains unreachable.
+
+**Cause:**
+The port is not open on your router.
+
+**Fix:**
+Make port forwarding is enabled for this port on your router.
+
#### Nix Organization
The nix build system is organized into modular files in the `/nix` directory:
diff --git a/src/qml/AdvancedSetup.qml b/src/qml/AdvancedSetup.qml
index e53bda8..8e23e22 100644
--- a/src/qml/AdvancedSetup.qml
+++ b/src/qml/AdvancedSetup.qml
@@ -36,9 +36,9 @@ LogosStorageLayout {
Rectangle {
Layout.fillWidth: true
Layout.fillHeight: true
- color: "#1e1e1e"
+ color: Theme.palette.backgroundElevated
radius: 8
- border.color: jsonArea.isValid ? "#3a3a3a" : "#ff0000"
+ border.color: jsonArea.isValid ? Theme.palette.borderSecondary : Theme.palette.error
border.width: 1
ScrollView {
@@ -49,7 +49,7 @@ LogosStorageLayout {
id: jsonArea
font.family: "monospace"
font.pixelSize: 12
- color: "#d4d4d4"
+ color: Theme.palette.text
wrapMode: Text.WrapAnywhere
background: Item {}
@@ -85,29 +85,14 @@ LogosStorageLayout {
onClicked: root.back()
}
- Rectangle {
- width: 120
- height: 36
- radius: 8
- color: jsonArea.isValid ? "#4CAF50" : "#444444"
-
- Text {
- anchors.centerIn: parent
- text: "Validate"
- color: "white"
- font.pixelSize: 14
- font.bold: true
- }
-
- MouseArea {
- anchors.fill: parent
- enabled: jsonArea.isValid
- cursorShape: jsonArea.isValid ? Qt.PointingHandCursor : Qt.ArrowCursor
- onClicked: function () {
- root.backend.saveUserConfig(jsonArea.text)
- root.backend.reloadIfChanged(jsonArea.text)
- root.completed()
- }
+ LogosStorageButton {
+ text: "Validate"
+ variant: "success"
+ enabled: jsonArea.isValid
+ onClicked: {
+ root.backend.saveUserConfig(jsonArea.text)
+ root.backend.reloadIfChanged(jsonArea.text)
+ root.completed()
}
}
}
diff --git a/src/qml/CMakeLists.txt b/src/qml/CMakeLists.txt
index 1e360ef..a96ce58 100644
--- a/src/qml/CMakeLists.txt
+++ b/src/qml/CMakeLists.txt
@@ -138,6 +138,12 @@ qt_add_qml_module(appqml
HealthIndicator.qml
ModeSelector.qml
AdvancedSetup.qml
+ DotIcon.qml
+ NodeStatusIcon.qml
+ GuideIcon.qml
+ AdvancedIcon.qml
+ UpnpIcon.qml
+ PortIcon.qml
)
# Set up QML module directory for runtime
diff --git a/src/qml/HealthIndicator.qml b/src/qml/HealthIndicator.qml
index 74a7aa2..3f18b7c 100644
--- a/src/qml/HealthIndicator.qml
+++ b/src/qml/HealthIndicator.qml
@@ -1,4 +1,5 @@
import QtQuick
+import Logos.Theme
Item {
id: root
@@ -57,14 +58,14 @@ Item {
height: 10
radius: 5
anchors.verticalCenter: parent.verticalCenter
- color: root.nodeIsUp ? "#4CAF50" : "#f44336"
+ color: root.nodeIsUp ? Theme.palette.success : Theme.palette.error
opacity: root.blinkOn ? 1.0 : 0.15
}
Text {
anchors.verticalCenter: parent.verticalCenter
text: root.nodeIsUp ? "Node reachable" : "Node unreachable"
- color: root.nodeIsUp ? "#4CAF50" : "#f44336"
+ color: root.nodeIsUp ? Theme.palette.success : Theme.palette.error
font.pixelSize: 12
}
}
diff --git a/src/qml/LogosStorageButton.qml b/src/qml/LogosStorageButton.qml
index 8b5bfb1..90fcddf 100644
--- a/src/qml/LogosStorageButton.qml
+++ b/src/qml/LogosStorageButton.qml
@@ -6,10 +6,21 @@ Button {
id: control
padding: Theme.spacing.small
+ // "default" | "success"
+ property string variant: "default"
+
+ readonly property bool isSuccess: variant === "success"
+
contentItem: Text {
text: control.text
font.pixelSize: Theme.typography.primaryText
- color: control.enabled ? Theme.palette.text : Theme.palette.textMuted
+ color: {
+ if (!control.enabled)
+ return Theme.palette.textMuted
+ if (control.isSuccess)
+ return Theme.palette.background
+ return Theme.palette.text
+ }
horizontalAlignment: Text.AlignHCenter
verticalAlignment: Text.AlignVCenter
}
@@ -18,10 +29,18 @@ Button {
color: {
if (!control.enabled)
return Theme.palette.backgroundElevated
+ if (control.isSuccess)
+ return Theme.palette.success
return Theme.palette.backgroundSecondary
}
border.width: 1
- border.color: Theme.palette.border
+ border.color: {
+ if (!control.enabled)
+ return Theme.palette.border
+ if (control.isSuccess)
+ return Theme.palette.success
+ return Theme.palette.border
+ }
radius: Theme.spacing.tiny
Behavior on color {
diff --git a/src/qml/ModeSelector.qml b/src/qml/ModeSelector.qml
index ffd14d7..eaac2c9 100644
--- a/src/qml/ModeSelector.qml
+++ b/src/qml/ModeSelector.qml
@@ -8,7 +8,7 @@ LogosStorageLayout {
signal completed(bool isGuide)
- property int selectedMode: -1 // 0 = guide, 1 = advanced
+ property int selectedMode: -1
ColumnLayout {
anchors.centerIn: parent
@@ -30,9 +30,7 @@ LogosStorageLayout {
Layout.fillWidth: true
}
- Item {
- height: Theme.spacing.medium
- }
+ Item { height: Theme.spacing.medium }
Row {
spacing: Theme.spacing.medium
@@ -43,38 +41,22 @@ LogosStorageLayout {
width: 190
height: 230
radius: 14
- color: root.selectedMode === 0 ? Qt.rgba(1, 1, 1,
- 0.08) : "transparent"
- border.color: root.selectedMode === 0 ? "white" : Qt.rgba(1, 1,
- 1,
- 0.2)
+ color: root.selectedMode === 0 ? Theme.palette.overlayLight : "transparent"
+ border.color: root.selectedMode === 0 ? Theme.palette.text : Theme.palette.borderTertiaryMuted
border.width: root.selectedMode === 0 ? 2 : 1
ColumnLayout {
anchors.centerIn: parent
spacing: 14
- // Nothing OS dot icon like
- Grid {
- columns: 5
- spacing: 4
+ GuideIcon {
+ dotColor: Theme.palette.text
Layout.alignment: Qt.AlignHCenter
-
- Repeater {
- model: [0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0]
- Rectangle {
- width: 6
- height: 6
- radius: 2
- color: "white"
- opacity: modelData ? 0.9 : 0.1
- }
- }
}
Text {
text: "Guide"
- color: "white"
+ color: Theme.palette.text
font.pixelSize: 16
font.bold: true
Layout.alignment: Qt.AlignHCenter
@@ -82,7 +64,7 @@ LogosStorageLayout {
Text {
text: "Step-by-step setup.\nRecommended for\nmost users."
- color: Qt.rgba(1, 1, 1, 0.55)
+ color: Theme.palette.textSecondary
font.pixelSize: 12
horizontalAlignment: Text.AlignHCenter
Layout.alignment: Qt.AlignHCenter
@@ -103,38 +85,22 @@ LogosStorageLayout {
width: 190
height: 230
radius: 14
- color: root.selectedMode === 1 ? Qt.rgba(1, 1, 1,
- 0.08) : "transparent"
- border.color: root.selectedMode === 1 ? "white" : Qt.rgba(1, 1,
- 1,
- 0.2)
+ color: root.selectedMode === 1 ? Theme.palette.overlayLight : "transparent"
+ border.color: root.selectedMode === 1 ? Theme.palette.text : Theme.palette.borderTertiaryMuted
border.width: root.selectedMode === 1 ? 2 : 1
ColumnLayout {
anchors.centerIn: parent
spacing: 14
- // Nothing OS dot icon like
- Grid {
- columns: 5
- spacing: 4
+ AdvancedIcon {
+ dotColor: Theme.palette.text
Layout.alignment: Qt.AlignHCenter
-
- Repeater {
- model: [1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1, 0, 0, 0, 1, 0, 1, 0, 1, 0, 0, 0, 1]
- Rectangle {
- width: 6
- height: 6
- radius: 2
- color: "white"
- opacity: modelData ? 0.9 : 0.1
- }
- }
}
Text {
text: "Advanced"
- color: "white"
+ color: Theme.palette.text
font.pixelSize: 16
font.bold: true
Layout.alignment: Qt.AlignHCenter
@@ -142,7 +108,7 @@ LogosStorageLayout {
Text {
text: "Manual JSON\nconfiguration for\nexperienced users."
- color: Qt.rgba(1, 1, 1, 0.55)
+ color: Theme.palette.textSecondary
font.pixelSize: 12
horizontalAlignment: Text.AlignHCenter
Layout.alignment: Qt.AlignHCenter
@@ -159,9 +125,7 @@ LogosStorageLayout {
}
}
- Item {
- height: Theme.spacing.small
- }
+ Item { height: Theme.spacing.small }
LogosStorageButton {
text: "Continue"
diff --git a/src/qml/OnBoarding.qml b/src/qml/OnBoarding.qml
index e3d8488..4843c89 100644
--- a/src/qml/OnBoarding.qml
+++ b/src/qml/OnBoarding.qml
@@ -11,7 +11,7 @@ LogosStorageLayout {
signal back
signal completed(bool upnpEnabled)
- property int selectedMode: -1 // 0 = upnp, 1 = port forwarding
+ property int selectedMode: -1
ColumnLayout {
anchors.centerIn: parent
@@ -33,9 +33,7 @@ LogosStorageLayout {
Layout.fillWidth: true
}
- Item {
- height: Theme.spacing.medium
- }
+ Item { height: Theme.spacing.medium }
Row {
spacing: Theme.spacing.medium
@@ -46,41 +44,22 @@ LogosStorageLayout {
width: 190
height: 230
radius: 14
- color: root.selectedMode === 0 ? Qt.rgba(1, 1, 1, 0.08) : "transparent"
- border.color: root.selectedMode === 0 ? "white" : Qt.rgba(1, 1, 1, 0.2)
+ color: root.selectedMode === 0 ? Theme.palette.overlayLight : "transparent"
+ border.color: root.selectedMode === 0 ? Theme.palette.text : Theme.palette.borderTertiaryMuted
border.width: root.selectedMode === 0 ? 2 : 1
ColumnLayout {
anchors.centerIn: parent
spacing: 14
- // Nothing OS dot icon — diamond/network
- Grid {
- columns: 5
- spacing: 4
+ UpnpIcon {
+ dotColor: Theme.palette.text
Layout.alignment: Qt.AlignHCenter
-
- Repeater {
- model: [
- 0, 0, 1, 0, 0,
- 0, 1, 0, 1, 0,
- 1, 0, 1, 0, 1,
- 0, 1, 0, 1, 0,
- 0, 0, 1, 0, 0
- ]
- Rectangle {
- width: 6
- height: 6
- radius: 2
- color: "white"
- opacity: modelData ? 0.9 : 0.1
- }
- }
}
Text {
text: "UPnP"
- color: "white"
+ color: Theme.palette.text
font.pixelSize: 16
font.bold: true
Layout.alignment: Qt.AlignHCenter
@@ -88,7 +67,7 @@ LogosStorageLayout {
Text {
text: "Automatic port\nforwarding via\nUPnP router."
- color: Qt.rgba(1, 1, 1, 0.55)
+ color: Theme.palette.textSecondary
font.pixelSize: 12
horizontalAlignment: Text.AlignHCenter
Layout.alignment: Qt.AlignHCenter
@@ -109,41 +88,22 @@ LogosStorageLayout {
width: 190
height: 230
radius: 14
- color: root.selectedMode === 1 ? Qt.rgba(1, 1, 1, 0.08) : "transparent"
- border.color: root.selectedMode === 1 ? "white" : Qt.rgba(1, 1, 1, 0.2)
+ color: root.selectedMode === 1 ? Theme.palette.overlayLight : "transparent"
+ border.color: root.selectedMode === 1 ? Theme.palette.text : Theme.palette.borderTertiaryMuted
border.width: root.selectedMode === 1 ? 2 : 1
ColumnLayout {
anchors.centerIn: parent
spacing: 14
- // Nothing OS dot icon — right arrow
- Grid {
- columns: 5
- spacing: 4
+ PortIcon {
+ dotColor: Theme.palette.text
Layout.alignment: Qt.AlignHCenter
-
- Repeater {
- model: [
- 0, 0, 1, 0, 0,
- 0, 0, 0, 1, 0,
- 1, 1, 1, 1, 1,
- 0, 0, 0, 1, 0,
- 0, 0, 1, 0, 0
- ]
- Rectangle {
- width: 6
- height: 6
- radius: 2
- color: "white"
- opacity: modelData ? 0.9 : 0.1
- }
- }
}
Text {
text: "Port Forwarding"
- color: "white"
+ color: Theme.palette.text
font.pixelSize: 16
font.bold: true
Layout.alignment: Qt.AlignHCenter
@@ -151,7 +111,7 @@ LogosStorageLayout {
Text {
text: "Manual TCP port\nconfiguration on\nyour router."
- color: Qt.rgba(1, 1, 1, 0.55)
+ color: Theme.palette.textSecondary
font.pixelSize: 12
horizontalAlignment: Text.AlignHCenter
Layout.alignment: Qt.AlignHCenter
@@ -168,9 +128,7 @@ LogosStorageLayout {
}
}
- Item {
- height: Theme.spacing.small
- }
+ Item { height: Theme.spacing.small }
RowLayout {
Layout.alignment: Qt.AlignHCenter
diff --git a/src/qml/PortForwarding.qml b/src/qml/PortForwarding.qml
index 7698476..26bc48b 100644
--- a/src/qml/PortForwarding.qml
+++ b/src/qml/PortForwarding.qml
@@ -16,10 +16,6 @@ LogosStorageLayout {
Connections {
target: root.backend
- // The nat ext checking needs a bit of
- // time because the Storage backend retrieves
- // the public IP by making a call to the echo service.
- // When the config is done, just push the startNodeComponent.
function onNatExtConfigCompleted() {
root.loading = false
root.completed(root.tcpPort)
@@ -30,20 +26,26 @@ LogosStorageLayout {
anchors.centerIn: parent
spacing: Theme.spacing.medium
width: 400
- Layout.fillWidth: true
- LogosText {
- id: questionText
- font.pixelSize: Theme.typography.titleText
- text: "Choose your TCP port"
- Layout.alignment: Qt.AlignCenter
+ PortIcon {
+ animated: root.loading
+ dotColor: Theme.palette.text
+ Layout.alignment: Qt.AlignHCenter
+ }
+
+ LogosText {
+ font.pixelSize: Theme.typography.titleText
+ text: "Port Configuration"
+ Layout.alignment: Qt.AlignHCenter
}
LogosText {
- id: questionDescriptionText
font.pixelSize: Theme.typography.primaryText
- text: "The TCP port has to be open to connect with other remote peers."
- Layout.alignment: Qt.AlignCenter
+ text: "The TCP port must be open to connect with remote peers."
+ Layout.alignment: Qt.AlignHCenter
+ horizontalAlignment: Text.AlignHCenter
+ wrapMode: Text.WordWrap
+ Layout.fillWidth: true
}
LogosTextField {
@@ -64,14 +66,14 @@ LogosStorageLayout {
}
}
- Row {
+ RowLayout {
+ Layout.alignment: Qt.AlignHCenter
spacing: Theme.spacing.small
LogosStorageButton {
text: "Back"
- onClicked: {
- root.back()
- }
+ enabled: !root.loading
+ onClicked: root.back()
}
LogosStorageButton {
@@ -83,6 +85,14 @@ LogosStorageLayout {
}
}
}
+
+ LogosText {
+ font.pixelSize: Theme.typography.primaryText
+ text: "Retrieving your public IP..."
+ color: Theme.palette.textTertiary
+ visible: root.loading
+ Layout.alignment: Qt.AlignHCenter
+ }
}
QtObject {
diff --git a/src/qml/StartNode.qml b/src/qml/StartNode.qml
index a52480b..7c466fb 100644
--- a/src/qml/StartNode.qml
+++ b/src/qml/StartNode.qml
@@ -9,7 +9,7 @@ LogosStorageLayout {
property var backend: mockBackend
property string status: ""
- property string title: "Starting your node...."
+ property string title: "Starting your node"
property string resolution: ""
property bool starting: true
property bool success: false
@@ -19,15 +19,13 @@ LogosStorageLayout {
function onNodeStarted() {
root.starting = false
- root.status = "Logos Storage started successfully."
- root.title = "Success"
+ root.status = "Your node is up and reachable."
+ root.title = "Node is ready"
root.success = true
}
Component.onCompleted: root.backend.start()
- // Wait after startCompleted before calling checkNodeIs to
- // make sure the the node is started and ready.
Timer {
id: nodeCheckTimer
interval: 500
@@ -39,9 +37,8 @@ LogosStorageLayout {
target: root.backend
function onStartCompleted() {
- console.info("startCompleted")
- root.title = "Checking.."
- root.status = "Your node is started, checking everything is up."
+ root.title = "Checking connectivity"
+ root.status = "Node started, verifying reachability..."
nodeCheckTimer.start()
}
@@ -57,7 +54,7 @@ LogosStorageLayout {
function onNodeIsntUp(reason) {
root.starting = false
- root.title = "Node not reachable"
+ root.title = "Node unreachable"
root.status = ""
root.resolution = reason
}
@@ -69,28 +66,35 @@ LogosStorageLayout {
spacing: Theme.spacing.medium
LogosText {
- id: titleText
font.pixelSize: Theme.typography.titleText
text: root.title
Layout.alignment: Qt.AlignHCenter
}
- LogosText {
- id: statusText
- font.pixelSize: Theme.typography.primaryText
- text: root.status
+ NodeStatusIcon {
+ starting: root.starting
+ success: root.success
Layout.alignment: Qt.AlignHCenter
- wrapMode: Text.WordWrap
}
LogosText {
- id: resolutionText
+ font.pixelSize: Theme.typography.primaryText
+ text: root.status
+ visible: root.status !== ""
+ Layout.alignment: Qt.AlignHCenter
+ horizontalAlignment: Text.AlignHCenter
+ wrapMode: Text.WordWrap
+ Layout.fillWidth: true
+ }
+
+ LogosText {
font.pixelSize: Theme.typography.primaryText
text: root.resolution
visible: root.resolution !== ""
color: Theme.palette.error
wrapMode: Text.WordWrap
Layout.fillWidth: true
+ horizontalAlignment: Text.AlignHCenter
Layout.alignment: Qt.AlignHCenter
}
}
@@ -101,12 +105,11 @@ LogosStorageLayout {
anchors.bottomMargin: 10
anchors.leftMargin: 10
text: "Back"
- onClicked: function () {
+ enabled: !root.starting
+ onClicked: {
root.backend.stop()
root.back()
}
-
- enabled: root.starting == false
}
LogosStorageButton {
@@ -115,40 +118,33 @@ LogosStorageLayout {
anchors.bottomMargin: 10
anchors.rightMargin: 10
text: "Next"
- onClicked: function () {
+ enabled: root.success
+ onClicked: {
root.backend.saveCurrentConfig()
root.next()
}
- enabled: root.success == true
}
- // In preview/mock mode, simulate a successful node start after 2 seconds
Timer {
interval: 2000
running: root.backend && root.backend.isMock === true
- onTriggered: root.onNodeStarted()
repeat: false
+ onTriggered: root.onNodeStarted()
}
QtObject {
id: mockBackend
readonly property bool isMock: true
- property string configJson: "{}"
signal startCompleted
signal startFailed(string error)
signal nodeIsUp
signal nodeIsntUp(string reason)
- function guessResolution() {
- return ""
- }
-
function checkNodeIsUp() {}
-
function stop() {}
-
function saveCurrentConfig() {}
+ function start() {}
}
}
diff --git a/src/storage_resources.qrc b/src/storage_resources.qrc
index fe9cd5e..646780a 100644
--- a/src/storage_resources.qrc
+++ b/src/storage_resources.qrc
@@ -10,5 +10,13 @@
qml/PortForwarding.qml
qml/ErrorToast.qml
qml/HealthIndicator.qml
+ qml/ModeSelector.qml
+ qml/AdvancedSetup.qml
+ qml/DotIcon.qml
+ qml/NodeStatusIcon.qml
+ qml/GuideIcon.qml
+ qml/AdvancedIcon.qml
+ qml/UpnpIcon.qml
+ qml/PortIcon.qml