mirror of
https://github.com/logos-blockchain/logos-blockchain-ui.git
synced 2026-05-17 07:19:33 +00:00
Merge module-builder port (PR #16) as base for basecamp fix
This commit is contained in:
commit
3140c94cfc
236
CMakeLists.txt
236
CMakeLists.txt
@ -1,219 +1,27 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(BlockchainUIPlugin VERSION 1.0.0 LANGUAGES CXX)
|
||||
cmake_minimum_required(VERSION 3.14)
|
||||
project(BlockchainUiPlugin LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_AUTORCC ON)
|
||||
set(CMAKE_AUTOUIC ON)
|
||||
|
||||
# Allow override from environment or command line
|
||||
if(NOT DEFINED LOGOS_LIBLOGOS_ROOT)
|
||||
set(_parent_liblogos "${CMAKE_SOURCE_DIR}/../logos-liblogos")
|
||||
set(_use_vendor ${LOGOS_BLOCKCHAIN_UI_USE_VENDOR})
|
||||
if(NOT _use_vendor)
|
||||
if(NOT EXISTS "${_parent_liblogos}/interface.h")
|
||||
set(_use_vendor ON)
|
||||
endif()
|
||||
endif()
|
||||
if(_use_vendor)
|
||||
set(LOGOS_LIBLOGOS_ROOT "${CMAKE_SOURCE_DIR}/vendor/logos-liblogos")
|
||||
else()
|
||||
set(LOGOS_LIBLOGOS_ROOT "${_parent_liblogos}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
if(NOT DEFINED LOGOS_CPP_SDK_ROOT)
|
||||
set(_parent_cpp_sdk "${CMAKE_SOURCE_DIR}/../logos-cpp-sdk")
|
||||
set(_use_vendor ${LOGOS_BLOCKCHAIN_UI_USE_VENDOR})
|
||||
if(NOT _use_vendor)
|
||||
if(NOT EXISTS "${_parent_cpp_sdk}/cpp/logos_api.h")
|
||||
set(_use_vendor ON)
|
||||
endif()
|
||||
endif()
|
||||
if(_use_vendor)
|
||||
set(LOGOS_CPP_SDK_ROOT "${CMAKE_SOURCE_DIR}/vendor/logos-cpp-sdk")
|
||||
else()
|
||||
set(LOGOS_CPP_SDK_ROOT "${_parent_cpp_sdk}")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Check if dependencies are available (support both source and installed layouts)
|
||||
set(_liblogos_found FALSE)
|
||||
if(EXISTS "${LOGOS_LIBLOGOS_ROOT}/interface.h")
|
||||
set(_liblogos_found TRUE)
|
||||
set(_liblogos_is_source TRUE)
|
||||
elseif(EXISTS "${LOGOS_LIBLOGOS_ROOT}/include/interface.h")
|
||||
set(_liblogos_found TRUE)
|
||||
set(_liblogos_is_source FALSE)
|
||||
endif()
|
||||
|
||||
set(_cpp_sdk_found FALSE)
|
||||
if(EXISTS "${LOGOS_CPP_SDK_ROOT}/cpp/logos_api.h")
|
||||
set(_cpp_sdk_found TRUE)
|
||||
set(_cpp_sdk_is_source TRUE)
|
||||
elseif(EXISTS "${LOGOS_CPP_SDK_ROOT}/include/cpp/logos_api.h")
|
||||
set(_cpp_sdk_found TRUE)
|
||||
set(_cpp_sdk_is_source FALSE)
|
||||
endif()
|
||||
|
||||
if(NOT _liblogos_found)
|
||||
message(FATAL_ERROR "logos-liblogos not found at ${LOGOS_LIBLOGOS_ROOT}. "
|
||||
"Set LOGOS_LIBLOGOS_ROOT or run git submodule update --init --recursive.")
|
||||
endif()
|
||||
|
||||
if(NOT _cpp_sdk_found)
|
||||
message(FATAL_ERROR "logos-cpp-sdk not found at ${LOGOS_CPP_SDK_ROOT}. "
|
||||
"Set LOGOS_CPP_SDK_ROOT or run git submodule update --init --recursive.")
|
||||
endif()
|
||||
|
||||
# Find Qt packages
|
||||
find_package(Qt6 REQUIRED COMPONENTS Core Widgets RemoteObjects Quick QuickWidgets)
|
||||
|
||||
# Try to find the component-interfaces package first
|
||||
find_package(component-interfaces QUIET)
|
||||
|
||||
# If not found, use the local interfaces folder
|
||||
if(NOT component-interfaces_FOUND)
|
||||
# Include the local interfaces directory
|
||||
include_directories(${CMAKE_CURRENT_SOURCE_DIR}/interfaces)
|
||||
|
||||
# Create a component-interfaces library
|
||||
add_library(component-interfaces INTERFACE)
|
||||
target_include_directories(component-interfaces INTERFACE ${CMAKE_CURRENT_SOURCE_DIR}/interfaces)
|
||||
endif()
|
||||
|
||||
# Source files
|
||||
set(SOURCES
|
||||
src/AccountsModel.cpp
|
||||
src/AccountsModel.h
|
||||
src/BlockchainPlugin.cpp
|
||||
src/BlockchainPlugin.h
|
||||
src/BlockchainBackend.cpp
|
||||
src/BlockchainBackend.h
|
||||
src/LogModel.cpp
|
||||
src/LogModel.h
|
||||
src/blockchain_resources.qrc
|
||||
)
|
||||
|
||||
# Add SDK sources (only if source layout, installed layout uses the library)
|
||||
if(_cpp_sdk_is_source)
|
||||
list(APPEND SOURCES
|
||||
${LOGOS_CPP_SDK_ROOT}/cpp/logos_api.cpp
|
||||
${LOGOS_CPP_SDK_ROOT}/cpp/logos_api.h
|
||||
${LOGOS_CPP_SDK_ROOT}/cpp/logos_api_client.cpp
|
||||
${LOGOS_CPP_SDK_ROOT}/cpp/logos_api_client.h
|
||||
${LOGOS_CPP_SDK_ROOT}/cpp/logos_api_consumer.cpp
|
||||
${LOGOS_CPP_SDK_ROOT}/cpp/logos_api_consumer.h
|
||||
${LOGOS_CPP_SDK_ROOT}/cpp/logos_api_provider.cpp
|
||||
${LOGOS_CPP_SDK_ROOT}/cpp/logos_api_provider.h
|
||||
${LOGOS_CPP_SDK_ROOT}/cpp/token_manager.cpp
|
||||
${LOGOS_CPP_SDK_ROOT}/cpp/token_manager.h
|
||||
${LOGOS_CPP_SDK_ROOT}/cpp/module_proxy.cpp
|
||||
${LOGOS_CPP_SDK_ROOT}/cpp/module_proxy.h
|
||||
)
|
||||
endif()
|
||||
|
||||
# Create the plugin library
|
||||
add_library(blockchain_ui SHARED ${SOURCES})
|
||||
|
||||
# Set output name without lib prefix
|
||||
set_target_properties(blockchain_ui PROPERTIES
|
||||
PREFIX ""
|
||||
OUTPUT_NAME "blockchain_ui"
|
||||
)
|
||||
|
||||
# Include directories
|
||||
target_include_directories(blockchain_ui PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
)
|
||||
|
||||
# Add include directories based on layout type
|
||||
if(_liblogos_is_source)
|
||||
target_include_directories(blockchain_ui PRIVATE ${LOGOS_LIBLOGOS_ROOT})
|
||||
if(DEFINED ENV{LOGOS_MODULE_BUILDER_ROOT})
|
||||
include($ENV{LOGOS_MODULE_BUILDER_ROOT}/cmake/LogosModule.cmake)
|
||||
else()
|
||||
target_include_directories(blockchain_ui PRIVATE ${LOGOS_LIBLOGOS_ROOT}/include)
|
||||
message(FATAL_ERROR "LogosModule.cmake not found. Set LOGOS_MODULE_BUILDER_ROOT.")
|
||||
endif()
|
||||
|
||||
if(_cpp_sdk_is_source)
|
||||
target_include_directories(blockchain_ui PRIVATE
|
||||
${LOGOS_CPP_SDK_ROOT}/cpp
|
||||
)
|
||||
else()
|
||||
target_include_directories(blockchain_ui PRIVATE
|
||||
${LOGOS_CPP_SDK_ROOT}/include
|
||||
${LOGOS_CPP_SDK_ROOT}/include/cpp
|
||||
${LOGOS_CPP_SDK_ROOT}/include/core
|
||||
)
|
||||
endif()
|
||||
|
||||
# Link against libraries
|
||||
target_link_libraries(blockchain_ui PRIVATE
|
||||
Qt6::Core
|
||||
Qt6::Widgets
|
||||
Qt6::RemoteObjects
|
||||
Qt6::Quick
|
||||
Qt6::QuickWidgets
|
||||
component-interfaces
|
||||
logos_module(
|
||||
NAME blockchain_ui
|
||||
REP_FILE src/BlockchainBackend.rep
|
||||
SOURCES
|
||||
src/BlockchainPluginInterface.h
|
||||
src/BlockchainPlugin.h
|
||||
src/BlockchainPlugin.cpp
|
||||
src/BlockchainBackend.h
|
||||
src/BlockchainBackend.cpp
|
||||
src/AccountsModel.h
|
||||
src/AccountsModel.cpp
|
||||
src/LogModel.h
|
||||
src/LogModel.cpp
|
||||
FIND_PACKAGES
|
||||
Qt6Gui
|
||||
LINK_LIBRARIES
|
||||
Qt6::Gui
|
||||
)
|
||||
|
||||
# When using installed SDK layout (e.g. Nix), link the pre-built SDK library
|
||||
if(NOT _cpp_sdk_is_source)
|
||||
find_library(LOGOS_SDK_LIB logos_sdk PATHS ${LOGOS_CPP_SDK_ROOT}/lib NO_DEFAULT_PATH)
|
||||
if(LOGOS_SDK_LIB)
|
||||
target_link_libraries(blockchain_ui PRIVATE ${LOGOS_SDK_LIB})
|
||||
else()
|
||||
message(FATAL_ERROR "logos_sdk library not found in ${LOGOS_CPP_SDK_ROOT}/lib - required when using installed SDK layout")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
# Link against Abseil libraries if found
|
||||
find_package(absl QUIET)
|
||||
if(absl_FOUND)
|
||||
target_link_libraries(blockchain_ui PRIVATE
|
||||
absl::base
|
||||
absl::strings
|
||||
absl::log
|
||||
absl::check
|
||||
)
|
||||
endif()
|
||||
|
||||
# Set common properties for both platforms
|
||||
set_target_properties(blockchain_ui PROPERTIES
|
||||
LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/modules"
|
||||
RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/modules" # For Windows .dll
|
||||
BUILD_WITH_INSTALL_RPATH TRUE
|
||||
SKIP_BUILD_RPATH FALSE)
|
||||
|
||||
if(APPLE)
|
||||
# macOS specific settings
|
||||
set_target_properties(blockchain_ui PROPERTIES
|
||||
INSTALL_RPATH "@loader_path"
|
||||
INSTALL_NAME_DIR "@rpath"
|
||||
BUILD_WITH_INSTALL_NAME_DIR TRUE)
|
||||
|
||||
add_custom_command(TARGET blockchain_ui POST_BUILD
|
||||
COMMAND install_name_tool -id "@rpath/blockchain_ui.dylib" $<TARGET_FILE:blockchain_ui>
|
||||
COMMENT "Updating library paths for macOS"
|
||||
)
|
||||
else()
|
||||
# Linux specific settings
|
||||
set_target_properties(blockchain_ui PROPERTIES
|
||||
INSTALL_RPATH "$ORIGIN"
|
||||
INSTALL_RPATH_USE_LINK_PATH FALSE)
|
||||
endif()
|
||||
|
||||
install(TARGETS blockchain_ui
|
||||
LIBRARY DESTINATION ${CMAKE_INSTALL_LIBDIR}/logos/modules
|
||||
RUNTIME DESTINATION ${CMAKE_INSTALL_LIBDIR}/logos/modules
|
||||
ARCHIVE DESTINATION ${CMAKE_INSTALL_LIBDIR}/logos/modules
|
||||
)
|
||||
|
||||
install(FILES ${CMAKE_CURRENT_SOURCE_DIR}/metadata.json
|
||||
DESTINATION ${CMAKE_INSTALL_DATADIR}/logos-blockchain-ui
|
||||
)
|
||||
|
||||
# Print status messages
|
||||
message(STATUS "Blockchain UI Plugin configured successfully")
|
||||
|
||||
154
README.md
154
README.md
@ -1,6 +1,8 @@
|
||||
# logos-blockchain-ui
|
||||
|
||||
A Qt UI plugin for the Logos Blockchain Module, providing a graphical interface to control and monitor the Logos blockchain node.
|
||||
A QML + C++ backend UI module for the [Logos](https://logos.co) platform that provides a graphical interface to control and monitor the Logos blockchain node.
|
||||
|
||||
Built with [`logos-module-builder`](https://github.com/logos-co/logos-module-builder) using the `mkLogosQmlModule` pattern (QML frontend + C++ backend with Qt Remote Objects).
|
||||
|
||||
## Features
|
||||
|
||||
@ -8,139 +10,119 @@ A Qt UI plugin for the Logos Blockchain Module, providing a graphical interface
|
||||
- Configure node parameters (config path, deployment)
|
||||
- Check wallet balances
|
||||
- Monitor node status and information
|
||||
- Account management
|
||||
|
||||
## Standalone App Quickstart
|
||||
|
||||
1. Build and run the app with
|
||||
1. Build and run the app:
|
||||
|
||||
```bash
|
||||
nix run '.#app'
|
||||
nix run
|
||||
```
|
||||
|
||||
2. Generate a new config using the some initial peers that are part of the live testnet. You can find some peers [here](https://www.notion.so/nomos-tech/Logos-Blockchain-Devnet-Lisbon-March-2026-2fe261aa09df8025ad94e380933b4cf9?source=copy_link#319261aa09df80a6ac9bcb7487d14d6a).
|
||||
2. Generate a new config using initial peers from the live testnet. Find peers [here](https://www.notion.so/nomos-tech/Logos-Blockchain-Devnet-Lisbon-March-2026-2fe261aa09df8025ad94e380933b4cf9?source=copy_link#319261aa09df80a6ac9bcb7487d14d6a).
|
||||
|
||||
3. Start the node, and let it sync with the initial peers. You can track progress by opening a terminal and running:
|
||||
3. Start the node and let it sync. Track progress:
|
||||
|
||||
```bash
|
||||
watch -n1 'curl -s localhost:8080/cryptarchia/info'
|
||||
```
|
||||
|
||||
And comparing the `height` with the block height in the [block explorer](https://devnet.blockchain.logos.co/web/explorer/).
|
||||
Compare the `height` with the [block explorer](https://devnet.blockchain.logos.co/web/explorer/).
|
||||
|
||||
4. In the meantime, you can request funds from the faucet, copy one of the keys visible in the ui and paste it into the [faucet](https://devnet.blockchain.logos.co/web/faucet/).
|
||||
4. Request funds from the [faucet](https://devnet.blockchain.logos.co/web/faucet/) — copy one of the keys from the UI and paste it there.
|
||||
|
||||
5. Once your node finishes syncing, you can refresh your balance and you should see your funds from the faucet.
|
||||
5. Once synced, refresh your balance to see your funds.
|
||||
|
||||
At this point, you are done. If you leave this node running, your tokens will age sufficiently such that they will become eligible for participation in consensus. This happens automatically, so long as you don't transfer your tokens for ~3.5 hours.
|
||||
Leaving the node running for ~3.5 hours allows your tokens to age and become eligible for consensus participation (automatic).
|
||||
|
||||
For a video walkthrough of this process, see this [recording](https://drive.google.com/file/d/1hw6rQZnuka3Y_JBpUz0WyLXglTSPiZEc/view?usp=drive_link).
|
||||
For a video walkthrough, see this [recording](https://drive.google.com/file/d/1hw6rQZnuka3Y_JBpUz0WyLXglTSPiZEc/view?usp=drive_link).
|
||||
|
||||
## How to Build
|
||||
## How to Run
|
||||
|
||||
### Using Nix (Recommended)
|
||||
|
||||
#### Build Complete UI Plugin
|
||||
### Standalone (recommended for development)
|
||||
|
||||
```bash
|
||||
# Build everything (default)
|
||||
nix build
|
||||
# Run directly
|
||||
nix run
|
||||
|
||||
# Or explicitly
|
||||
nix build '.#default'
|
||||
# With local workspace overrides
|
||||
nix run --override-input liblogos_blockchain_module path:../logos-blockchain-module \
|
||||
--override-input liblogos_blockchain_module/logos-module-builder path:../logos-module-builder
|
||||
```
|
||||
|
||||
The result will include:
|
||||
- `/lib/blockchain_ui.dylib` (or `.so` on Linux) - The Blockchain UI plugin
|
||||
|
||||
#### Build Individual Components
|
||||
### In Basecamp
|
||||
|
||||
```bash
|
||||
# Build only the library (plugin)
|
||||
nix build '.#lib'
|
||||
# Build LGX
|
||||
nix build .#lgx
|
||||
|
||||
# Build the standalone Qt application
|
||||
nix build '.#app'
|
||||
# Install into Basecamp's plugin directory
|
||||
lgpm --ui-plugins-dir ~/Library/Application\ Support/Logos/LogosBasecampDev/plugins \
|
||||
install --file result/*.lgx
|
||||
```
|
||||
|
||||
#### Development Shell
|
||||
Or from the workspace:
|
||||
|
||||
```bash
|
||||
# Enter development shell with all dependencies
|
||||
nix develop
|
||||
ws bundle logos-blockchain-ui --auto-local
|
||||
```
|
||||
|
||||
**Note:** In zsh, you need to quote the target (e.g., `'.#default'`) to prevent glob expansion.
|
||||
|
||||
If you don't have flakes enabled globally, add experimental flags:
|
||||
### Build Targets
|
||||
|
||||
```bash
|
||||
nix build --extra-experimental-features 'nix-command flakes'
|
||||
nix build # default — combined plugin + QML output
|
||||
nix build .#lgx # .lgx package for distribution
|
||||
nix build .#install # lgpm-installed output (modules/ + plugins/)
|
||||
nix run # standalone app with blockchain module
|
||||
nix develop # enter development shell
|
||||
```
|
||||
|
||||
The compiled artifacts can be found at `result/`
|
||||
## Module Structure
|
||||
|
||||
#### Running the Standalone App
|
||||
|
||||
After building the app with `nix build '.#app'`, you can run it:
|
||||
|
||||
```bash
|
||||
# Run the standalone Qt application
|
||||
./result/bin/logos-blockchain-ui-app
|
||||
```
|
||||
|
||||
The app will automatically load the required modules (capability_module, liblogos_blockchain_module) and the blockchain_ui Qt plugin. All dependencies are bundled in the Nix store layout.
|
||||
|
||||
#### Nix Organization
|
||||
|
||||
The nix build system is organized into modular files in the `/nix` directory:
|
||||
- `nix/default.nix` - Common configuration (dependencies, flags, metadata)
|
||||
- `nix/lib.nix` - UI plugin compilation
|
||||
- `nix/app.nix` - Standalone Qt application compilation
|
||||
|
||||
## Output Structure
|
||||
|
||||
When built with Nix:
|
||||
|
||||
**Library build (`nix build '.#lib'`):**
|
||||
```
|
||||
result/
|
||||
└── lib/
|
||||
└── blockchain_ui.dylib # Logos Blockchain UI plugin
|
||||
```
|
||||
|
||||
**App build (`nix build '.#app'`):**
|
||||
```
|
||||
result/
|
||||
├── bin/
|
||||
│ ├── logos-blockchain-ui-app # Standalone Qt application
|
||||
│ ├── logos_host # Logos host executable (for plugins)
|
||||
│ └── logoscore # Logos core executable
|
||||
├── lib/
|
||||
│ ├── liblogos_core.dylib # Logos core library
|
||||
│ ├── liblogos_sdk.dylib # Logos SDK library
|
||||
│ └── Logos/DesignSystem/ # QML design system
|
||||
├── modules/
|
||||
│ ├── capability_module_plugin.dylib
|
||||
│ ├── liblogos_blockchain_module.dylib
|
||||
│ └── liblogos_blockchain.dylib
|
||||
└── blockchain_ui.dylib # Qt plugin (loaded by app)
|
||||
logos-blockchain-ui/
|
||||
├── flake.nix # mkLogosQmlModule
|
||||
├── metadata.json # Module config (ui_qml type)
|
||||
├── CMakeLists.txt # logos_module() macro
|
||||
└── src/
|
||||
├── BlockchainBackend.rep # RemoteObject interface
|
||||
├── BlockchainBackend.h/cpp # Business logic (extends BlockchainBackendSimpleSource)
|
||||
├── BlockchainPlugin.h/cpp # Thin plugin entry point
|
||||
├── BlockchainPluginInterface.h # Plugin interface marker
|
||||
├── AccountsModel.h/cpp # QAbstractListModel for accounts
|
||||
├── LogModel.h/cpp # QAbstractListModel for logs
|
||||
└── qml/
|
||||
└── BlockchainView.qml # QML frontend (+ sub-views)
|
||||
```
|
||||
|
||||
## Configuration
|
||||
|
||||
### Blockchain Node Configuration
|
||||
|
||||
The blockchain node can be configured in two ways:
|
||||
|
||||
1. **Via UI**: Enter the config path in the "Config Path" field
|
||||
2. **Via Environment Variable**: Set `LB_CONFIG_PATH` to your configuration file path
|
||||
|
||||
Example configuration file can be found in the logos-blockchain-module repository at `config/node_config.yaml`.
|
||||
- **Via UI**: Enter the config path in the "Config Path" field
|
||||
- **Via Environment Variable**: Set `LB_CONFIG_PATH` to your configuration file path
|
||||
|
||||
### QML Hot Reload
|
||||
|
||||
During development, you can enable QML hot reload by setting an environment variable:
|
||||
During development, set the environment variable to load QML from disk:
|
||||
|
||||
```bash
|
||||
export BLOCKCHAIN_UI_QML_PATH=/path/to/logos-blockchain-ui/src/qml
|
||||
```
|
||||
This allows you to edit the QML file and see changes by reloading the plugin without recompiling.
|
||||
|
||||
## Dependencies
|
||||
|
||||
| Dependency | Purpose |
|
||||
|---|---|
|
||||
| Qt6 Core, Gui, RemoteObjects, Declarative | UI framework + IPC |
|
||||
| [`logos-module-builder`](https://github.com/logos-co/logos-module-builder) | Build system (mkLogosQmlModule) |
|
||||
| [`logos-blockchain-module`](https://github.com/logos-blockchain/logos-blockchain-module) | Blockchain backend module |
|
||||
|
||||
## Related Repositories
|
||||
|
||||
| Repository | Role |
|
||||
|---|---|
|
||||
| [`logos-blockchain-module`](https://github.com/logos-blockchain/logos-blockchain-module) | Blockchain backend — this UI's required dependency |
|
||||
| [`logos-module-builder`](https://github.com/logos-co/logos-module-builder) | Module build system |
|
||||
| [`logos-liblogos`](https://github.com/logos-co/logos-liblogos) | Logos Core platform |
|
||||
|
||||
@ -1,62 +0,0 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(LogosBlockchainUIApp LANGUAGES CXX)
|
||||
|
||||
set(CMAKE_CXX_STANDARD 17)
|
||||
set(CMAKE_CXX_STANDARD_REQUIRED ON)
|
||||
set(CMAKE_AUTOMOC ON)
|
||||
set(CMAKE_INCLUDE_CURRENT_DIR ON)
|
||||
|
||||
# Find Qt packages
|
||||
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Core Widgets)
|
||||
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Core Widgets)
|
||||
|
||||
# Find logos-liblogos
|
||||
if(NOT DEFINED LOGOS_LIBLOGOS_ROOT)
|
||||
message(FATAL_ERROR "LOGOS_LIBLOGOS_ROOT must be defined")
|
||||
endif()
|
||||
|
||||
message(STATUS "Using logos-liblogos at: ${LOGOS_LIBLOGOS_ROOT}")
|
||||
|
||||
# Include and link directories (app only uses logos_core from liblogos, not the SDK)
|
||||
include_directories(
|
||||
${LOGOS_LIBLOGOS_ROOT}/include
|
||||
)
|
||||
|
||||
link_directories(
|
||||
${LOGOS_LIBLOGOS_ROOT}/lib
|
||||
)
|
||||
|
||||
# Set output directories
|
||||
set(CMAKE_RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/bin)
|
||||
|
||||
# Create the executable
|
||||
add_executable(logos-blockchain-ui-app
|
||||
main.cpp
|
||||
mainwindow.cpp
|
||||
mainwindow.h
|
||||
)
|
||||
|
||||
# Link libraries
|
||||
target_link_libraries(logos-blockchain-ui-app PRIVATE
|
||||
Qt${QT_VERSION_MAJOR}::Core
|
||||
Qt${QT_VERSION_MAJOR}::Widgets
|
||||
logos_core
|
||||
)
|
||||
|
||||
# Set RPATH settings for the executable
|
||||
if(APPLE)
|
||||
set_target_properties(logos-blockchain-ui-app PROPERTIES
|
||||
INSTALL_RPATH "@executable_path/../lib"
|
||||
BUILD_WITH_INSTALL_RPATH TRUE
|
||||
)
|
||||
elseif(UNIX)
|
||||
set_target_properties(logos-blockchain-ui-app PROPERTIES
|
||||
INSTALL_RPATH "$ORIGIN/../lib"
|
||||
BUILD_WITH_INSTALL_RPATH TRUE
|
||||
)
|
||||
endif()
|
||||
|
||||
# Install rules
|
||||
install(TARGETS logos-blockchain-ui-app
|
||||
RUNTIME DESTINATION bin
|
||||
)
|
||||
56
app/main.cpp
56
app/main.cpp
@ -1,56 +0,0 @@
|
||||
#include "mainwindow.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QDir>
|
||||
#include <QDebug>
|
||||
#include <iostream>
|
||||
#include <memory>
|
||||
|
||||
// CoreManager C API functions
|
||||
extern "C" {
|
||||
void logos_core_set_plugins_dir(const char* plugins_dir);
|
||||
void logos_core_start();
|
||||
void logos_core_cleanup();
|
||||
char** logos_core_get_loaded_plugins();
|
||||
int logos_core_load_plugin(const char* plugin_name);
|
||||
}
|
||||
|
||||
int main(int argc, char *argv[])
|
||||
{
|
||||
QApplication app(argc, argv);
|
||||
|
||||
QString pluginsDir = QDir::cleanPath(QCoreApplication::applicationDirPath() + "/../modules");
|
||||
logos_core_set_plugins_dir(pluginsDir.toUtf8().constData());
|
||||
|
||||
logos_core_start();
|
||||
|
||||
if (!logos_core_load_plugin("capability_module")) {
|
||||
qWarning() << "Failed to load capability_module plugin";
|
||||
}
|
||||
|
||||
if (!logos_core_load_plugin("liblogos_blockchain_module")) {
|
||||
qWarning() << "Failed to load blockchain module plugin";
|
||||
}
|
||||
|
||||
char** loadedPlugins = logos_core_get_loaded_plugins();
|
||||
int count = 0;
|
||||
if (loadedPlugins) {
|
||||
qInfo() << "Currently loaded plugins:";
|
||||
for (char** p = loadedPlugins; *p != nullptr; ++p) {
|
||||
qInfo() << " -" << *p;
|
||||
++count;
|
||||
}
|
||||
qInfo() << "Total plugins:" << count;
|
||||
} else {
|
||||
qInfo() << "No plugins loaded.";
|
||||
}
|
||||
|
||||
MainWindow window;
|
||||
window.show();
|
||||
|
||||
int result = app.exec();
|
||||
|
||||
logos_core_cleanup();
|
||||
|
||||
return result;
|
||||
}
|
||||
@ -1,66 +0,0 @@
|
||||
#include <QtWidgets>
|
||||
#include "mainwindow.h"
|
||||
|
||||
MainWindow::MainWindow(QWidget *parent)
|
||||
: QMainWindow(parent)
|
||||
{
|
||||
setupUi();
|
||||
}
|
||||
|
||||
MainWindow::~MainWindow()
|
||||
{
|
||||
}
|
||||
|
||||
void MainWindow::setupUi()
|
||||
{
|
||||
// Determine the appropriate plugin extension based on the platform
|
||||
QString pluginExtension;
|
||||
#if defined(Q_OS_WIN)
|
||||
pluginExtension = ".dll";
|
||||
#elif defined(Q_OS_MAC)
|
||||
pluginExtension = ".dylib";
|
||||
#else // Linux and other Unix-like systems
|
||||
pluginExtension = ".so";
|
||||
#endif
|
||||
|
||||
QString pluginPath = QCoreApplication::applicationDirPath() + "/../blockchain_ui" + pluginExtension;
|
||||
QPluginLoader loader(pluginPath);
|
||||
|
||||
QWidget* blockchainWidget = nullptr;
|
||||
|
||||
if (loader.load()) {
|
||||
QObject* plugin = loader.instance();
|
||||
if (plugin) {
|
||||
// Try to create the blockchain widget using the plugin's createWidget method
|
||||
QMetaObject::invokeMethod(plugin, "createWidget",
|
||||
Qt::DirectConnection,
|
||||
Q_RETURN_ARG(QWidget*, blockchainWidget));
|
||||
}
|
||||
}
|
||||
|
||||
if (blockchainWidget) {
|
||||
setCentralWidget(blockchainWidget);
|
||||
} else {
|
||||
qWarning() << "================================================";
|
||||
qWarning() << "Failed to load blockchain UI plugin from:" << pluginPath;
|
||||
qWarning() << "Error:" << loader.errorString();
|
||||
qWarning() << "================================================";
|
||||
|
||||
// Fallback: show a message when plugin is not found
|
||||
QWidget* fallbackWidget = new QWidget(this);
|
||||
QVBoxLayout* layout = new QVBoxLayout(fallbackWidget);
|
||||
|
||||
QLabel* messageLabel = new QLabel("Blockchain UI module not loaded", fallbackWidget);
|
||||
QFont font = messageLabel->font();
|
||||
font.setPointSize(14);
|
||||
messageLabel->setFont(font);
|
||||
messageLabel->setAlignment(Qt::AlignCenter);
|
||||
|
||||
layout->addWidget(messageLabel);
|
||||
setCentralWidget(fallbackWidget);
|
||||
}
|
||||
|
||||
// Set window title and size
|
||||
setWindowTitle("Logos Blockchain UI App");
|
||||
resize(800, 600);
|
||||
}
|
||||
@ -1,18 +0,0 @@
|
||||
#ifndef MAINWINDOW_H
|
||||
#define MAINWINDOW_H
|
||||
|
||||
#include <QMainWindow>
|
||||
|
||||
class MainWindow : public QMainWindow
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
MainWindow(QWidget *parent = nullptr);
|
||||
~MainWindow();
|
||||
|
||||
private:
|
||||
void setupUi();
|
||||
};
|
||||
|
||||
#endif // MAINWINDOW_H
|
||||
2038
flake.lock
generated
2038
flake.lock
generated
File diff suppressed because it is too large
Load Diff
139
flake.nix
139
flake.nix
@ -1,82 +1,71 @@
|
||||
{
|
||||
description = "Logos Blockchain UI - A Qt UI plugin for Logos Blockchain Module";
|
||||
description = "Blockchain UI plugin for the Logos application";
|
||||
|
||||
inputs = {
|
||||
# Follow the same nixpkgs as logos-liblogos to ensure compatibility
|
||||
nixpkgs.follows = "logos-liblogos/nixpkgs";
|
||||
logos-cpp-sdk.url = "github:logos-co/logos-cpp-sdk";
|
||||
logos-liblogos.url = "github:logos-co/logos-liblogos?rev=e3741c01fd3abf6b7bd9ff2fa8edf89c41fc0cea";
|
||||
logos-blockchain-module.url = "github:logos-blockchain/logos-blockchain-module";
|
||||
logos-capability-module.url = "github:logos-co/logos-capability-module";
|
||||
logos-design-system.url = "github:logos-co/logos-design-system";
|
||||
logos-design-system.inputs.nixpkgs.follows = "nixpkgs";
|
||||
# Promoted to direct inputs so they can be overridden with one --override-input each.
|
||||
logos-cpp-sdk.url = "github:logos-co/logos-cpp-sdk";
|
||||
logos-module.url = "github:logos-co/logos-module";
|
||||
logos-liblogos.url = "github:logos-co/logos-liblogos";
|
||||
logos-capability-module.url = "github:logos-co/logos-capability-module";
|
||||
logos-package-manager.url = "github:logos-co/logos-package-manager";
|
||||
process-stats.url = "github:logos-co/process-stats";
|
||||
logos-view-module-runtime.url = "github:logos-co/logos-view-module-runtime";
|
||||
logos-standalone-app.url = "github:logos-co/logos-standalone-app";
|
||||
logos-plugin-qt.url = "github:logos-co/logos-plugin-qt";
|
||||
|
||||
nix-bundle-lgx.url = "github:logos-co/nix-bundle-lgx";
|
||||
|
||||
# All nixpkgs in the closure must come from logos-cpp-sdk's logos-nix to keep one Qt.
|
||||
nixpkgs.follows = "logos-cpp-sdk/logos-nix/nixpkgs";
|
||||
|
||||
# ── Force every direct input's nixpkgs to the unified one ──
|
||||
logos-module.inputs.nixpkgs.follows = "nixpkgs";
|
||||
logos-liblogos.inputs.nixpkgs.follows = "nixpkgs";
|
||||
logos-capability-module.inputs.nixpkgs.follows = "nixpkgs";
|
||||
logos-package-manager.inputs.nixpkgs.follows = "nixpkgs";
|
||||
process-stats.inputs.nixpkgs.follows = "nixpkgs";
|
||||
logos-view-module-runtime.inputs.nixpkgs.follows = "nixpkgs";
|
||||
logos-standalone-app.inputs.nixpkgs.follows = "nixpkgs";
|
||||
logos-plugin-qt.inputs.nixpkgs.follows = "nixpkgs";
|
||||
nix-bundle-lgx.inputs.nixpkgs.follows = "nixpkgs";
|
||||
logos-module-builder.inputs.nixpkgs.follows = "nixpkgs";
|
||||
|
||||
# ── logos-module-builder: rewire its logos-* deps to our top-level pins ──
|
||||
logos-module-builder.url = "github:logos-co/logos-module-builder";
|
||||
logos-module-builder.inputs.logos-cpp-sdk.follows = "logos-cpp-sdk";
|
||||
logos-module-builder.inputs.logos-module.follows = "logos-module";
|
||||
logos-module-builder.inputs.logos-plugin-qt.follows = "logos-plugin-qt";
|
||||
logos-module-builder.inputs.logos-plugin-core.follows = "logos-plugin-qt";
|
||||
logos-module-builder.inputs.logos-standalone-app.follows = "logos-standalone-app";
|
||||
logos-module-builder.inputs.nix-bundle-lgx.follows = "nix-bundle-lgx";
|
||||
|
||||
# ── logos-standalone-app: rewire its logos-* deps too ──
|
||||
logos-standalone-app.inputs.logos-cpp-sdk.follows = "logos-cpp-sdk";
|
||||
logos-standalone-app.inputs.logos-liblogos.follows = "logos-liblogos";
|
||||
logos-standalone-app.inputs.logos-capability-module.follows = "logos-capability-module";
|
||||
logos-standalone-app.inputs.logos-view-module-runtime.follows = "logos-view-module-runtime";
|
||||
logos-standalone-app.inputs.nix-bundle-lgx.follows = "nix-bundle-lgx";
|
||||
|
||||
# ── logos-liblogos: rewire its logos-* deps ──
|
||||
logos-liblogos.inputs.logos-cpp-sdk.follows = "logos-cpp-sdk";
|
||||
logos-liblogos.inputs.logos-module.follows = "logos-module";
|
||||
logos-liblogos.inputs.logos-capability-module.follows = "logos-capability-module";
|
||||
logos-liblogos.inputs.logos-package-manager.follows = "logos-package-manager";
|
||||
logos-liblogos.inputs.process-stats.follows = "process-stats";
|
||||
|
||||
logos-capability-module.inputs.logos-cpp-sdk.follows = "logos-cpp-sdk";
|
||||
logos-capability-module.inputs.logos-module.follows = "logos-module";
|
||||
|
||||
logos-plugin-qt.inputs.logos-module.follows = "logos-module";
|
||||
|
||||
liblogos_blockchain_module.url = "github:logos-blockchain/logos-blockchain-module/09eda0211df54b45d88d912aea28498d427ddada";
|
||||
liblogos_blockchain_module.inputs.logos-module-builder.follows = "logos-module-builder";
|
||||
};
|
||||
|
||||
outputs = { self, nixpkgs, logos-cpp-sdk, logos-liblogos, logos-blockchain-module, logos-capability-module, logos-design-system }:
|
||||
let
|
||||
systems = [ "aarch64-darwin" "x86_64-darwin" "aarch64-linux" "x86_64-linux" ];
|
||||
forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f {
|
||||
pkgs = import nixpkgs { inherit system; };
|
||||
logosSdk = logos-cpp-sdk.packages.${system}.default;
|
||||
logosLiblogos = logos-liblogos.packages.${system}.default;
|
||||
logosBlockchainModule = logos-blockchain-module.packages.${system}.default;
|
||||
logosCapabilityModule = logos-capability-module.packages.${system}.default;
|
||||
logosDesignSystem = logos-design-system.packages.${system}.default;
|
||||
});
|
||||
in
|
||||
{
|
||||
packages = forAllSystems ({ pkgs, logosSdk, logosLiblogos, logosBlockchainModule, logosCapabilityModule, logosDesignSystem }:
|
||||
let
|
||||
# Common configuration
|
||||
common = import ./nix/default.nix {
|
||||
inherit pkgs logosSdk logosLiblogos;
|
||||
};
|
||||
src = ./.;
|
||||
|
||||
# Library package (default blockchain-module has lib + include via symlinkJoin)
|
||||
lib = import ./nix/lib.nix {
|
||||
inherit pkgs common src logosBlockchainModule;
|
||||
};
|
||||
|
||||
# App package
|
||||
app = import ./nix/app.nix {
|
||||
inherit pkgs common src logosLiblogos logosBlockchainModule logosCapabilityModule logosDesignSystem;
|
||||
logosBlockchainUI = lib;
|
||||
};
|
||||
in
|
||||
{
|
||||
# Individual outputs
|
||||
logos-blockchain-ui-lib = lib;
|
||||
app = app;
|
||||
lib = lib;
|
||||
|
||||
# Default package
|
||||
default = lib;
|
||||
}
|
||||
);
|
||||
|
||||
devShells = forAllSystems ({ pkgs, logosSdk, logosLiblogos, logosBlockchainModule, logosCapabilityModule, logosDesignSystem }: {
|
||||
default = pkgs.mkShell {
|
||||
nativeBuildInputs = [
|
||||
pkgs.cmake
|
||||
pkgs.ninja
|
||||
pkgs.pkg-config
|
||||
];
|
||||
buildInputs = [
|
||||
pkgs.qt6.qtbase
|
||||
pkgs.qt6.qtremoteobjects
|
||||
pkgs.zstd
|
||||
pkgs.krb5
|
||||
pkgs.abseil-cpp
|
||||
];
|
||||
|
||||
shellHook = ''
|
||||
export LOGOS_LIBLOGOS_ROOT="${logosLiblogos}"
|
||||
export LOGOS_DESIGN_SYSTEM_ROOT="${logosDesignSystem}"
|
||||
echo "Logos Blockchain UI development environment"
|
||||
echo "LOGOS_LIBLOGOS_ROOT: $LOGOS_LIBLOGOS_ROOT"
|
||||
'';
|
||||
};
|
||||
});
|
||||
outputs = inputs@{ logos-module-builder, ... }:
|
||||
logos-module-builder.lib.mkLogosQmlModule {
|
||||
src = ./.;
|
||||
configFile = ./metadata.json;
|
||||
flakeInputs = inputs;
|
||||
};
|
||||
}
|
||||
|
||||
@ -1,17 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QWidget>
|
||||
#include <QtPlugin>
|
||||
|
||||
class LogosAPI;
|
||||
|
||||
class IComponent {
|
||||
public:
|
||||
virtual ~IComponent() = default;
|
||||
virtual QWidget* createWidget(LogosAPI* logosAPI = nullptr) = 0;
|
||||
virtual void destroyWidget(QWidget* widget) = 0;
|
||||
};
|
||||
|
||||
#define IComponent_iid "com.logos.component.IComponent"
|
||||
Q_DECLARE_INTERFACE(IComponent, IComponent_iid)
|
||||
@ -1,27 +1,24 @@
|
||||
{
|
||||
"name": "blockchain_ui",
|
||||
"version": "1.0.0",
|
||||
"description": "Blockchain UI module for the Logos application",
|
||||
"author": "Logos Blockchain Team",
|
||||
"type": "ui",
|
||||
"main": "blockchain_ui",
|
||||
"icon": ":/icons/blockchain.png",
|
||||
"dependencies": ["liblogos_blockchain_module"],
|
||||
"type": "ui_qml",
|
||||
"category": "blockchain",
|
||||
"build": {
|
||||
"type": "cmake",
|
||||
"files": [
|
||||
"src/BlockchainPlugin.cpp",
|
||||
"src/BlockchainPlugin.h",
|
||||
"src/BlockchainBackend.cpp",
|
||||
"src/BlockchainBackend.h",
|
||||
"src/LogModel.cpp",
|
||||
"src/LogModel.h",
|
||||
"src/blockchain_resources.qrc"
|
||||
]
|
||||
},
|
||||
"capabilities": [
|
||||
"ui_components",
|
||||
"blockchain"
|
||||
]
|
||||
"description": "Blockchain UI module for the Logos application",
|
||||
"main": "blockchain_ui_plugin",
|
||||
"icon": "src/icons/blockchain.png",
|
||||
"view": "qml/BlockchainView.qml",
|
||||
"dependencies": ["liblogos_blockchain_module"],
|
||||
"nix": {
|
||||
"packages": {
|
||||
"build": [],
|
||||
"runtime": ["qt6.qtdeclarative", "zstd", "krb5", "abseil-cpp"]
|
||||
},
|
||||
"external_libraries": [],
|
||||
"cmake": {
|
||||
"find_packages": ["Qt6Gui"],
|
||||
"extra_sources": [],
|
||||
"extra_include_dirs": [],
|
||||
"extra_link_libraries": ["Qt6::Gui"]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
216
nix/app.nix
216
nix/app.nix
@ -1,216 +0,0 @@
|
||||
# Builds the logos-blockchain-ui-app standalone application
|
||||
{ pkgs, common, src, logosLiblogos, logosBlockchainModule, logosCapabilityModule, logosBlockchainUI, logosDesignSystem }:
|
||||
|
||||
pkgs.stdenv.mkDerivation rec {
|
||||
pname = "logos-blockchain-ui-app";
|
||||
version = common.version;
|
||||
|
||||
inherit src;
|
||||
inherit (common) buildInputs meta;
|
||||
|
||||
nativeBuildInputs = common.nativeBuildInputs ++ [ pkgs.patchelf pkgs.removeReferencesTo ];
|
||||
|
||||
# Provide Qt/GL runtime paths so the wrapper can inject them
|
||||
qtLibPath = pkgs.lib.makeLibraryPath (
|
||||
[
|
||||
pkgs.qt6.qtbase
|
||||
pkgs.qt6.qtremoteobjects
|
||||
pkgs.zstd
|
||||
pkgs.krb5
|
||||
pkgs.zlib
|
||||
pkgs.glib
|
||||
pkgs.stdenv.cc.cc
|
||||
pkgs.freetype
|
||||
pkgs.fontconfig
|
||||
]
|
||||
++ pkgs.lib.optionals pkgs.stdenv.isLinux [
|
||||
pkgs.libglvnd
|
||||
pkgs.mesa.drivers
|
||||
pkgs.xorg.libX11
|
||||
pkgs.xorg.libXext
|
||||
pkgs.xorg.libXrender
|
||||
pkgs.xorg.libXrandr
|
||||
pkgs.xorg.libXcursor
|
||||
pkgs.xorg.libXi
|
||||
pkgs.xorg.libXfixes
|
||||
pkgs.xorg.libxcb
|
||||
]
|
||||
);
|
||||
qtPluginPath = "${pkgs.qt6.qtbase}/lib/qt-6/plugins";
|
||||
qmlImportPath = "${placeholder "out"}/lib:${pkgs.qt6.qtbase}/lib/qt-6/qml";
|
||||
|
||||
# This is a GUI application, enable Qt wrapping
|
||||
dontWrapQtApps = false;
|
||||
|
||||
# This is an aggregate runtime layout; avoid stripping to prevent hook errors
|
||||
dontStrip = true;
|
||||
|
||||
# Ensure proper Qt environment setup via wrapper
|
||||
qtWrapperArgs = [
|
||||
"--prefix" "LD_LIBRARY_PATH" ":" qtLibPath
|
||||
"--prefix" "QT_PLUGIN_PATH" ":" qtPluginPath
|
||||
"--prefix" "QML2_IMPORT_PATH" ":" qmlImportPath
|
||||
];
|
||||
|
||||
preConfigure = ''
|
||||
runHook prePreConfigure
|
||||
export MACOSX_DEPLOYMENT_TARGET=12.0
|
||||
runHook postPreConfigure
|
||||
'';
|
||||
|
||||
# Additional environment variables for Qt and RPATH cleanup
|
||||
preFixup = ''
|
||||
runHook prePreFixup
|
||||
|
||||
# Set up Qt environment variables
|
||||
export QT_PLUGIN_PATH="${pkgs.qt6.qtbase}/lib/qt-6/plugins"
|
||||
export QML_IMPORT_PATH="${pkgs.qt6.qtbase}/lib/qt-6/qml"
|
||||
|
||||
# Remove any remaining references to /build/ in binaries and set proper RPATH
|
||||
find $out -type f -executable -exec sh -c '
|
||||
if file "$1" | grep -q "ELF.*executable"; then
|
||||
# Use patchelf to clean up RPATH if it contains /build/
|
||||
if patchelf --print-rpath "$1" 2>/dev/null | grep -q "/build/"; then
|
||||
echo "Cleaning RPATH for $1"
|
||||
patchelf --remove-rpath "$1" 2>/dev/null || true
|
||||
fi
|
||||
# Set proper RPATH for the main binary
|
||||
if echo "$1" | grep -q "/logos-blockchain-ui-app$"; then
|
||||
echo "Setting RPATH for $1"
|
||||
patchelf --set-rpath "$out/lib" "$1" 2>/dev/null || true
|
||||
fi
|
||||
fi
|
||||
' _ {} \;
|
||||
|
||||
# Also clean up shared libraries
|
||||
find $out -name "*.so" -exec sh -c '
|
||||
if patchelf --print-rpath "$1" 2>/dev/null | grep -q "/build/"; then
|
||||
echo "Cleaning RPATH for $1"
|
||||
patchelf --remove-rpath "$1" 2>/dev/null || true
|
||||
fi
|
||||
' _ {} \;
|
||||
|
||||
runHook prePostFixup
|
||||
'';
|
||||
|
||||
configurePhase = ''
|
||||
runHook preConfigure
|
||||
|
||||
echo "Configuring logos-blockchain-ui-app..."
|
||||
|
||||
test -d "${logosLiblogos}" || (echo "liblogos not found" && exit 1)
|
||||
test -d "${logosBlockchainModule}" || (echo "blockchain-module not found" && exit 1)
|
||||
test -d "${logosCapabilityModule}" || (echo "capability-module not found" && exit 1)
|
||||
test -d "${logosBlockchainUI}" || (echo "blockchain-ui not found" && exit 1)
|
||||
test -d "${logosDesignSystem}" || (echo "logos-design-system not found" && exit 1)
|
||||
|
||||
cmake -S app -B build \
|
||||
-GNinja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
-DCMAKE_OSX_DEPLOYMENT_TARGET=12.0 \
|
||||
-DCMAKE_INSTALL_RPATH_USE_LINK_PATH=FALSE \
|
||||
-DCMAKE_INSTALL_RPATH="" \
|
||||
-DCMAKE_SKIP_BUILD_RPATH=TRUE \
|
||||
-DLOGOS_LIBLOGOS_ROOT=${logosLiblogos}
|
||||
|
||||
runHook postConfigure
|
||||
'';
|
||||
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
|
||||
cmake --build build
|
||||
echo "logos-blockchain-ui-app built successfully!"
|
||||
|
||||
runHook postBuild
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
|
||||
# Create output directories
|
||||
mkdir -p $out/bin $out/lib $out/modules
|
||||
|
||||
# Install our app binary
|
||||
if [ -f "build/bin/logos-blockchain-ui-app" ]; then
|
||||
cp build/bin/logos-blockchain-ui-app "$out/bin/"
|
||||
echo "Installed logos-blockchain-ui-app binary"
|
||||
fi
|
||||
|
||||
# Copy the core binaries from liblogos
|
||||
if [ -f "${logosLiblogos}/bin/logoscore" ]; then
|
||||
cp -L "${logosLiblogos}/bin/logoscore" "$out/bin/"
|
||||
echo "Installed logoscore binary"
|
||||
fi
|
||||
if [ -f "${logosLiblogos}/bin/logos_host" ]; then
|
||||
cp -L "${logosLiblogos}/bin/logos_host" "$out/bin/"
|
||||
echo "Installed logos_host binary"
|
||||
fi
|
||||
|
||||
# Copy required shared libraries from liblogos
|
||||
if ls "${logosLiblogos}/lib/"liblogos_core.* >/dev/null 2>&1; then
|
||||
cp -L "${logosLiblogos}/lib/"liblogos_core.* "$out/lib/" || true
|
||||
fi
|
||||
|
||||
# Determine platform-specific plugin extension
|
||||
OS_EXT="so"
|
||||
case "$(uname -s)" in
|
||||
Darwin) OS_EXT="dylib";;
|
||||
Linux) OS_EXT="so";;
|
||||
MINGW*|MSYS*|CYGWIN*) OS_EXT="dll";;
|
||||
esac
|
||||
|
||||
# Copy module plugins into the modules directory
|
||||
if [ -f "${logosCapabilityModule}/lib/capability_module_plugin.$OS_EXT" ]; then
|
||||
cp -L "${logosCapabilityModule}/lib/capability_module_plugin.$OS_EXT" "$out/modules/"
|
||||
fi
|
||||
if [ -f "${logosBlockchainModule}/lib/liblogos_blockchain_module.$OS_EXT" ]; then
|
||||
cp -L "${logosBlockchainModule}/lib/liblogos_blockchain_module.$OS_EXT" "$out/modules/"
|
||||
fi
|
||||
|
||||
# Copy liblogos_blockchain library to modules directory (needed by blockchain module)
|
||||
if [ -f "${logosBlockchainModule}/lib/liblogos_blockchain.$OS_EXT" ]; then
|
||||
cp -L "${logosBlockchainModule}/lib/liblogos_blockchain.$OS_EXT" "$out/modules/"
|
||||
fi
|
||||
|
||||
# Copy circuits from blockchain module to lib (needed at runtime)
|
||||
if [ -d "${logosBlockchainModule}/share/circuits" ]; then
|
||||
cp -r "${logosBlockchainModule}/share/circuits" "$out/modules/"
|
||||
fi
|
||||
|
||||
# Copy blockchain_ui Qt plugin to root directory (not modules, as it's loaded differently)
|
||||
if [ -f "${logosBlockchainUI}/lib/blockchain_ui.$OS_EXT" ]; then
|
||||
cp -L "${logosBlockchainUI}/lib/blockchain_ui.$OS_EXT" "$out/"
|
||||
fi
|
||||
|
||||
# Copy design system QML modules (Logos.Theme, Logos.Controls) for runtime
|
||||
if [ -d "${logosDesignSystem}/lib/Logos/Theme" ]; then
|
||||
mkdir -p "$out/lib/Logos"
|
||||
cp -R "${logosDesignSystem}/lib/Logos/Theme" "$out/lib/Logos/"
|
||||
echo "Copied Logos.Theme to lib/Logos/Theme/"
|
||||
fi
|
||||
if [ -d "${logosDesignSystem}/lib/Logos/Controls" ]; then
|
||||
mkdir -p "$out/lib/Logos"
|
||||
cp -R "${logosDesignSystem}/lib/Logos/Controls" "$out/lib/Logos/"
|
||||
echo "Copied Logos.Controls to lib/Logos/Controls/"
|
||||
fi
|
||||
|
||||
cat > $out/README.txt <<EOF
|
||||
Logos Blockchain UI App
|
||||
=======================
|
||||
liblogos: ${logosLiblogos}
|
||||
blockchain-module: ${logosBlockchainModule}
|
||||
capability-module: ${logosCapabilityModule}
|
||||
blockchain-ui: ${logosBlockchainUI}
|
||||
design-system: ${logosDesignSystem}
|
||||
|
||||
Layout:
|
||||
bin/logos-blockchain-ui-app
|
||||
lib/
|
||||
modules/
|
||||
blockchain_ui.$OS_EXT
|
||||
EOF
|
||||
|
||||
runHook postInstall
|
||||
'';
|
||||
}
|
||||
@ -1,41 +0,0 @@
|
||||
# Common build configuration shared across all packages
|
||||
{ pkgs, logosSdk, logosLiblogos }:
|
||||
|
||||
{
|
||||
pname = "logos-blockchain-ui";
|
||||
version = "1.0.0";
|
||||
|
||||
# Common native build inputs
|
||||
nativeBuildInputs = [
|
||||
pkgs.cmake
|
||||
pkgs.ninja
|
||||
pkgs.pkg-config
|
||||
pkgs.qt6.wrapQtAppsHook
|
||||
];
|
||||
|
||||
# Common runtime dependencies
|
||||
buildInputs = [
|
||||
pkgs.qt6.qtbase
|
||||
pkgs.qt6.qtremoteobjects
|
||||
pkgs.zstd
|
||||
pkgs.krb5
|
||||
pkgs.abseil-cpp
|
||||
];
|
||||
|
||||
# Common CMake flags
|
||||
cmakeFlags = [
|
||||
"-GNinja"
|
||||
"-DLOGOS_CPP_SDK_ROOT=${logosSdk}"
|
||||
"-DLOGOS_LIBLOGOS_ROOT=${logosLiblogos}"
|
||||
];
|
||||
|
||||
env = {
|
||||
LOGOS_LIBLOGOS_ROOT = "${logosLiblogos}";
|
||||
};
|
||||
|
||||
# Metadata
|
||||
meta = with pkgs.lib; {
|
||||
description = "Logos Blockchain UI - A Qt UI plugin for Logos Blockchain Module";
|
||||
platforms = platforms.unix;
|
||||
};
|
||||
}
|
||||
51
nix/lib.nix
51
nix/lib.nix
@ -1,51 +0,0 @@
|
||||
# Builds the logos-blockchain-ui library
|
||||
{ pkgs, common, src, logosBlockchainModule }:
|
||||
|
||||
pkgs.stdenv.mkDerivation {
|
||||
pname = "${common.pname}-lib";
|
||||
version = common.version;
|
||||
|
||||
inherit src;
|
||||
inherit (common) buildInputs cmakeFlags meta env;
|
||||
nativeBuildInputs = common.nativeBuildInputs;
|
||||
|
||||
# Library (Qt plugin), not an app — no Qt wrapper
|
||||
dontWrapQtApps = true;
|
||||
|
||||
configurePhase = ''
|
||||
runHook preConfigure
|
||||
cmake -S . -B build \
|
||||
-GNinja \
|
||||
-DCMAKE_BUILD_TYPE=Release \
|
||||
''${cmakeFlags}
|
||||
runHook postConfigure
|
||||
'';
|
||||
|
||||
buildPhase = ''
|
||||
runHook preBuild
|
||||
cmake --build build
|
||||
runHook postBuild
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
|
||||
mkdir -p $out/lib
|
||||
if [ -f build/modules/blockchain_ui.dylib ]; then
|
||||
cp build/modules/blockchain_ui.dylib $out/lib/
|
||||
elif [ -f build/modules/blockchain_ui.so ]; then
|
||||
cp build/modules/blockchain_ui.so $out/lib/
|
||||
else
|
||||
echo "Error: No library file found in build/modules/"
|
||||
ls -la build/modules/ 2>/dev/null || true
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Copy circuits from blockchain module so result/lib/circuits is available
|
||||
if [ -d "${logosBlockchainModule}/share/circuits" ]; then
|
||||
cp -r "${logosBlockchainModule}/share/circuits" $out/modules/
|
||||
fi
|
||||
|
||||
runHook postInstall
|
||||
'';
|
||||
}
|
||||
@ -1,154 +1,120 @@
|
||||
#include "BlockchainBackend.h"
|
||||
#include "logos_api.h"
|
||||
#include "logos_api_client.h"
|
||||
|
||||
#include <QByteArray>
|
||||
#include <QClipboard>
|
||||
#include <QDebug>
|
||||
#include <QDateTime>
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QGuiApplication>
|
||||
#include <QJsonDocument>
|
||||
#include <QJsonObject>
|
||||
#include <QModelIndex>
|
||||
#include <QSettings>
|
||||
#include <QSignalBlocker>
|
||||
#include <QTimer>
|
||||
#include <QUrl>
|
||||
#include <QVariant>
|
||||
|
||||
namespace {
|
||||
const char SETTINGS_ORG[] = "Logos";
|
||||
const char SETTINGS_APP[] = "BlockchainUI";
|
||||
const char USER_CONFIG_KEY[] = "userConfigPath";
|
||||
const char DEPLOYMENT_CONFIG_KEY[] = "deploymentConfigPath";
|
||||
const QString BLOCKCHAIN_MODULE_NAME = QStringLiteral("liblogos_blockchain_module");
|
||||
const QString BlockchainBackend::BLOCKCHAIN_MODULE_NAME =
|
||||
QStringLiteral("liblogos_blockchain_module");
|
||||
|
||||
static QString toLocalPath(const QString& pathInput)
|
||||
{
|
||||
if (pathInput.trimmed().isEmpty())
|
||||
return pathInput;
|
||||
return QUrl::fromUserInput(pathInput).toLocalFile();
|
||||
}
|
||||
|
||||
BlockchainBackend::BlockchainBackend(LogosAPI* logosAPI, QObject* parent)
|
||||
: QObject(parent),
|
||||
m_status(NotStarted),
|
||||
m_userConfig(""),
|
||||
m_deploymentConfig(""),
|
||||
m_logModel(new LogModel(this)),
|
||||
m_accountsModel(new AccountsModel(this)),
|
||||
m_logosAPI(nullptr),
|
||||
m_blockchainClient(nullptr)
|
||||
: BlockchainBackendSimpleSource(parent)
|
||||
, m_logosAPI(logosAPI)
|
||||
, m_accountsModel(new AccountsModel(this))
|
||||
, m_logModel(new LogModel(this))
|
||||
{
|
||||
QSettings s(SETTINGS_ORG, SETTINGS_APP);
|
||||
const QString envConfigPath = QString::fromUtf8(qgetenv("LB_CONFIG_PATH"));
|
||||
const QString savedUserConfig = s.value(USER_CONFIG_KEY).toString();
|
||||
const QString savedDeploymentConfig = s.value(DEPLOYMENT_CONFIG_KEY).toString();
|
||||
setStatus(NotStarted);
|
||||
setUseGeneratedConfig(false);
|
||||
setGeneratedUserConfigPath(
|
||||
QDir::currentPath() + QStringLiteral("/user_config.yaml"));
|
||||
|
||||
if (!envConfigPath.isEmpty()) {
|
||||
m_userConfig = envConfigPath;
|
||||
} else if (!savedUserConfig.isEmpty()) {
|
||||
m_userConfig = savedUserConfig;
|
||||
}
|
||||
if (!savedDeploymentConfig.isEmpty()) {
|
||||
m_deploymentConfig = savedDeploymentConfig;
|
||||
}
|
||||
// Restore saved config paths
|
||||
QSettings s("Logos", "BlockchainUI");
|
||||
const QString envConfigPath =
|
||||
QString::fromUtf8(qgetenv("LB_CONFIG_PATH"));
|
||||
const QString savedUserConfig =
|
||||
s.value("userConfigPath").toString();
|
||||
const QString savedDeploymentConfig =
|
||||
s.value("deploymentConfigPath").toString();
|
||||
|
||||
if (!logosAPI) {
|
||||
logosAPI = new LogosAPI("blockchain_ui", this);
|
||||
}
|
||||
if (!envConfigPath.isEmpty())
|
||||
setUserConfig(toLocalPath(envConfigPath));
|
||||
else if (!savedUserConfig.isEmpty())
|
||||
setUserConfig(toLocalPath(savedUserConfig));
|
||||
|
||||
m_logosAPI = logosAPI;
|
||||
m_blockchainClient = m_logosAPI->getClient(BLOCKCHAIN_MODULE_NAME);
|
||||
if (!savedDeploymentConfig.isEmpty())
|
||||
setDeploymentConfig(toLocalPath(savedDeploymentConfig));
|
||||
|
||||
if (!m_blockchainClient) {
|
||||
setStatus(ErrorNotInitialized);
|
||||
// Re-apply pre-.rep behavior: normalize file URLs, then persist (as master did in setters).
|
||||
connect(this, &BlockchainBackendSimpleSource::userConfigChanged, this, [this]() {
|
||||
const QString p = userConfig();
|
||||
const QString n = toLocalPath(p);
|
||||
if (n != p) {
|
||||
QSignalBlocker b(this);
|
||||
setUserConfig(n);
|
||||
}
|
||||
QSettings("Logos", "BlockchainUI")
|
||||
.setValue("userConfigPath", userConfig());
|
||||
});
|
||||
connect(this, &BlockchainBackendSimpleSource::deploymentConfigChanged, this, [this]() {
|
||||
const QString p = deploymentConfig();
|
||||
const QString n = toLocalPath(p);
|
||||
if (n != p) {
|
||||
QSignalBlocker b(this);
|
||||
setDeploymentConfig(n);
|
||||
}
|
||||
QSettings("Logos", "BlockchainUI")
|
||||
.setValue("deploymentConfigPath", deploymentConfig());
|
||||
});
|
||||
|
||||
if (!m_logosAPI) {
|
||||
qWarning() << "BlockchainBackend: constructed without LogosAPI";
|
||||
return;
|
||||
}
|
||||
|
||||
QObject* replica = m_blockchainClient->requestObject(BLOCKCHAIN_MODULE_NAME);
|
||||
m_blockchainClient = m_logosAPI->getClient(BLOCKCHAIN_MODULE_NAME);
|
||||
if (!m_blockchainClient) {
|
||||
setStatus(ErrorNotInitialized);
|
||||
qWarning() << "BlockchainBackend: failed to get blockchain module client";
|
||||
return;
|
||||
}
|
||||
|
||||
LogosObject* replica =
|
||||
m_blockchainClient->requestObject(BLOCKCHAIN_MODULE_NAME);
|
||||
if (replica) {
|
||||
replica->setParent(this);
|
||||
m_blockchainClient->onEvent(replica, this, "newBlock", [this](const QString&, const QVariantList& data) {
|
||||
onNewBlock(data);
|
||||
});
|
||||
m_blockchainClient->onEvent(
|
||||
replica, "newBlock",
|
||||
[this](const QString&, const QVariantList& data) {
|
||||
const QString timestamp =
|
||||
QDateTime::currentDateTime().toString("HH:mm:ss");
|
||||
QString line;
|
||||
if (!data.isEmpty())
|
||||
line = QString("[%1] New block: %2")
|
||||
.arg(timestamp, data.first().toString());
|
||||
else
|
||||
line = QString("[%1] New block (no data)").arg(timestamp);
|
||||
m_logModel->append(line);
|
||||
});
|
||||
} else {
|
||||
setStatus(ErrorSubscribeFailed);
|
||||
}
|
||||
|
||||
qDebug() << "BlockchainBackend: initialized";
|
||||
}
|
||||
|
||||
BlockchainBackend::~BlockchainBackend()
|
||||
{
|
||||
stopBlockchain();
|
||||
}
|
||||
|
||||
void BlockchainBackend::setStatus(BlockchainStatus newStatus)
|
||||
{
|
||||
if (m_status != newStatus) {
|
||||
m_status = newStatus;
|
||||
emit statusChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void BlockchainBackend::setUserConfig(const QString& path)
|
||||
{
|
||||
const QString localPath = QUrl::fromUserInput(path).toLocalFile();
|
||||
if (m_userConfig != localPath) {
|
||||
m_userConfig = localPath;
|
||||
QSettings s(SETTINGS_ORG, SETTINGS_APP);
|
||||
s.setValue(USER_CONFIG_KEY, m_userConfig);
|
||||
emit userConfigChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void BlockchainBackend::setDeploymentConfig(const QString& path)
|
||||
{
|
||||
const QString localPath = QUrl::fromUserInput(path).toLocalFile();
|
||||
if (m_deploymentConfig != localPath) {
|
||||
m_deploymentConfig = localPath;
|
||||
QSettings s(SETTINGS_ORG, SETTINGS_APP);
|
||||
s.setValue(DEPLOYMENT_CONFIG_KEY, m_deploymentConfig);
|
||||
emit deploymentConfigChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void BlockchainBackend::setUseGeneratedConfig(bool useGenerated)
|
||||
{
|
||||
if (m_useGeneratedConfig != useGenerated) {
|
||||
m_useGeneratedConfig = useGenerated;
|
||||
emit useGeneratedConfigChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void BlockchainBackend::clearLogs()
|
||||
{
|
||||
m_logModel->clear();
|
||||
}
|
||||
|
||||
void BlockchainBackend::copyToClipboard(const QString& text)
|
||||
{
|
||||
if (QGuiApplication::clipboard())
|
||||
QGuiApplication::clipboard()->setText(text);
|
||||
}
|
||||
|
||||
QString BlockchainBackend::getBalance(const QString& addressHex)
|
||||
{
|
||||
QString result;
|
||||
if (!m_blockchainClient) {
|
||||
result = QStringLiteral("Error: Module not initialized.");
|
||||
} else {
|
||||
QVariant v = m_blockchainClient->invokeRemoteMethod(BLOCKCHAIN_MODULE_NAME, "wallet_get_balance", addressHex);
|
||||
result = v.isValid() ? v.toString() : QStringLiteral("Error: Call failed.");
|
||||
}
|
||||
m_accountsModel->setBalanceForAddress(addressHex, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
QString BlockchainBackend::transferFunds(const QString& fromKeyHex, const QString& toKeyHex, const QString& amountStr)
|
||||
{
|
||||
if (!m_blockchainClient) {
|
||||
return QStringLiteral("Error: Module not initialized.");
|
||||
}
|
||||
QVariant result = m_blockchainClient->invokeRemoteMethod(
|
||||
BLOCKCHAIN_MODULE_NAME,
|
||||
"wallet_transfer_funds",
|
||||
fromKeyHex,
|
||||
fromKeyHex,
|
||||
toKeyHex,
|
||||
amountStr,
|
||||
QString());
|
||||
return result.isValid() ? result.toString() : QStringLiteral("Error: Call failed.");
|
||||
if (status() == Running || status() == Starting)
|
||||
stopBlockchain();
|
||||
}
|
||||
|
||||
void BlockchainBackend::startBlockchain()
|
||||
@ -161,7 +127,7 @@ void BlockchainBackend::startBlockchain()
|
||||
setStatus(Starting);
|
||||
|
||||
QVariant result = m_blockchainClient->invokeRemoteMethod(
|
||||
BLOCKCHAIN_MODULE_NAME, "start", m_userConfig, m_deploymentConfig);
|
||||
BLOCKCHAIN_MODULE_NAME, "start", userConfig(), deploymentConfig());
|
||||
int resultCode = result.isValid() ? result.toInt() : -1;
|
||||
|
||||
if (resultCode == 0 || resultCode == 1) {
|
||||
@ -169,40 +135,15 @@ void BlockchainBackend::startBlockchain()
|
||||
QTimer::singleShot(500, this, [this]() { refreshAccounts(); });
|
||||
} else if (resultCode == 2) {
|
||||
setStatus(ErrorConfigMissing);
|
||||
} else if (resultCode == 3) {
|
||||
setStatus(ErrorStartFailed);
|
||||
} else {
|
||||
setStatus(ErrorStartFailed);
|
||||
}
|
||||
}
|
||||
|
||||
void BlockchainBackend::refreshAccounts()
|
||||
{
|
||||
if (!m_blockchainClient) return;
|
||||
QVariant result = m_blockchainClient->invokeRemoteMethod(BLOCKCHAIN_MODULE_NAME, "wallet_get_known_addresses");
|
||||
QStringList list = result.isValid() && result.canConvert<QStringList>() ? result.toStringList() : QStringList();
|
||||
qDebug() << "BlockchainBackend: received from blockchain lib: type=QStringList, count=" << list.size();
|
||||
m_accountsModel->setAddresses(list);
|
||||
QTimer::singleShot(0, this, [this, list]() { fetchBalancesForAccounts(list); });
|
||||
}
|
||||
|
||||
void BlockchainBackend::fetchBalancesForAccounts(const QStringList& list)
|
||||
{
|
||||
if (!m_blockchainClient) return;
|
||||
const int n = list.size();
|
||||
for (int i = 0; i < n; ++i) {
|
||||
const QString address = list[i];
|
||||
if (address.isEmpty()) continue;
|
||||
const QString balance = getBalance(address);
|
||||
m_accountsModel->setBalanceForAddress(address, balance);
|
||||
}
|
||||
}
|
||||
|
||||
void BlockchainBackend::stopBlockchain()
|
||||
{
|
||||
if (m_status != Running && m_status != Starting) {
|
||||
if (status() != Running && status() != Starting)
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_blockchainClient) {
|
||||
setStatus(ErrorNotInitialized);
|
||||
@ -211,60 +152,87 @@ void BlockchainBackend::stopBlockchain()
|
||||
|
||||
setStatus(Stopping);
|
||||
|
||||
QVariant result = m_blockchainClient->invokeRemoteMethod(BLOCKCHAIN_MODULE_NAME, "stop");
|
||||
QVariant result = m_blockchainClient->invokeRemoteMethod(
|
||||
BLOCKCHAIN_MODULE_NAME, "stop");
|
||||
int resultCode = result.isValid() ? result.toInt() : -1;
|
||||
|
||||
if (resultCode == 0 || resultCode == 1) {
|
||||
if (resultCode == 0 || resultCode == 1)
|
||||
setStatus(Stopped);
|
||||
} else {
|
||||
else
|
||||
setStatus(ErrorStopFailed);
|
||||
}
|
||||
|
||||
void BlockchainBackend::refreshAccounts()
|
||||
{
|
||||
if (!m_blockchainClient) return;
|
||||
|
||||
QVariant result = m_blockchainClient->invokeRemoteMethod(
|
||||
BLOCKCHAIN_MODULE_NAME, "wallet_get_known_addresses");
|
||||
QStringList list =
|
||||
result.isValid() && result.canConvert<QStringList>()
|
||||
? result.toStringList()
|
||||
: QStringList();
|
||||
|
||||
m_accountsModel->setAddresses(list);
|
||||
|
||||
QTimer::singleShot(0, this,
|
||||
[this, list]() { fetchBalancesForAccounts(list); });
|
||||
}
|
||||
|
||||
void BlockchainBackend::fetchBalancesForAccounts(const QStringList& list)
|
||||
{
|
||||
if (!m_blockchainClient) return;
|
||||
for (const QString& address : list) {
|
||||
if (address.isEmpty()) continue;
|
||||
getBalance(address);
|
||||
}
|
||||
}
|
||||
|
||||
void BlockchainBackend::onNewBlock(const QVariantList& data)
|
||||
{
|
||||
QString timestamp = QDateTime::currentDateTime().toString("HH:mm:ss");
|
||||
QString line;
|
||||
if (!data.isEmpty()) {
|
||||
QString blockInfo = data.first().toString();
|
||||
line = QString("[%1] 📦 New block: %2").arg(timestamp, blockInfo);
|
||||
} else {
|
||||
line = QString("[%1] 📦 New block (no data)").arg(timestamp);
|
||||
}
|
||||
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)
|
||||
QString BlockchainBackend::getBalance(QString addressHex)
|
||||
{
|
||||
QString result;
|
||||
if (!m_blockchainClient) {
|
||||
return -1;
|
||||
result = QStringLiteral("Error: Module not initialized.");
|
||||
} else {
|
||||
QVariant v = m_blockchainClient->invokeRemoteMethod(
|
||||
BLOCKCHAIN_MODULE_NAME, "wallet_get_balance", addressHex);
|
||||
result = v.isValid() ? v.toString()
|
||||
: QStringLiteral("Error: Call failed.");
|
||||
}
|
||||
|
||||
m_accountsModel->setBalanceForAddress(addressHex, result);
|
||||
return result;
|
||||
}
|
||||
|
||||
QString BlockchainBackend::transferFunds(
|
||||
QString fromKeyHex, QString toKeyHex, QString amountStr)
|
||||
{
|
||||
if (!m_blockchainClient)
|
||||
return QStringLiteral("Error: Module not initialized.");
|
||||
|
||||
QVariant result = m_blockchainClient->invokeRemoteMethod(
|
||||
BLOCKCHAIN_MODULE_NAME, "wallet_transfer_funds",
|
||||
fromKeyHex, fromKeyHex, toKeyHex, amountStr, QString());
|
||||
return result.isValid() ? result.toString()
|
||||
: QStringLiteral("Error: Call failed.");
|
||||
}
|
||||
|
||||
int BlockchainBackend::generateConfig(
|
||||
QString outputPath, QStringList initialPeers, int netPort, int blendPort,
|
||||
QString httpAddr, QString externalAddress, bool noPublicIpCheck,
|
||||
int deploymentMode, QString deploymentConfigPath, QString statePath)
|
||||
{
|
||||
if (!m_blockchainClient)
|
||||
return -1;
|
||||
|
||||
QVariantMap normalized;
|
||||
|
||||
// Output path: default if empty, then normalize
|
||||
QString out = outputPath.trimmed();
|
||||
if (out.isEmpty()) {
|
||||
if (out.isEmpty())
|
||||
out = generatedUserConfigPath();
|
||||
} else {
|
||||
else
|
||||
out = toLocalPath(out);
|
||||
}
|
||||
normalized.insert(QStringLiteral("output"), out);
|
||||
normalized.insert("output", out);
|
||||
|
||||
if (!initialPeers.isEmpty()) {
|
||||
QVariantList peersList;
|
||||
@ -273,40 +241,48 @@ int BlockchainBackend::generateConfig(const QString& outputPath,
|
||||
peersList.append(p.trimmed());
|
||||
}
|
||||
if (!peersList.isEmpty())
|
||||
normalized.insert(QStringLiteral("initial_peers"), peersList);
|
||||
normalized.insert("initial_peers", peersList);
|
||||
}
|
||||
if (netPort > 0)
|
||||
normalized.insert(QStringLiteral("net_port"), netPort);
|
||||
normalized.insert("net_port", netPort);
|
||||
if (blendPort > 0)
|
||||
normalized.insert(QStringLiteral("blend_port"), blendPort);
|
||||
normalized.insert("blend_port", blendPort);
|
||||
if (!httpAddr.trimmed().isEmpty())
|
||||
normalized.insert(QStringLiteral("http_addr"), httpAddr.trimmed());
|
||||
normalized.insert("http_addr", httpAddr.trimmed());
|
||||
if (!externalAddress.trimmed().isEmpty())
|
||||
normalized.insert(QStringLiteral("external_address"), externalAddress.trimmed());
|
||||
normalized.insert("external_address", externalAddress.trimmed());
|
||||
if (noPublicIpCheck)
|
||||
normalized.insert(QStringLiteral("no_public_ip_check"), true);
|
||||
normalized.insert("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()) {
|
||||
deployment.insert("well_known_deployment", "devnet");
|
||||
normalized.insert("deployment", deployment);
|
||||
} else if (deploymentMode == 1
|
||||
&& !deploymentConfigPath.trimmed().isEmpty()) {
|
||||
QVariantMap deployment;
|
||||
deployment.insert(QStringLiteral("config_path"), toLocalPath(deploymentConfigPath.trimmed()));
|
||||
normalized.insert(QStringLiteral("deployment"), deployment);
|
||||
deployment.insert("config_path",
|
||||
toLocalPath(deploymentConfigPath.trimmed()));
|
||||
normalized.insert("deployment", deployment);
|
||||
}
|
||||
if (!statePath.trimmed().isEmpty())
|
||||
normalized.insert(QStringLiteral("state_path"), toLocalPath(statePath.trimmed()));
|
||||
normalized.insert("state_path", toLocalPath(statePath.trimmed()));
|
||||
|
||||
const QJsonDocument doc = QJsonDocument::fromVariant(normalized);
|
||||
const QByteArray jsonBytes = doc.toJson(QJsonDocument::Compact);
|
||||
const QString jsonToSend = QString::fromUtf8(jsonBytes);
|
||||
const QString jsonToSend =
|
||||
QString::fromUtf8(doc.toJson(QJsonDocument::Compact));
|
||||
|
||||
QVariant result = m_blockchainClient->invokeRemoteMethod(
|
||||
BLOCKCHAIN_MODULE_NAME, "generate_user_config_from_str", jsonToSend);
|
||||
return result.isValid() ? result.toInt() : -1;
|
||||
}
|
||||
|
||||
QString BlockchainBackend::generatedUserConfigPath() const
|
||||
void BlockchainBackend::clearLogs()
|
||||
{
|
||||
return QDir::currentPath() + QStringLiteral("/user_config.yaml");
|
||||
m_logModel->clear();
|
||||
}
|
||||
|
||||
void BlockchainBackend::copyToClipboard(QString text)
|
||||
{
|
||||
if (QGuiApplication::clipboard())
|
||||
QGuiApplication::clipboard()->setText(text);
|
||||
}
|
||||
|
||||
@ -1,96 +1,64 @@
|
||||
#pragma once
|
||||
#ifndef BLOCKCHAIN_BACKEND_H
|
||||
#define BLOCKCHAIN_BACKEND_H
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QStringList>
|
||||
#include <QVariant>
|
||||
#include "logos_api.h"
|
||||
#include "logos_api_client.h"
|
||||
#include <QVariantList>
|
||||
|
||||
#include "rep_BlockchainBackend_source.h"
|
||||
|
||||
#include "AccountsModel.h"
|
||||
#include "LogModel.h"
|
||||
|
||||
class BlockchainBackend : public QObject {
|
||||
class LogosAPI;
|
||||
class LogosAPIClient;
|
||||
|
||||
// Source-side implementation of the BlockchainBackend .rep interface.
|
||||
//
|
||||
// Inheriting from BlockchainBackendSimpleSource gives us the generated PROPs,
|
||||
// SLOTs and SIGNALs from BlockchainBackend.rep.
|
||||
//
|
||||
// AccountsModel* / LogModel* are subclass-only Q_PROPERTYs — QAbstractItemModel*
|
||||
// can't flow through a .rep, so ui-host auto-remotes each such property as
|
||||
// "<module>/<propertyName>" (see logos-view-module-runtime/ui-host/main.cpp).
|
||||
// QML acquires them via logos.model("blockchain_ui", "accounts"|"logs").
|
||||
class BlockchainBackend : public BlockchainBackendSimpleSource
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(AccountsModel* accounts READ accounts CONSTANT)
|
||||
Q_PROPERTY(LogModel* logs READ logs CONSTANT)
|
||||
|
||||
public:
|
||||
enum BlockchainStatus {
|
||||
NotStarted = 0,
|
||||
Starting,
|
||||
Running,
|
||||
Stopping,
|
||||
Stopped,
|
||||
Error,
|
||||
ErrorNotInitialized,
|
||||
ErrorConfigMissing,
|
||||
ErrorStartFailed,
|
||||
ErrorStopFailed,
|
||||
ErrorSubscribeFailed
|
||||
};
|
||||
Q_ENUM(BlockchainStatus)
|
||||
explicit BlockchainBackend(LogosAPI* logosAPI, QObject* parent = nullptr);
|
||||
~BlockchainBackend() override;
|
||||
|
||||
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(AccountsModel* accountsModel READ accountsModel CONSTANT)
|
||||
Q_PROPERTY(QString generatedUserConfigPath READ generatedUserConfigPath CONSTANT)
|
||||
|
||||
explicit BlockchainBackend(LogosAPI* logosAPI = nullptr, QObject* parent = nullptr);
|
||||
~BlockchainBackend();
|
||||
|
||||
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; }
|
||||
AccountsModel* accountsModel() const { return m_accountsModel; }
|
||||
|
||||
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);
|
||||
Q_INVOKABLE QString transferFunds(
|
||||
const QString& fromKeyHex,
|
||||
const QString& toKeyHex,
|
||||
const QString& amountStr);
|
||||
Q_INVOKABLE void startBlockchain();
|
||||
Q_INVOKABLE void stopBlockchain();
|
||||
Q_INVOKABLE void refreshAccounts();
|
||||
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;
|
||||
AccountsModel* accounts() const { return m_accountsModel; }
|
||||
LogModel* logs() const { return m_logModel; }
|
||||
|
||||
public slots:
|
||||
void onNewBlock(const QVariantList& data);
|
||||
|
||||
signals:
|
||||
void statusChanged();
|
||||
void userConfigChanged();
|
||||
void deploymentConfigChanged();
|
||||
void useGeneratedConfigChanged();
|
||||
// Overrides of the pure-virtual slots generated from the .rep.
|
||||
void startBlockchain() override;
|
||||
void stopBlockchain() override;
|
||||
void refreshAccounts() override;
|
||||
QString getBalance(QString addressHex) override;
|
||||
QString transferFunds(QString fromKeyHex, QString toKeyHex, QString amountStr) override;
|
||||
int generateConfig(QString outputPath, QStringList initialPeers, int netPort,
|
||||
int blendPort, QString httpAddr, QString externalAddress,
|
||||
bool noPublicIpCheck, int deploymentMode,
|
||||
QString deploymentConfigPath, QString statePath) override;
|
||||
void clearLogs() override;
|
||||
void copyToClipboard(QString text) override;
|
||||
|
||||
private:
|
||||
void setStatus(BlockchainStatus newStatus);
|
||||
void fetchBalancesForAccounts(const QStringList& list);
|
||||
|
||||
BlockchainStatus m_status;
|
||||
QString m_userConfig;
|
||||
QString m_deploymentConfig;
|
||||
bool m_useGeneratedConfig = false;
|
||||
LogModel* m_logModel;
|
||||
AccountsModel* m_accountsModel;
|
||||
LogosAPI* m_logosAPI = nullptr;
|
||||
LogosAPIClient* m_blockchainClient = nullptr;
|
||||
AccountsModel* m_accountsModel = nullptr;
|
||||
LogModel* m_logModel = nullptr;
|
||||
|
||||
LogosAPI* m_logosAPI;
|
||||
LogosAPIClient* m_blockchainClient;
|
||||
static const QString BLOCKCHAIN_MODULE_NAME;
|
||||
};
|
||||
|
||||
#endif // BLOCKCHAIN_BACKEND_H
|
||||
|
||||
19
src/BlockchainBackend.rep
Normal file
19
src/BlockchainBackend.rep
Normal file
@ -0,0 +1,19 @@
|
||||
class BlockchainBackend
|
||||
{
|
||||
ENUM BlockchainStatus { NotStarted=0, Starting=1, Running=2, Stopping=3, Stopped=4, Error=5, ErrorNotInitialized=6, ErrorConfigMissing=7, ErrorStartFailed=8, ErrorStopFailed=9, ErrorSubscribeFailed=10 }
|
||||
|
||||
PROP(BlockchainStatus status READONLY)
|
||||
PROP(QString userConfig READWRITE)
|
||||
PROP(QString deploymentConfig READWRITE)
|
||||
PROP(bool useGeneratedConfig READWRITE)
|
||||
PROP(QString generatedUserConfigPath READONLY)
|
||||
|
||||
SLOT(void startBlockchain())
|
||||
SLOT(void stopBlockchain())
|
||||
SLOT(void refreshAccounts())
|
||||
SLOT(QString getBalance(QString addressHex))
|
||||
SLOT(QString transferFunds(QString fromKeyHex, QString toKeyHex, QString amountStr))
|
||||
SLOT(int generateConfig(QString outputPath, QStringList initialPeers, int netPort, int blendPort, QString httpAddr, QString externalAddress, bool noPublicIpCheck, int deploymentMode, QString deploymentConfigPath, QString statePath))
|
||||
SLOT(void clearLogs())
|
||||
SLOT(void copyToClipboard(QString text))
|
||||
}
|
||||
@ -1,54 +1,19 @@
|
||||
#include "BlockchainPlugin.h"
|
||||
#include "BlockchainBackend.h"
|
||||
#include "LogModel.h"
|
||||
#include <QQuickWidget>
|
||||
#include <QQmlContext>
|
||||
#include <QQmlEngine>
|
||||
|
||||
#include <QDebug>
|
||||
#include <QDir>
|
||||
#include <QFile>
|
||||
#include <QFileInfo>
|
||||
#include <QUrl>
|
||||
|
||||
QWidget* BlockchainPlugin::createWidget(LogosAPI* logosAPI) {
|
||||
qDebug() << "BlockchainPlugin::createWidget called";
|
||||
|
||||
QQuickWidget* quickWidget = new QQuickWidget();
|
||||
quickWidget->setResizeMode(QQuickWidget::SizeRootObjectToView);
|
||||
|
||||
qmlRegisterType<BlockchainBackend>("BlockchainBackend", 1, 0, "BlockchainBackend");
|
||||
qmlRegisterType<LogModel>("BlockchainBackend", 1, 0, "LogModel");
|
||||
|
||||
BlockchainBackend* backend = new BlockchainBackend(logosAPI, quickWidget);
|
||||
quickWidget->rootContext()->setContextProperty("backend", backend);
|
||||
|
||||
QString qmlSource = "qrc:/qml/BlockchainView.qml";
|
||||
QString importPath = "qrc:/qml";
|
||||
|
||||
QString envPath = QString::fromUtf8(qgetenv("BLOCKCHAIN_UI_QML_PATH")).trimmed();
|
||||
if (!envPath.isEmpty()) {
|
||||
QFileInfo info(envPath);
|
||||
if (info.isDir()) {
|
||||
QString main = QDir(info.absoluteFilePath()).absoluteFilePath("BlockchainView.qml");
|
||||
if (QFile::exists(main)) {
|
||||
importPath = info.absoluteFilePath();
|
||||
qmlSource = QUrl::fromLocalFile(main).toString();
|
||||
} else {
|
||||
qWarning() << "BLOCKCHAIN_UI_QML_PATH: BlockchainView.qml not found in" << info.absoluteFilePath();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
quickWidget->engine()->addImportPath(importPath);
|
||||
quickWidget->setSource(QUrl(qmlSource));
|
||||
|
||||
if (quickWidget->status() == QQuickWidget::Error) {
|
||||
qWarning() << "BlockchainPlugin: Failed to load QML:" << quickWidget->errors();
|
||||
}
|
||||
|
||||
return quickWidget;
|
||||
BlockchainPlugin::BlockchainPlugin(QObject* parent)
|
||||
: QObject(parent)
|
||||
{
|
||||
}
|
||||
|
||||
void BlockchainPlugin::destroyWidget(QWidget* widget) {
|
||||
delete widget;
|
||||
BlockchainPlugin::~BlockchainPlugin() = default;
|
||||
|
||||
void BlockchainPlugin::initLogos(LogosAPI* api)
|
||||
{
|
||||
if (m_backend) return;
|
||||
m_backend = new BlockchainBackend(api, this);
|
||||
setBackend(m_backend);
|
||||
qDebug() << "BlockchainPlugin: backend initialized";
|
||||
}
|
||||
|
||||
@ -1,14 +1,38 @@
|
||||
#pragma once
|
||||
#ifndef BLOCKCHAIN_PLUGIN_H
|
||||
#define BLOCKCHAIN_PLUGIN_H
|
||||
|
||||
#include <IComponent.h>
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <QtPlugin> // for Q_PLUGIN_METADATA, Q_INTERFACES
|
||||
#include "BlockchainPluginInterface.h"
|
||||
#include "LogosViewPluginBase.h"
|
||||
|
||||
class BlockchainPlugin : public QObject, public IComponent {
|
||||
class LogosAPI;
|
||||
class BlockchainBackend;
|
||||
|
||||
// Thin plugin entry point. Holds a BlockchainBackend and lets the
|
||||
// generated view-plugin base expose it to ui-host.
|
||||
class BlockchainPlugin : public QObject,
|
||||
public BlockchainPluginInterface,
|
||||
public BlockchainBackendViewPluginBase
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_INTERFACES(IComponent)
|
||||
Q_PLUGIN_METADATA(IID IComponent_iid FILE "metadata.json")
|
||||
Q_PLUGIN_METADATA(IID BlockchainPluginInterface_iid FILE "../metadata.json")
|
||||
Q_INTERFACES(BlockchainPluginInterface)
|
||||
|
||||
public:
|
||||
Q_INVOKABLE QWidget* createWidget(LogosAPI* logosAPI = nullptr) override;
|
||||
void destroyWidget(QWidget* widget) override;
|
||||
explicit BlockchainPlugin(QObject* parent = nullptr);
|
||||
~BlockchainPlugin() override;
|
||||
|
||||
QString name() const override { return "blockchain_ui"; }
|
||||
QString version() const override { return "1.0.0"; }
|
||||
|
||||
// Called by ui-host after plugin load. Creates the backend and wires
|
||||
// it up with the provided LogosAPI.
|
||||
Q_INVOKABLE void initLogos(LogosAPI* api);
|
||||
|
||||
private:
|
||||
BlockchainBackend* m_backend = nullptr;
|
||||
};
|
||||
|
||||
#endif // BLOCKCHAIN_PLUGIN_H
|
||||
|
||||
19
src/BlockchainPluginInterface.h
Normal file
19
src/BlockchainPluginInterface.h
Normal file
@ -0,0 +1,19 @@
|
||||
#ifndef BLOCKCHAIN_PLUGIN_INTERFACE_H
|
||||
#define BLOCKCHAIN_PLUGIN_INTERFACE_H
|
||||
|
||||
#include <QtPlugin> // for Q_DECLARE_INTERFACE
|
||||
#include "interface.h"
|
||||
|
||||
// Marker interface used by Qt's plugin loader to identify the blockchain UI
|
||||
// plugin. The actual API surface (Q_INVOKABLE methods, properties, signals)
|
||||
// lives in BlockchainBackend.rep — this header only carries the IID.
|
||||
class BlockchainPluginInterface : public PluginInterface
|
||||
{
|
||||
public:
|
||||
virtual ~BlockchainPluginInterface() = default;
|
||||
};
|
||||
|
||||
#define BlockchainPluginInterface_iid "org.logos.BlockchainPluginInterface"
|
||||
Q_DECLARE_INTERFACE(BlockchainPluginInterface, BlockchainPluginInterface_iid)
|
||||
|
||||
#endif // BLOCKCHAIN_PLUGIN_INTERFACE_H
|
||||
@ -1,18 +0,0 @@
|
||||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>qml/BlockchainView.qml</file>
|
||||
<file>qml/views/qmldir</file>
|
||||
<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>qml/controls/AccountDelegate.qml</file>
|
||||
<file>qml/controls/LogosCopyButton.qml</file>
|
||||
<file>icons/blockchain.png</file>
|
||||
<file>icons/copy.svg</file>
|
||||
<file>icons/checkmark.svg</file>
|
||||
<file>icons/refresh.svg</file>
|
||||
</qresource>
|
||||
</RCC>
|
||||
@ -2,91 +2,160 @@ import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import BlockchainBackend
|
||||
import Logos.Theme
|
||||
// BlockchainStatus enum (NotStarted/Starting/Running/.../ErrorSubscribeFailed)
|
||||
// declared in BlockchainBackend.rep — registered with QML by the replica
|
||||
// factory plugin.
|
||||
import Logos.BlockchainBackend 1.0
|
||||
|
||||
import "views"
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
readonly property var backend: logos.module("blockchain_ui")
|
||||
// `ready` can't be a binding on logos.isViewModuleReady(): that's a
|
||||
// Q_INVOKABLE method, not a Q_PROPERTY, so the binding wouldn't refresh
|
||||
// when the replica transitions to Valid. Drive it from the bridge's
|
||||
// viewModuleReadyChanged signal instead.
|
||||
property bool ready: false
|
||||
|
||||
Connections {
|
||||
target: logos
|
||||
function onViewModuleReadyChanged(moduleName, isReady) {
|
||||
if (moduleName === "blockchain_ui")
|
||||
root.ready = isReady && root.backend !== null
|
||||
}
|
||||
}
|
||||
|
||||
Component.onCompleted: {
|
||||
// Cover the case where the replica is already Valid by the time
|
||||
// we attach the Connections handler.
|
||||
root.ready = root.backend !== null && logos.isViewModuleReady("blockchain_ui")
|
||||
}
|
||||
|
||||
// Models live on the C++ backend and are auto-remoted by ui-host as
|
||||
// "<module>/<propertyName>". QML acquires them via logos.model(...).
|
||||
readonly property var accountsModel: logos.model("blockchain_ui", "accounts")
|
||||
readonly property var logModel: logos.model("blockchain_ui", "logs")
|
||||
|
||||
QtObject {
|
||||
id: _d
|
||||
function getStatusString(status) {
|
||||
switch(status) {
|
||||
case BlockchainBackend.NotStarted: return qsTr("Not Started");
|
||||
case BlockchainBackend.Starting: return qsTr("Starting...");
|
||||
case BlockchainBackend.Running: return qsTr("Running");
|
||||
case BlockchainBackend.Stopping: return qsTr("Stopping...");
|
||||
case BlockchainBackend.Stopped: return qsTr("Stopped");
|
||||
case BlockchainBackend.Error: return qsTr("Error");
|
||||
case BlockchainBackend.ErrorNotInitialized: return qsTr("Error: Module not initialized");
|
||||
case BlockchainBackend.ErrorConfigMissing: return qsTr("Error: Config path missing");
|
||||
case BlockchainBackend.ErrorStartFailed: return qsTr("Error: Failed to start node");
|
||||
case BlockchainBackend.ErrorStopFailed: return qsTr("Error: Failed to stop node");
|
||||
case BlockchainBackend.ErrorSubscribeFailed: return qsTr("Error: Failed to subscribe to events");
|
||||
default: return qsTr("Unknown");
|
||||
function getStatusString(s) {
|
||||
switch(s) {
|
||||
case BlockchainBackend.NotStarted: return qsTr("Not Started")
|
||||
case BlockchainBackend.Starting: return qsTr("Starting...")
|
||||
case BlockchainBackend.Running: return qsTr("Running")
|
||||
case BlockchainBackend.Stopping: return qsTr("Stopping...")
|
||||
case BlockchainBackend.Stopped: return qsTr("Stopped")
|
||||
case BlockchainBackend.Error: return qsTr("Error")
|
||||
case BlockchainBackend.ErrorNotInitialized: return qsTr("Error: Module not initialized")
|
||||
case BlockchainBackend.ErrorConfigMissing: return qsTr("Error: Config path missing")
|
||||
case BlockchainBackend.ErrorStartFailed: return qsTr("Error: Failed to start node")
|
||||
case BlockchainBackend.ErrorStopFailed: return qsTr("Error: Failed to stop node")
|
||||
case BlockchainBackend.ErrorSubscribeFailed: return qsTr("Error: Failed to subscribe to events")
|
||||
default: return qsTr("Unknown")
|
||||
}
|
||||
}
|
||||
function getStatusColor(status) {
|
||||
switch(status) {
|
||||
case BlockchainBackend.Running: return Theme.palette.success;
|
||||
case BlockchainBackend.Starting: return Theme.palette.warning;
|
||||
case BlockchainBackend.Stopping: return Theme.palette.warning;
|
||||
case BlockchainBackend.NotStarted: return Theme.palette.error;
|
||||
case BlockchainBackend.Stopped: return Theme.palette.error;
|
||||
case BlockchainBackend.Error:
|
||||
case BlockchainBackend.ErrorNotInitialized:
|
||||
case BlockchainBackend.ErrorConfigMissing:
|
||||
case BlockchainBackend.ErrorStartFailed:
|
||||
case BlockchainBackend.ErrorStopFailed:
|
||||
case BlockchainBackend.ErrorSubscribeFailed: return Theme.palette.error;
|
||||
default: return Theme.palette.textSecondary;
|
||||
function getStatusColor(s) {
|
||||
switch(s) {
|
||||
case BlockchainBackend.Running: return Theme.palette.success
|
||||
case BlockchainBackend.Starting:
|
||||
case BlockchainBackend.Stopping: return Theme.palette.warning
|
||||
default: return Theme.palette.error
|
||||
}
|
||||
}
|
||||
property int currentPage: 0 // 0 = config choice (page 1), 1 = node + wallet + logs (page 2)
|
||||
property int currentPage: 0
|
||||
}
|
||||
|
||||
color: Theme.palette.background
|
||||
|
||||
// Loading state before backend connects
|
||||
ColumnLayout {
|
||||
anchors.centerIn: parent
|
||||
visible: !root.ready
|
||||
spacing: 12
|
||||
Text {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
text: qsTr("Connecting to blockchain backend...")
|
||||
color: Theme.palette.textSecondary
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
}
|
||||
BusyIndicator { Layout.alignment: Qt.AlignHCenter; running: !root.ready }
|
||||
}
|
||||
|
||||
StackLayout {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacing.large
|
||||
currentIndex: _d.currentPage
|
||||
visible: root.ready
|
||||
|
||||
// Page 1: Config choice (Option 1: Generate own config, Option 2: Set path to configs)
|
||||
// Page 1: Config choice
|
||||
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 }
|
||||
userConfigPath: root.backend ? root.backend.userConfig : ""
|
||||
deploymentConfigPath: root.backend ? root.backend.deploymentConfig : ""
|
||||
generatedUserConfigPath: root.backend ? root.backend.generatedUserConfigPath : ""
|
||||
onUserConfigPathSelected: function(path) {
|
||||
if (root.backend) root.backend.userConfig = path
|
||||
}
|
||||
onDeploymentConfigPathSelected: function(path) {
|
||||
if (root.backend) root.backend.deploymentConfig = path
|
||||
}
|
||||
onSetPathToConfigsRequested: function() {
|
||||
backend.useGeneratedConfig = false
|
||||
if (root.backend) root.backend.useGeneratedConfig = false
|
||||
_d.currentPage = 1
|
||||
}
|
||||
onGenerateRequested: function(outputPath, initialPeers, netPort, blendPort, httpAddr, externalAddress, noPublicIpCheck, deploymentMode, deploymentConfigPath, statePath) {
|
||||
if (!root.backend) return
|
||||
console.log("[BlockchainView] generateRequested: outputPath=", outputPath,
|
||||
"initialPeers=", JSON.stringify(initialPeers),
|
||||
"netPort=", netPort, "blendPort=", blendPort,
|
||||
"httpAddr=", httpAddr, "externalAddress=", externalAddress,
|
||||
"noPublicIpCheck=", noPublicIpCheck, "deploymentMode=", deploymentMode,
|
||||
"deploymentConfigPath=", deploymentConfigPath, "statePath=", 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
|
||||
}
|
||||
logos.watch(
|
||||
root.backend.generateConfig(
|
||||
outputPath, initialPeers, netPort, blendPort,
|
||||
httpAddr, externalAddress, noPublicIpCheck,
|
||||
deploymentMode, deploymentConfigPath, statePath),
|
||||
function(code) {
|
||||
// logos.watch stringifies the returned int — coerce back.
|
||||
var rc = parseInt(code, 10)
|
||||
console.log("[BlockchainView] generateConfig success callback: code=", code, "type=", typeof code, "→ rc=", rc)
|
||||
configChoiceView.generateResultSuccess = (rc === 0)
|
||||
configChoiceView.generateResultMessage =
|
||||
rc === 0
|
||||
? qsTr("Config generated successfully.")
|
||||
: qsTr("Generate failed (code: %1).").arg(rc)
|
||||
if (rc === 0) {
|
||||
root.backend.userConfig = (outputPath !== "")
|
||||
? outputPath : root.backend.generatedUserConfigPath
|
||||
root.backend.deploymentConfig =
|
||||
(deploymentMode === 1 && deploymentConfigPath !== "")
|
||||
? deploymentConfigPath : ""
|
||||
root.backend.useGeneratedConfig = true
|
||||
_d.currentPage = 1
|
||||
}
|
||||
},
|
||||
function(error) {
|
||||
console.log("[BlockchainView] generateConfig error callback: error=", error)
|
||||
configChoiceView.generateResultSuccess = false
|
||||
configChoiceView.generateResultMessage =
|
||||
qsTr("Generate failed: %1").arg(error)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Page 2: Start node, balances, transfer, logs
|
||||
// Page 2: Node control, wallet, logs
|
||||
SplitView {
|
||||
orientation: Qt.Vertical
|
||||
|
||||
@ -97,39 +166,61 @@ Rectangle {
|
||||
|
||||
StatusConfigView {
|
||||
Layout.fillWidth: true
|
||||
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
|
||||
statusText: root.backend
|
||||
? _d.getStatusString(root.backend.status)
|
||||
: qsTr("Not Connected")
|
||||
statusColor: root.backend
|
||||
? _d.getStatusColor(root.backend.status)
|
||||
: Theme.palette.error
|
||||
userConfig: root.backend ? root.backend.userConfig : ""
|
||||
deploymentConfig: root.backend ? root.backend.deploymentConfig : ""
|
||||
useGeneratedConfig: root.backend ? root.backend.useGeneratedConfig : false
|
||||
canStart: root.backend
|
||||
&& !!root.backend.userConfig
|
||||
&& root.backend.status !== BlockchainBackend.Starting
|
||||
&& root.backend.status !== BlockchainBackend.Stopping
|
||||
isRunning: root.backend
|
||||
? root.backend.status === BlockchainBackend.Running
|
||||
: false
|
||||
|
||||
onStartRequested: backend.startBlockchain()
|
||||
onStopRequested: backend.stopBlockchain()
|
||||
onStartRequested: if (root.backend) root.backend.startBlockchain()
|
||||
onStopRequested: if (root.backend) root.backend.stopBlockchain()
|
||||
onChangeConfigRequested: _d.currentPage = 0
|
||||
}
|
||||
|
||||
WalletView {
|
||||
id: walletView
|
||||
accountsModel: backend.accountsModel
|
||||
accountsModel: root.accountsModel
|
||||
|
||||
onGetBalanceRequested: function(addressHex) {
|
||||
var result = backend.getBalance(addressHex)
|
||||
if ((result || "").indexOf("Error") === 0) {
|
||||
lastBalanceErrorAddress = addressHex
|
||||
lastBalanceError = result
|
||||
}
|
||||
else {
|
||||
lastBalanceErrorAddress = ""
|
||||
lastBalanceError = ""
|
||||
}
|
||||
if (!root.backend) return
|
||||
logos.watch(
|
||||
root.backend.getBalance(addressHex),
|
||||
function(result) {
|
||||
if ((result || "").indexOf("Error") === 0) {
|
||||
walletView.lastBalanceErrorAddress = addressHex
|
||||
walletView.lastBalanceError = result
|
||||
} else {
|
||||
walletView.lastBalanceErrorAddress = ""
|
||||
walletView.lastBalanceError = ""
|
||||
}
|
||||
},
|
||||
function(error) {
|
||||
walletView.lastBalanceErrorAddress = addressHex
|
||||
walletView.lastBalanceError = "Error: " + error
|
||||
}
|
||||
)
|
||||
}
|
||||
onCopyToClipboard: (text) => {
|
||||
if (root.backend) root.backend.copyToClipboard(text)
|
||||
}
|
||||
onCopyToClipboard: (text) => backend.copyToClipboard(text)
|
||||
onTransferRequested: function(fromKeyHex, toKeyHex, amount) {
|
||||
walletView.setTransferResult(backend.transferFunds(fromKeyHex, toKeyHex, amount))
|
||||
if (!root.backend) return
|
||||
logos.watch(
|
||||
root.backend.transferFunds(fromKeyHex, toKeyHex, amount),
|
||||
function(result) { walletView.setTransferResult(result) },
|
||||
function(error) { walletView.setTransferResult("Error: " + error) }
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@ -142,11 +233,12 @@ Rectangle {
|
||||
SplitView.fillWidth: true
|
||||
SplitView.minimumHeight: 150
|
||||
|
||||
logModel: backend.logModel
|
||||
onClearRequested: backend.clearLogs()
|
||||
onCopyToClipboard: (text) => backend.copyToClipboard(text)
|
||||
logModel: root.logModel
|
||||
onClearRequested: if (root.backend) root.backend.clearLogs()
|
||||
onCopyToClipboard: (text) => {
|
||||
if (root.backend) root.backend.copyToClipboard(text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -50,7 +50,7 @@ ItemDelegate {
|
||||
Layout.preferredWidth: 40
|
||||
display: AbstractButton.IconOnly
|
||||
flat: true
|
||||
icon.source: "qrc:/icons/refresh.svg"
|
||||
icon.source: Qt.resolvedUrl("../icons/refresh.svg")
|
||||
icon.color: Theme.palette.textSecondary
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
padding: 4
|
||||
|
||||
@ -1,6 +1,8 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
|
||||
import Logos.Theme
|
||||
|
||||
Button {
|
||||
id: root
|
||||
|
||||
@ -11,7 +13,7 @@ Button {
|
||||
display: AbstractButton.IconOnly
|
||||
flat: true
|
||||
|
||||
property string iconSource: "qrc:/icons/copy.svg"
|
||||
property string iconSource: Qt.resolvedUrl("../icons/copy.svg")
|
||||
|
||||
icon.source: root.iconSource
|
||||
icon.width: 24
|
||||
@ -19,7 +21,7 @@ Button {
|
||||
icon.color: Theme.palette.textSecondary
|
||||
|
||||
function reset() {
|
||||
iconSource = "qrc:/icons/copy.svg"
|
||||
iconSource = Qt.resolvedUrl("../icons/copy.svg")
|
||||
}
|
||||
|
||||
Timer {
|
||||
@ -31,7 +33,7 @@ Button {
|
||||
|
||||
onClicked: {
|
||||
root.copyText()
|
||||
root.iconSource = "qrc:/icons/checkmark.svg"
|
||||
root.iconSource = Qt.resolvedUrl("../icons/checkmark.svg")
|
||||
resetTimer.restart()
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 462 B After Width: | Height: | Size: 462 B |
|
Before Width: | Height: | Size: 976 B After Width: | Height: | Size: 976 B |
|
Before Width: | Height: | Size: 634 B After Width: | Height: | Size: 634 B |
@ -85,17 +85,13 @@ ColumnLayout {
|
||||
resultSuccess: root.generateResultSuccess
|
||||
resultMessage: root.generateResultMessage
|
||||
Layout.fillWidth: true
|
||||
onGenerateRequested: root.generateRequested(
|
||||
outputPath,
|
||||
initialPeers,
|
||||
netPort,
|
||||
blendPort,
|
||||
httpAddr,
|
||||
externalAddress,
|
||||
noPublicIpCheck,
|
||||
deploymentMode,
|
||||
deploymentConfigPath,
|
||||
statePath)
|
||||
onGenerateRequested: function(outputPath, initialPeers, netPort, blendPort,
|
||||
httpAddr, externalAddress, noPublicIpCheck,
|
||||
deploymentMode, deploymentConfigPath, statePath) {
|
||||
root.generateRequested(outputPath, initialPeers, netPort, blendPort,
|
||||
httpAddr, externalAddress, noPublicIpCheck,
|
||||
deploymentMode, deploymentConfigPath, statePath)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -210,7 +210,7 @@ ColumnLayout {
|
||||
FileDialog {
|
||||
id: deploymentConfigFileDialog
|
||||
modality: Qt.NonModal
|
||||
nameFilters: ["YAML files (*.yaml)", "All files (*)"]
|
||||
nameFilters: ["YAML files (*.yaml *.yml)", "All files (*)"]
|
||||
currentFolder: StandardPaths.standardLocations(StandardPaths.DocumentsLocation)[0]
|
||||
onAccepted: customDeploymentField.text = selectedFile
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@ Control {
|
||||
id: root
|
||||
|
||||
// --- Public API ---
|
||||
required property var logModel // LogModel (QAbstractListModel with "text" role)
|
||||
required property var logModel // ListModel with "text" role
|
||||
|
||||
signal clearRequested()
|
||||
signal copyToClipboard(string text)
|
||||
@ -61,6 +61,12 @@ Control {
|
||||
model: root.logModel
|
||||
spacing: 2
|
||||
|
||||
// Auto-scroll to the latest log on insert. Use the ListView's
|
||||
// own `count` (it's always available and emits countChanged) —
|
||||
// the model replica is a QAbstractItemModelReplica and does
|
||||
// not carry the source-side `count` Q_PROPERTY through QtRO.
|
||||
onCountChanged: if (count > 0) positionViewAtEnd()
|
||||
|
||||
delegate: ItemDelegate{
|
||||
width: ListView.view.width
|
||||
contentItem: LogosText {
|
||||
@ -76,20 +82,15 @@ Control {
|
||||
}
|
||||
|
||||
LogosText {
|
||||
visible: !root.logModel || root.logModel.count === 0
|
||||
// ListView's `count` reflects the model row count and has
|
||||
// a NOTIFY signal — using it here gives the binding
|
||||
// automatic refresh, unlike `root.logModel.count`.
|
||||
visible: logsListView.count === 0
|
||||
anchors.centerIn: parent
|
||||
text: qsTr("No logs yet...")
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
color: Theme.palette.textSecondary
|
||||
}
|
||||
|
||||
Connections {
|
||||
target: root.logModel
|
||||
function onCountChanged() {
|
||||
if (root.logModel.count > 0)
|
||||
logsListView.positionViewAtEnd()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,14 +78,14 @@ ColumnLayout {
|
||||
FileDialog {
|
||||
id: userConfigFileDialog
|
||||
modality: Qt.NonModal
|
||||
nameFilters: ["YAML files (*.yaml)"]
|
||||
nameFilters: ["YAML files (*.yaml *.yml)", "All files (*)"]
|
||||
onAccepted: root.userConfigPathSelected(selectedFile)
|
||||
}
|
||||
|
||||
FileDialog {
|
||||
id: deploymentConfigFileDialog
|
||||
modality: Qt.NonModal
|
||||
nameFilters: ["YAML files (*.yaml)"]
|
||||
nameFilters: ["YAML files (*.yaml *.yml)", "All files (*)"]
|
||||
onAccepted: root.deploymentConfigPathSelected(selectedFile)
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user