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