mirror of
https://github.com/logos-blockchain/logos-blockchain-ui.git
synced 2026-04-01 17:03:31 +00:00
Merge pull request #8 from logos-blockchain/feat/generateConfig
feat: add support to generate configs
This commit is contained in:
commit
c904b91112
@ -3,7 +3,10 @@
|
||||
#include <QClipboard>
|
||||
#include <QDebug>
|
||||
#include <QDateTime>
|
||||
#include <QDir>
|
||||
#include <QGuiApplication>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QSettings>
|
||||
#include <QTimer>
|
||||
#include <QUrl>
|
||||
@ -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");
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QVariant>
|
||||
#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;
|
||||
|
||||
|
||||
@ -5,6 +5,9 @@
|
||||
<file>qml/views/StatusConfigView.qml</file>
|
||||
<file>qml/views/LogsView.qml</file>
|
||||
<file>qml/views/WalletView.qml</file>
|
||||
<file>qml/views/GenerateConfigView.qml</file>
|
||||
<file>qml/views/ConfigChoiceView.qml</file>
|
||||
<file>qml/views/SetConfigPathView.qml</file>
|
||||
<file>icons/blockchain.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
|
||||
@ -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
|
||||
}
|
||||
}
|
||||
|
||||
113
src/qml/views/ConfigChoiceView.qml
Normal file
113
src/qml/views/ConfigChoiceView.qml
Normal file
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
215
src/qml/views/GenerateConfigView.qml
Normal file
215
src/qml/views/GenerateConfigView.qml
Normal file
@ -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"
|
||||
}
|
||||
}
|
||||
}
|
||||
91
src/qml/views/SetConfigPathView.qml
Normal file
91
src/qml/views/SetConfigPathView.qml
Normal file
@ -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)
|
||||
}
|
||||
}
|
||||
@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -183,7 +183,7 @@ ColumnLayout {
|
||||
visible: comboControl.count > 0
|
||||
}
|
||||
contentItem: Item {
|
||||
implicitWidth: comboControl.availableWidth
|
||||
implicitWidth: 200
|
||||
implicitHeight: 30
|
||||
|
||||
TextField {
|
||||
|
||||
@ -1,4 +1,7 @@
|
||||
module views
|
||||
StatusConfigView 1.0 StatusConfigView.qml
|
||||
LogsView 1.0 LogsView.qml
|
||||
WalletView 1.0 WalletView.qml
|
||||
WalletView 1.0 WalletView.qml
|
||||
GenerateConfigView 1.0 GenerateConfigView.qml
|
||||
ConfigChoiceView 1.0 ConfigChoiceView.qml
|
||||
SetConfigPathView 1.0 SetConfigPathView.qml
|
||||
Loading…
x
Reference in New Issue
Block a user