diff --git a/src/BlockchainBackend.cpp b/src/BlockchainBackend.cpp index 491ff06..09f5b83 100644 --- a/src/BlockchainBackend.cpp +++ b/src/BlockchainBackend.cpp @@ -3,7 +3,10 @@ #include #include #include +#include #include +#include +#include #include #include #include @@ -98,6 +101,14 @@ void BlockchainBackend::setDeploymentConfig(const QString& path) } } +void BlockchainBackend::setUseGeneratedConfig(bool useGenerated) +{ + if (m_useGeneratedConfig != useGenerated) { + m_useGeneratedConfig = useGenerated; + emit useGeneratedConfigChanged(); + } +} + void BlockchainBackend::clearLogs() { m_logModel->clear(); @@ -206,3 +217,80 @@ void BlockchainBackend::onNewBlock(const QVariantList& data) } m_logModel->append(line); } + +static QString toLocalPath(const QString& pathInput) +{ + if (pathInput.trimmed().isEmpty()) + return pathInput; + return QUrl::fromUserInput(pathInput).toLocalFile(); +} + +int BlockchainBackend::generateConfig(const QString& outputPath, + const QStringList& initialPeers, + int netPort, + int blendPort, + const QString& httpAddr, + const QString& externalAddress, + bool noPublicIpCheck, + int deploymentMode, + const QString& deploymentConfigPath, + const QString& statePath) +{ + if (!m_blockchainClient) { + return -1; + } + QVariantMap normalized; + + // Output path: default if empty, then normalize + QString out = outputPath.trimmed(); + if (out.isEmpty()) { + out = generatedUserConfigPath(); + } else { + out = toLocalPath(out); + } + normalized.insert(QStringLiteral("output"), out); + + if (!initialPeers.isEmpty()) { + QVariantList peersList; + for (const QString& p : initialPeers) { + if (!p.trimmed().isEmpty()) + peersList.append(p.trimmed()); + } + if (!peersList.isEmpty()) + normalized.insert(QStringLiteral("initial_peers"), peersList); + } + if (netPort > 0) + normalized.insert(QStringLiteral("net_port"), netPort); + if (blendPort > 0) + normalized.insert(QStringLiteral("blend_port"), blendPort); + if (!httpAddr.trimmed().isEmpty()) + normalized.insert(QStringLiteral("http_addr"), httpAddr.trimmed()); + if (!externalAddress.trimmed().isEmpty()) + normalized.insert(QStringLiteral("external_address"), externalAddress.trimmed()); + if (noPublicIpCheck) + normalized.insert(QStringLiteral("no_public_ip_check"), true); + if (deploymentMode == 0) { + QVariantMap deployment; + deployment.insert(QStringLiteral("well_known_deployment"), QStringLiteral("devnet")); + normalized.insert(QStringLiteral("deployment"), deployment); + } else if (deploymentMode == 1 && !deploymentConfigPath.trimmed().isEmpty()) { + QVariantMap deployment; + deployment.insert(QStringLiteral("config_path"), toLocalPath(deploymentConfigPath.trimmed())); + normalized.insert(QStringLiteral("deployment"), deployment); + } + if (!statePath.trimmed().isEmpty()) + normalized.insert(QStringLiteral("state_path"), toLocalPath(statePath.trimmed())); + + const QJsonDocument doc = QJsonDocument::fromVariant(normalized); + const QByteArray jsonBytes = doc.toJson(QJsonDocument::Compact); + const QString jsonToSend = QString::fromUtf8(jsonBytes); + + QVariant result = m_blockchainClient->invokeRemoteMethod( + BLOCKCHAIN_MODULE_NAME, "generate_user_config_from_str", jsonToSend); + return result.isValid() ? result.toInt() : -1; +} + +QString BlockchainBackend::generatedUserConfigPath() const +{ + return QDir::currentPath() + QStringLiteral("/user_config.yaml"); +} diff --git a/src/BlockchainBackend.h b/src/BlockchainBackend.h index fb053ab..3150e3f 100644 --- a/src/BlockchainBackend.h +++ b/src/BlockchainBackend.h @@ -3,6 +3,7 @@ #include #include #include +#include #include "logos_api.h" #include "logos_api_client.h" #include "LogModel.h" @@ -29,8 +30,10 @@ public: Q_PROPERTY(BlockchainStatus status READ status NOTIFY statusChanged) Q_PROPERTY(QString userConfig READ userConfig WRITE setUserConfig NOTIFY userConfigChanged) Q_PROPERTY(QString deploymentConfig READ deploymentConfig WRITE setDeploymentConfig NOTIFY deploymentConfigChanged) + Q_PROPERTY(bool useGeneratedConfig READ useGeneratedConfig WRITE setUseGeneratedConfig NOTIFY useGeneratedConfigChanged) Q_PROPERTY(LogModel* logModel READ logModel CONSTANT) Q_PROPERTY(QStringList knownAddresses READ knownAddresses NOTIFY knownAddressesChanged) + Q_PROPERTY(QString generatedUserConfigPath READ generatedUserConfigPath CONSTANT) explicit BlockchainBackend(LogosAPI* logosAPI = nullptr, QObject* parent = nullptr); ~BlockchainBackend(); @@ -38,11 +41,13 @@ public: BlockchainStatus status() const { return m_status; } QString userConfig() const { return m_userConfig; } QString deploymentConfig() const { return m_deploymentConfig; } + bool useGeneratedConfig() const { return m_useGeneratedConfig; } LogModel* logModel() const { return m_logModel; } QStringList knownAddresses() const { return m_knownAddresses; } void setUserConfig(const QString& path); void setDeploymentConfig(const QString& path); + void setUseGeneratedConfig(bool useGenerated); Q_INVOKABLE void clearLogs(); Q_INVOKABLE void copyToClipboard(const QString& text); Q_INVOKABLE QString getBalance(const QString& addressHex); @@ -53,6 +58,17 @@ public: Q_INVOKABLE void startBlockchain(); Q_INVOKABLE void stopBlockchain(); Q_INVOKABLE void refreshKnownAddresses(); + Q_INVOKABLE int generateConfig(const QString& outputPath, + const QStringList& initialPeers, + int netPort, + int blendPort, + const QString& httpAddr, + const QString& externalAddress, + bool noPublicIpCheck, + int deploymentMode, + const QString& deploymentConfigPath, + const QString& statePath); + Q_INVOKABLE QString generatedUserConfigPath() const; public slots: void onNewBlock(const QVariantList& data); @@ -61,6 +77,7 @@ signals: void statusChanged(); void userConfigChanged(); void deploymentConfigChanged(); + void useGeneratedConfigChanged(); void knownAddressesChanged(); private: @@ -69,6 +86,7 @@ private: BlockchainStatus m_status; QString m_userConfig; QString m_deploymentConfig; + bool m_useGeneratedConfig = false; LogModel* m_logModel; QStringList m_knownAddresses; diff --git a/src/blockchain_resources.qrc b/src/blockchain_resources.qrc index 9286533..38432f5 100644 --- a/src/blockchain_resources.qrc +++ b/src/blockchain_resources.qrc @@ -5,6 +5,9 @@ qml/views/StatusConfigView.qml qml/views/LogsView.qml qml/views/WalletView.qml + qml/views/GenerateConfigView.qml + qml/views/ConfigChoiceView.qml + qml/views/SetConfigPathView.qml icons/blockchain.png diff --git a/src/qml/BlockchainView.qml b/src/qml/BlockchainView.qml index ff9e6bd..6836a71 100644 --- a/src/qml/BlockchainView.qml +++ b/src/qml/BlockchainView.qml @@ -1,8 +1,6 @@ import QtQuick import QtQuick.Controls import QtQuick.Layouts -import QtQuick.Dialogs -import QtCore import BlockchainBackend import Logos.Theme @@ -46,75 +44,96 @@ Rectangle { default: return Theme.palette.textSecondary; } } + property int currentPage: 0 // 0 = config choice (page 1), 1 = node + wallet + logs (page 2) } color: Theme.palette.background - SplitView { + StackLayout { anchors.fill: parent anchors.margins: Theme.spacing.large - orientation: Qt.Vertical + currentIndex: _d.currentPage - // Top: Status/Config + Wallet side-by-side - RowLayout { - SplitView.fillWidth: true - SplitView.minimumHeight: 200 - - StatusConfigView { - Layout.preferredWidth: parent.width / 2 - statusText: _d.getStatusString(backend.status) - statusColor: _d.getStatusColor(backend.status) - userConfig: backend.userConfig - deploymentConfig: backend.deploymentConfig - canStart: !!backend.userConfig - && backend.status !== BlockchainBackend.Starting - && backend.status !== BlockchainBackend.Stopping - isRunning: backend.status === BlockchainBackend.Running - - onStartRequested: backend.startBlockchain() - onStopRequested: backend.stopBlockchain() - onChangeUserConfigRequested: userConfigFileDialog.open() - onChangeDeploymentConfigRequested: deploymentConfigFileDialog.open() - } - - WalletView { - id: walletView - Layout.preferredWidth: parent.width / 2 - knownAddresses: backend.knownAddresses - - onGetBalanceRequested: function(addressHex) { - walletView.setBalanceResult(backend.getBalance(addressHex)) + // Page 1: Config choice (Option 1: Generate own config, Option 2: Set path to configs) + ScrollView { + id: configChoiceScrollView + clip: true + ConfigChoiceView { + id: configChoiceView + width: configChoiceScrollView.availableWidth + userConfigPath: backend.userConfig + deploymentConfigPath: backend.deploymentConfig + generatedUserConfigPath: backend.generatedUserConfigPath + onUserConfigPathSelected: function(path) { backend.userConfig = path } + onDeploymentConfigPathSelected: function(path) { backend.deploymentConfig = path } + onSetPathToConfigsRequested: function() { + backend.useGeneratedConfig = false + _d.currentPage = 1 } - onTransferRequested: function(fromKeyHex, toKeyHex, amount) { - walletView.setTransferResult(backend.transferFunds(fromKeyHex, toKeyHex, amount)) + onGenerateRequested: function(outputPath, initialPeers, netPort, blendPort, httpAddr, externalAddress, noPublicIpCheck, deploymentMode, deploymentConfigPath, statePath) { + configChoiceView.generateResultSuccess = false + configChoiceView.generateResultMessage = "" + var code = backend.generateConfig(outputPath, initialPeers, netPort, blendPort, httpAddr, externalAddress, noPublicIpCheck, deploymentMode, deploymentConfigPath, statePath) + configChoiceView.generateResultSuccess = (code === 0) + configChoiceView.generateResultMessage = code === 0 ? qsTr("Config generated successfully.") : qsTr("Generate failed (code: %1).").arg(code) + if (code === 0) { + backend.userConfig = (outputPath !== "") ? outputPath : backend.generatedUserConfigPath + backend.deploymentConfig = (deploymentMode === 1 && deploymentConfigPath !== "") ? deploymentConfigPath : "" + backend.useGeneratedConfig = true + _d.currentPage = 1 + } } } } - // Bottom: Logs - LogsView { - SplitView.fillWidth: true - SplitView.minimumHeight: 150 + // Page 2: Start node, balances, transfer, logs + SplitView { + orientation: Qt.Vertical - logModel: backend.logModel - onClearRequested: backend.clearLogs() - onCopyToClipboard: (text) => backend.copyToClipboard(text) + RowLayout { + SplitView.fillWidth: true + SplitView.minimumHeight: 200 + + StatusConfigView { + Layout.preferredWidth: parent.width / 2 + statusText: _d.getStatusString(backend.status) + statusColor: _d.getStatusColor(backend.status) + userConfig: backend.userConfig + deploymentConfig: backend.deploymentConfig + useGeneratedConfig: backend.useGeneratedConfig + canStart: !!backend.userConfig + && backend.status !== BlockchainBackend.Starting + && backend.status !== BlockchainBackend.Stopping + isRunning: backend.status === BlockchainBackend.Running + + onStartRequested: backend.startBlockchain() + onStopRequested: backend.stopBlockchain() + onChangeConfigRequested: _d.currentPage = 0 + } + + WalletView { + id: walletView + Layout.preferredWidth: parent.width / 2 + knownAddresses: backend.knownAddresses + + onGetBalanceRequested: function(addressHex) { + walletView.setBalanceResult(backend.getBalance(addressHex)) + } + onTransferRequested: function(fromKeyHex, toKeyHex, amount) { + walletView.setTransferResult(backend.transferFunds(fromKeyHex, toKeyHex, amount)) + } + } + } + + LogsView { + SplitView.fillWidth: true + SplitView.minimumHeight: 150 + + logModel: backend.logModel + onClearRequested: backend.clearLogs() + onCopyToClipboard: (text) => backend.copyToClipboard(text) + } } } - FileDialog { - id: userConfigFileDialog - modality: Qt.NonModal - nameFilters: ["YAML files (*.yaml)"] - currentFolder: StandardPaths.standardLocations(StandardPaths.DocumentsLocation)[0] - onAccepted: backend.userConfig = selectedFile - } - - FileDialog { - id: deploymentConfigFileDialog - modality: Qt.NonModal - nameFilters: ["YAML files (*.yaml)"] - currentFolder: StandardPaths.standardLocations(StandardPaths.DocumentsLocation)[0] - onAccepted: backend.deploymentConfig = selectedFile - } } diff --git a/src/qml/views/ConfigChoiceView.qml b/src/qml/views/ConfigChoiceView.qml new file mode 100644 index 0000000..83915e3 --- /dev/null +++ b/src/qml/views/ConfigChoiceView.qml @@ -0,0 +1,113 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import Logos.Theme +import Logos.Controls + +ColumnLayout { + id: root + + property string userConfigPath: "" + property string deploymentConfigPath: "" + property string generatedUserConfigPath: "" + + property bool generateResultSuccess: false + property string generateResultMessage: "" + + signal generateRequested(string outputPath, var initialPeers, int netPort, int blendPort, string httpAddr, string externalAddress, bool noPublicIpCheck, int deploymentMode, string deploymentConfigPath, string statePath) + signal setPathToConfigsRequested() + signal userConfigPathSelected(string path) + signal deploymentConfigPathSelected(string path) + + QtObject { + id: d + property int selectedOption: 0 + } + + spacing: Theme.spacing.large + + LogosText { + Layout.alignment: Qt.AlignLeft + font.bold: true + font.pixelSize: Theme.typography.primaryText + text: qsTr("Choose how to set up your node config") + } + + LogosText { + Layout.alignment: Qt.AlignLeft + Layout.topMargin: -Theme.spacing.small + text: qsTr("Generate a new config, or set paths to your existing config files.") + font.pixelSize: Theme.typography.secondaryText + color: Theme.palette.textSecondary + wrapMode: Text.WordWrap + } + + RowLayout { + Layout.fillWidth: true + spacing: Theme.spacing.large + + LogosButton { + text: qsTr("Generate config") + Layout.preferredHeight: 50 + Layout.fillWidth: true + onClicked: d.selectedOption = 1 + } + + LogosButton { + text: qsTr("Set path to config") + Layout.preferredHeight: 50 + Layout.fillWidth: true + onClicked: d.selectedOption = 2 + } + } + + Loader { + id: contentLoader + Layout.fillWidth: true + Layout.fillHeight: true + visible: d.selectedOption === 1 || d.selectedOption === 2 + active: d.selectedOption === 1 || d.selectedOption === 2 + sourceComponent: d.selectedOption === 1 ? generateConfigComponent : (d.selectedOption === 2 ? setConfigPathComponent : null) + } + + Item { + Layout.fillHeight: true + visible: d.selectedOption !== 1 && d.selectedOption !== 2 + } + + Component { + id: generateConfigComponent + ColumnLayout { + spacing: Theme.spacing.medium + GenerateConfigView { + generatedUserConfigPath: root.generatedUserConfigPath + resultSuccess: root.generateResultSuccess + resultMessage: root.generateResultMessage + Layout.fillWidth: true + onGenerateRequested: root.generateRequested( + outputPath, + initialPeers, + netPort, + blendPort, + httpAddr, + externalAddress, + noPublicIpCheck, + deploymentMode, + deploymentConfigPath, + statePath) + } + } + } + + Component { + id: setConfigPathComponent + SetConfigPathView { + userConfigPath: root.userConfigPath + deploymentConfigPath: root.deploymentConfigPath + onUserConfigPathSelected: function(path) { root.userConfigPathSelected(path) } + onDeploymentConfigPathSelected: function(path) { root.deploymentConfigPathSelected(path) } + onSetPathToConfigsRequested: root.setPathToConfigsRequested() + } + } +} diff --git a/src/qml/views/GenerateConfigView.qml b/src/qml/views/GenerateConfigView.qml new file mode 100644 index 0000000..93f453d --- /dev/null +++ b/src/qml/views/GenerateConfigView.qml @@ -0,0 +1,215 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Dialogs +import QtCore + +import Logos.Theme +import Logos.Controls + +ColumnLayout { + id: root + + property string generatedUserConfigPath: "" + property bool resultSuccess: false + property string resultMessage: "" + + signal generateRequested(string outputPath, var initialPeers, int netPort, int blendPort, string httpAddr, string externalAddress, bool noPublicIpCheck, int deploymentMode, string deploymentConfigPath, string statePath) + + + Component.onCompleted: { + if (outputField.text === "" && root.generatedUserConfigPath !== "") + outputField.text = root.generatedUserConfigPath + } + + QtObject { + id: d + function doGenerate() { + var peers = initialPeersArea.text.split("\n").map(function(s) { return s.trim() }).filter(function(s) { return s.length > 0 }) + root.generateRequested( + outputField.text.trim(), + peers, + netPortSpin.value, + blendPortSpin.value, + httpAddrField.text.trim(), + externalAddrField.text.trim(), + noPublicIpCheckBox.checked, + devnetRadio.checked ? 0 : 1, + customDeploymentField.text.trim(), + statePathField.text.trim()) + } + } + + spacing: Theme.spacing.medium + + LogosText { + Layout.alignment: Qt.AlignLeft + font.bold: true + text: qsTr("Generate user config") + } + + LogosText { + Layout.alignment: Qt.AlignLeft + Layout.topMargin: -Theme.spacing.small + text: qsTr("All fields are optional. Values are passed as args to generate config.") + font.pixelSize: Theme.typography.secondaryText + color: Theme.palette.textSecondary + wrapMode: Text.WordWrap + } + + // Output path (defaults to generated path; user can change via text or folder browse) + RowLayout { + Layout.fillWidth: true + spacing: Theme.spacing.small + LogosTextField { + id: outputField + Layout.fillWidth: true + placeholderText: root.generatedUserConfigPath || qsTr("Output config path (e.g. node_config.yaml)") + } + LogosButton { + text: qsTr("Browse…") + onClicked: outputFolderDialog.open() + } + } + + // Initial peers (multi-line) + LogosText { + Layout.alignment: Qt.AlignLeft + text: qsTr("Initial peers (one per line)") + font.pixelSize: Theme.typography.secondaryText + } + ScrollView { + Layout.fillWidth: true + Layout.preferredHeight: 60 + clip: true + TextArea { + id: initialPeersArea + placeholderText: qsTr("Peer addresses, one per line") + font.pixelSize: Theme.typography.secondaryText + } + } + + // Net port / Blend port + RowLayout { + Layout.fillWidth: true + spacing: Theme.spacing.large + LogosText { + text: qsTr("Net port") + font.pixelSize: Theme.typography.secondaryText + } + SpinBox { + id: netPortSpin + from: 0 + to: 65535 + value: 0 + Layout.preferredWidth: 100 + editable: true + } + Item { Layout.fillWidth: true } + LogosText { + text: qsTr("Blend port") + font.pixelSize: Theme.typography.secondaryText + } + SpinBox { + id: blendPortSpin + from: 0 + to: 65535 + value: 0 + Layout.preferredWidth: 100 + editable: true + } + } + + LogosTextField { + id: httpAddrField + Layout.fillWidth: true + placeholderText: qsTr("HTTP address (e.g. 0.0.0.0:8080)") + } + + LogosTextField { + id: externalAddrField + Layout.fillWidth: true + placeholderText: qsTr("External address (e.g. public IP:port)") + } + + CheckBox { + id: noPublicIpCheckBox + text: qsTr("No public IP check") + } + + // Deployment + LogosText { + Layout.alignment: Qt.AlignLeft + text: qsTr("Deployment") + font.pixelSize: Theme.typography.secondaryText + } + RowLayout { + Layout.fillWidth: true + spacing: Theme.spacing.medium + RadioButton { + id: devnetRadio + checked: true + text: qsTr("Devnet") + } + RadioButton { + id: customRadio + text: qsTr("Custom config") + } + LogosTextField { + id: customDeploymentField + visible: customRadio.checked + Layout.fillWidth: true + placeholderText: qsTr("Path to deployment config") + } + LogosButton { + visible: customRadio.checked + text: qsTr("Browse") + onClicked: deploymentConfigFileDialog.open() + } + } + + LogosTextField { + id: statePathField + Layout.fillWidth: true + placeholderText: qsTr("State path") + } + + LogosButton { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + Layout.preferredHeight: 50 + text: qsTr("Generate config") + onClicked: d.doGenerate() + } + + LogosText { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + text: root.resultMessage + color: root.resultSuccess ? Theme.palette.success : Theme.palette.error + font.pixelSize: Theme.typography.secondaryText + wrapMode: Text.WordWrap + visible: root.resultMessage !== "" + } + + FileDialog { + id: deploymentConfigFileDialog + modality: Qt.NonModal + nameFilters: ["YAML files (*.yaml)", "All files (*)"] + currentFolder: StandardPaths.standardLocations(StandardPaths.DocumentsLocation)[0] + onAccepted: customDeploymentField.text = selectedFile + } + + FolderDialog { + id: outputFolderDialog + modality: Qt.NonModal + title: qsTr("Choose folder for config file") + onAccepted: { + var urlStr = selectedFolder.toString() + if (urlStr.indexOf("file://") === 0) + urlStr = urlStr.substring(7) + if (urlStr.length > 0) + outputField.text = urlStr + "/user_config.yaml" + } + } +} diff --git a/src/qml/views/SetConfigPathView.qml b/src/qml/views/SetConfigPathView.qml new file mode 100644 index 0000000..2fc797f --- /dev/null +++ b/src/qml/views/SetConfigPathView.qml @@ -0,0 +1,91 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts +import QtQuick.Dialogs + +import Logos.Theme +import Logos.Controls + +ColumnLayout { + id: root + + property string userConfigPath: "" + property string deploymentConfigPath: "" + + signal userConfigPathSelected(string path) + signal deploymentConfigPathSelected(string path) + signal setPathToConfigsRequested() + + spacing: Theme.spacing.medium + + LogosText { + Layout.alignment: Qt.AlignLeft + text: qsTr("Select your config files, then continue to the node.") + font.pixelSize: Theme.typography.secondaryText + color: Theme.palette.textSecondary + wrapMode: Text.WordWrap + } + + RowLayout { + Layout.fillWidth: true + spacing: Theme.spacing.small + LogosText { + text: qsTr("User Config: ") + font.bold: true + } + LogosText { + Layout.fillWidth: true + text: root.userConfigPath || qsTr("No file selected") + font.pixelSize: Theme.typography.secondaryText + color: Theme.palette.textSecondary + wrapMode: Text.WordWrap + } + LogosButton { + text: qsTr("Browse") + onClicked: userConfigFileDialog.open() + } + } + + RowLayout { + Layout.fillWidth: true + spacing: Theme.spacing.small + LogosText { + text: qsTr("Deployment Config: ") + font.bold: true + } + LogosText { + Layout.fillWidth: true + text: root.deploymentConfigPath || qsTr("No file selected") + font.pixelSize: Theme.typography.secondaryText + color: Theme.palette.textSecondary + wrapMode: Text.WordWrap + } + LogosButton { + text: qsTr("Browse") + onClicked: deploymentConfigFileDialog.open() + } + } + + LogosButton { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + Layout.preferredHeight: 50 + text: qsTr("Continue") + enabled: !!root.userConfigPath + onClicked: root.setPathToConfigsRequested() + } + + FileDialog { + id: userConfigFileDialog + modality: Qt.NonModal + nameFilters: ["YAML files (*.yaml)"] + onAccepted: root.userConfigPathSelected(selectedFile) + } + + FileDialog { + id: deploymentConfigFileDialog + modality: Qt.NonModal + nameFilters: ["YAML files (*.yaml)"] + onAccepted: root.deploymentConfigPathSelected(selectedFile) + } +} diff --git a/src/qml/views/StatusConfigView.qml b/src/qml/views/StatusConfigView.qml index 455543f..926797e 100644 --- a/src/qml/views/StatusConfigView.qml +++ b/src/qml/views/StatusConfigView.qml @@ -13,13 +13,13 @@ ColumnLayout { required property color statusColor required property string userConfig required property string deploymentConfig + required property bool useGeneratedConfig required property bool canStart required property bool isRunning signal startRequested() signal stopRequested() - signal changeUserConfigRequested() - signal changeDeploymentConfigRequested() + signal changeConfigRequested() spacing: Theme.spacing.large @@ -89,55 +89,56 @@ ColumnLayout { anchors.margins: Theme.spacing.large spacing: Theme.spacing.medium - // User Config Card - ConfigSelectionPanel { - id: userConfigContent - - title: qsTr("User Config: ") - configPath: root.userConfig || qsTr("No file selected") - onChangeRequested: root.changeUserConfigRequested() + LogosText { + text: qsTr("Config") + font.bold: true } - // Deployment Config Card - ConfigSelectionPanel { - id: deploymentConfigContent + RowLayout { + Layout.fillWidth: true + spacing: Theme.spacing.small + LogosText { + text: qsTr("User Config: ") + font.bold: true + } + LogosText { + Layout.fillWidth: true + text: (root.userConfig || qsTr("No file selected")) + + (root.useGeneratedConfig ? " " + qsTr("(Generated)") : "") + font.pixelSize: Theme.typography.secondaryText + color: Theme.palette.textSecondary + wrapMode: Text.WordWrap + } + } - title: qsTr("Deployment Config: ") - configPath: root.deploymentConfig || qsTr("No file selected") - onChangeRequested: root.changeDeploymentConfigRequested() + RowLayout { + Layout.fillWidth: true + Layout.topMargin: -Theme.spacing.small + spacing: Theme.spacing.small + LogosText { + text: qsTr("Deployment Config: ") + font.bold: true + } + LogosText { + Layout.fillWidth: true + text: (root.useGeneratedConfig && root.deploymentConfig ? root.deploymentConfig : + root.useGeneratedConfig ? qsTr("Devnet (default)") : + (root.deploymentConfig || qsTr("No file selected"))) + font.pixelSize: Theme.typography.secondaryText + color: Theme.palette.textSecondary + wrapMode: Text.WordWrap + } + } + + LogosButton { + Layout.alignment: Qt.AlignHCenter + Layout.fillWidth: true + Layout.preferredHeight: 50 + text: qsTr("Change") + onClicked: root.changeConfigRequested() } } } Item { Layout.fillHeight: true } - - component ConfigSelectionPanel: ColumnLayout { - property string title - property string configPath - signal changeRequested() - - spacing: Theme.spacing.medium - - LogosText { - text: title - font.bold: true - } - - LogosText { - Layout.fillWidth: true - Layout.topMargin: -Theme.spacing.small - text: configPath - font.pixelSize: Theme.typography.secondaryText - color: Theme.palette.textSecondary - wrapMode: Text.WordWrap - } - - LogosButton { - Layout.alignment: Qt.AlignHCenter - Layout.fillWidth: true - Layout.preferredHeight: 50 - text: qsTr("Change") - onClicked: changeRequested() - } - } } diff --git a/src/qml/views/WalletView.qml b/src/qml/views/WalletView.qml index c954e15..9f857e9 100644 --- a/src/qml/views/WalletView.qml +++ b/src/qml/views/WalletView.qml @@ -183,7 +183,7 @@ ColumnLayout { visible: comboControl.count > 0 } contentItem: Item { - implicitWidth: comboControl.availableWidth + implicitWidth: 200 implicitHeight: 30 TextField { diff --git a/src/qml/views/qmldir b/src/qml/views/qmldir index 8de2446..a90a4e1 100644 --- a/src/qml/views/qmldir +++ b/src/qml/views/qmldir @@ -1,4 +1,7 @@ module views StatusConfigView 1.0 StatusConfigView.qml LogsView 1.0 LogsView.qml -WalletView 1.0 WalletView.qml \ No newline at end of file +WalletView 1.0 WalletView.qml +GenerateConfigView 1.0 GenerateConfigView.qml +ConfigChoiceView 1.0 ConfigChoiceView.qml +SetConfigPathView 1.0 SetConfigPathView.qml \ No newline at end of file