mirror of
https://github.com/logos-blockchain/logos-blockchain-ui.git
synced 2026-02-14 02:23:40 +00:00
Initial commit
This commit is contained in:
commit
da055ad72e
12
.gitignore
vendored
Normal file
12
.gitignore
vendored
Normal file
@ -0,0 +1,12 @@
|
||||
.DS_Store
|
||||
build/
|
||||
result
|
||||
*.dylib
|
||||
*.so
|
||||
*.dll
|
||||
*.a
|
||||
CMakeCache.txt
|
||||
CMakeFiles/
|
||||
*.cmake
|
||||
generated_code/
|
||||
db/
|
||||
273
CMakeLists.txt
Normal file
273
CMakeLists.txt
Normal file
@ -0,0 +1,273 @@
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
project(BlockchainUIPlugin VERSION 1.0.0 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)
|
||||
|
||||
# Get the real path to handle symlinks correctly
|
||||
get_filename_component(REAL_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}" REALPATH)
|
||||
|
||||
# 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/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()
|
||||
|
||||
# Run Logos C++ generator on metadata before compilation (only for source layout)
|
||||
set(METADATA_JSON "${CMAKE_CURRENT_SOURCE_DIR}/metadata.json")
|
||||
|
||||
# Only run generator for source layout - nix builds already have generated files
|
||||
if(_cpp_sdk_is_source)
|
||||
# Source layout: build and run the generator
|
||||
# Generate into build directory
|
||||
set(PLUGINS_OUTPUT_DIR "${CMAKE_BINARY_DIR}/generated_code")
|
||||
set(CPP_GENERATOR_BUILD_DIR "${LOGOS_CPP_SDK_ROOT}/../build/cpp-generator")
|
||||
set(CPP_GENERATOR "${CPP_GENERATOR_BUILD_DIR}/bin/logos-cpp-generator")
|
||||
|
||||
if(NOT TARGET cpp_generator_build)
|
||||
add_custom_target(cpp_generator_build
|
||||
COMMAND bash "${LOGOS_CPP_SDK_ROOT}/cpp-generator/compile.sh"
|
||||
WORKING_DIRECTORY "${LOGOS_CPP_SDK_ROOT}/.."
|
||||
COMMENT "Building logos-cpp-generator via ${LOGOS_CPP_SDK_ROOT}/cpp-generator/compile.sh"
|
||||
VERBATIM
|
||||
)
|
||||
endif()
|
||||
|
||||
add_custom_target(run_cpp_generator_blockchain_ui
|
||||
COMMAND "${CPP_GENERATOR}" --metadata "${METADATA_JSON}" --general-only --output-dir "${PLUGINS_OUTPUT_DIR}"
|
||||
WORKING_DIRECTORY "${LOGOS_CPP_SDK_ROOT}/.."
|
||||
COMMENT "Running logos-cpp-generator on ${METADATA_JSON} with output-dir ${PLUGINS_OUTPUT_DIR}"
|
||||
VERBATIM
|
||||
)
|
||||
add_dependencies(run_cpp_generator_blockchain_ui cpp_generator_build)
|
||||
|
||||
# Add generated logos_sdk.cpp - will be generated by run_cpp_generator_blockchain_ui
|
||||
list(APPEND SOURCES ${PLUGINS_OUTPUT_DIR}/logos_sdk.cpp)
|
||||
# Mark as generated so CMake knows to wait for it
|
||||
set_source_files_properties(
|
||||
${PLUGINS_OUTPUT_DIR}/logos_sdk.cpp
|
||||
PROPERTIES GENERATED TRUE
|
||||
)
|
||||
else()
|
||||
# Installed/nix layout: lib.nix preConfigure populates generated_code before CMake.
|
||||
# May be overridden by -DPLUGINS_OUTPUT_DIR= when source tree is read-only (e.g. Nix sandbox).
|
||||
set(PLUGINS_OUTPUT_DIR "${CMAKE_CURRENT_SOURCE_DIR}/generated_code" CACHE PATH "Generated code directory (nix layout)")
|
||||
list(APPEND SOURCES ${PLUGINS_OUTPUT_DIR}/include/logos_sdk.cpp)
|
||||
endif()
|
||||
|
||||
# Create the plugin library
|
||||
add_library(blockchain_ui SHARED ${SOURCES})
|
||||
|
||||
# Set output name without lib prefix and with correct name for generator
|
||||
set_target_properties(blockchain_ui PROPERTIES
|
||||
PREFIX ""
|
||||
OUTPUT_NAME "blockchain_ui"
|
||||
)
|
||||
|
||||
# Ensure generator runs before building the plugin (only for source layout)
|
||||
if(_cpp_sdk_is_source)
|
||||
add_dependencies(blockchain_ui run_cpp_generator_blockchain_ui)
|
||||
endif()
|
||||
|
||||
# Include directories
|
||||
target_include_directories(blockchain_ui PRIVATE
|
||||
${CMAKE_CURRENT_SOURCE_DIR}
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/src
|
||||
${CMAKE_CURRENT_BINARY_DIR}
|
||||
${PLUGINS_OUTPUT_DIR}
|
||||
)
|
||||
|
||||
# Add include directories based on layout type
|
||||
if(_liblogos_is_source)
|
||||
target_include_directories(blockchain_ui PRIVATE ${LOGOS_LIBLOGOS_ROOT})
|
||||
else()
|
||||
target_include_directories(blockchain_ui PRIVATE ${LOGOS_LIBLOGOS_ROOT}/include)
|
||||
endif()
|
||||
|
||||
if(_cpp_sdk_is_source)
|
||||
target_include_directories(blockchain_ui PRIVATE
|
||||
${LOGOS_CPP_SDK_ROOT}/cpp
|
||||
${LOGOS_CPP_SDK_ROOT}/cpp/generated
|
||||
)
|
||||
else()
|
||||
# For nix builds, also include the include subdirectory
|
||||
# (lib.nix moves header files to ./generated_code/include/)
|
||||
target_include_directories(blockchain_ui PRIVATE
|
||||
${LOGOS_CPP_SDK_ROOT}/include
|
||||
${LOGOS_CPP_SDK_ROOT}/include/cpp
|
||||
${LOGOS_CPP_SDK_ROOT}/include/core
|
||||
${PLUGINS_OUTPUT_DIR}/include
|
||||
)
|
||||
endif()
|
||||
|
||||
# Link against libraries
|
||||
target_link_libraries(blockchain_ui PRIVATE
|
||||
Qt6::Core
|
||||
Qt6::Widgets
|
||||
Qt6::RemoteObjects
|
||||
Qt6::Quick
|
||||
Qt6::QuickWidgets
|
||||
component-interfaces
|
||||
)
|
||||
|
||||
# Link SDK library if using installed layout
|
||||
if(NOT _cpp_sdk_is_source)
|
||||
find_library(LOGOS_SDK_LIB logos_sdk PATHS ${LOGOS_CPP_SDK_ROOT}/lib NO_DEFAULT_PATH REQUIRED)
|
||||
target_link_libraries(blockchain_ui PRIVATE ${LOGOS_SDK_LIB})
|
||||
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 ${METADATA_JSON}
|
||||
DESTINATION ${CMAKE_INSTALL_DATADIR}/logos-blockchain-ui-new
|
||||
)
|
||||
|
||||
install(DIRECTORY "${PLUGINS_OUTPUT_DIR}/"
|
||||
DESTINATION ${CMAKE_INSTALL_DATADIR}/logos-blockchain-ui-new/generated
|
||||
OPTIONAL
|
||||
)
|
||||
|
||||
# Print status messages
|
||||
message(STATUS "Blockchain UI Plugin configured successfully")
|
||||
120
README.md
Normal file
120
README.md
Normal file
@ -0,0 +1,120 @@
|
||||
# logos-blockchain-ui-new
|
||||
|
||||
A Qt UI plugin for the Logos Blockchain Module, providing a graphical interface to control and monitor the Logos blockchain node.
|
||||
|
||||
## Features
|
||||
|
||||
- Start/Stop blockchain node
|
||||
- Configure node parameters (config path, deployment)
|
||||
- Check wallet balances
|
||||
- Monitor node status and information
|
||||
|
||||
## How to Build
|
||||
|
||||
### Using Nix (Recommended)
|
||||
|
||||
#### Build Complete UI Plugin
|
||||
|
||||
```bash
|
||||
# Build everything (default)
|
||||
nix build
|
||||
|
||||
# Or explicitly
|
||||
nix build '.#default'
|
||||
```
|
||||
|
||||
The result will include:
|
||||
- `/lib/blockchain_ui.dylib` (or `.so` on Linux) - The Blockchain UI plugin
|
||||
|
||||
#### Build Individual Components
|
||||
|
||||
```bash
|
||||
# Build only the library (plugin)
|
||||
nix build '.#lib'
|
||||
|
||||
# Build the standalone Qt application
|
||||
nix build '.#app'
|
||||
```
|
||||
|
||||
#### Development Shell
|
||||
|
||||
```bash
|
||||
# Enter development shell with all dependencies
|
||||
nix develop
|
||||
```
|
||||
|
||||
**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:
|
||||
|
||||
```bash
|
||||
nix build --extra-experimental-features 'nix-command flakes'
|
||||
```
|
||||
|
||||
The compiled artifacts can be found at `result/`
|
||||
|
||||
#### 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)
|
||||
```
|
||||
|
||||
## 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`.
|
||||
|
||||
### QML Hot Reload
|
||||
|
||||
During development, you can enable QML hot reload by setting an environment variable:
|
||||
```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.
|
||||
84
app/CMakeLists.txt
Normal file
84
app/CMakeLists.txt
Normal file
@ -0,0 +1,84 @@
|
||||
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()
|
||||
|
||||
# Find logos-cpp-sdk
|
||||
if(NOT DEFINED LOGOS_CPP_SDK_ROOT)
|
||||
message(FATAL_ERROR "LOGOS_CPP_SDK_ROOT must be defined")
|
||||
endif()
|
||||
|
||||
message(STATUS "Using logos-liblogos at: ${LOGOS_LIBLOGOS_ROOT}")
|
||||
message(STATUS "Using logos-cpp-sdk at: ${LOGOS_CPP_SDK_ROOT}")
|
||||
|
||||
# Check if logos_sdk library exists
|
||||
if(NOT EXISTS "${LOGOS_CPP_SDK_ROOT}/lib/liblogos_sdk.a" AND NOT EXISTS "${LOGOS_CPP_SDK_ROOT}/lib/liblogos_sdk.dylib" AND NOT EXISTS "${LOGOS_CPP_SDK_ROOT}/lib/liblogos_sdk.so")
|
||||
message(WARNING "logos_sdk library not found in ${LOGOS_CPP_SDK_ROOT}/lib/")
|
||||
message(STATUS "Available files in ${LOGOS_CPP_SDK_ROOT}/lib/:")
|
||||
file(GLOB SDK_LIB_FILES "${LOGOS_CPP_SDK_ROOT}/lib/*")
|
||||
foreach(file ${SDK_LIB_FILES})
|
||||
message(STATUS " ${file}")
|
||||
endforeach()
|
||||
endif()
|
||||
|
||||
# Include directories - the new structure has headers in /include with subdirectories
|
||||
include_directories(
|
||||
${LOGOS_LIBLOGOS_ROOT}/include
|
||||
${LOGOS_CPP_SDK_ROOT}/include
|
||||
${LOGOS_CPP_SDK_ROOT}/include/cpp
|
||||
${LOGOS_CPP_SDK_ROOT}/include/core
|
||||
)
|
||||
|
||||
# Link directories
|
||||
link_directories(
|
||||
${LOGOS_LIBLOGOS_ROOT}/lib
|
||||
${LOGOS_CPP_SDK_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
|
||||
logos_sdk
|
||||
)
|
||||
|
||||
# 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
Normal file
56
app/main.cpp
Normal file
@ -0,0 +1,56 @@
|
||||
#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;
|
||||
}
|
||||
66
app/mainwindow.cpp
Normal file
66
app/mainwindow.cpp
Normal file
@ -0,0 +1,66 @@
|
||||
#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);
|
||||
}
|
||||
18
app/mainwindow.h
Normal file
18
app/mainwindow.h
Normal file
@ -0,0 +1,18 @@
|
||||
#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
|
||||
1411
flake.lock
generated
Normal file
1411
flake.lock
generated
Normal file
File diff suppressed because it is too large
Load Diff
84
flake.nix
Normal file
84
flake.nix
Normal file
@ -0,0 +1,84 @@
|
||||
{
|
||||
description = "Logos Blockchain UI - A Qt UI plugin for Logos Blockchain Module";
|
||||
|
||||
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";
|
||||
logos-blockchain-module.url = "github:logos-blockchain/logos-blockchain-module/578308270ecfe7463a94ac50cae0584451c135ef";
|
||||
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";
|
||||
};
|
||||
|
||||
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 logosSdk;
|
||||
};
|
||||
|
||||
# App package
|
||||
app = import ./nix/app.nix {
|
||||
inherit pkgs common src logosLiblogos logosSdk 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_CPP_SDK_ROOT="${logosSdk}"
|
||||
export LOGOS_LIBLOGOS_ROOT="${logosLiblogos}"
|
||||
export LOGOS_DESIGN_SYSTEM_ROOT="${logosDesignSystem}"
|
||||
echo "Logos Blockchain UI development environment"
|
||||
echo "LOGOS_CPP_SDK_ROOT: $LOGOS_CPP_SDK_ROOT"
|
||||
echo "LOGOS_LIBLOGOS_ROOT: $LOGOS_LIBLOGOS_ROOT"
|
||||
'';
|
||||
};
|
||||
});
|
||||
};
|
||||
}
|
||||
17
interfaces/IComponent.h
Normal file
17
interfaces/IComponent.h
Normal file
@ -0,0 +1,17 @@
|
||||
#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)
|
||||
26
metadata.json
Normal file
26
metadata.json
Normal file
@ -0,0 +1,26 @@
|
||||
{
|
||||
"name": "blockchain_ui",
|
||||
"version": "1.0.0",
|
||||
"description": "Blockchain UI module for the Logos application",
|
||||
"author": "Logos Blockchain Team",
|
||||
"type": "ui",
|
||||
"main": "blockchain_ui",
|
||||
"dependencies": ["liblogos_blockchain_module"],
|
||||
"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"
|
||||
]
|
||||
}
|
||||
250
nix/app.nix
Normal file
250
nix/app.nix
Normal file
@ -0,0 +1,250 @@
|
||||
# Builds the logos-blockchain-ui-app standalone application
|
||||
{ pkgs, common, src, logosLiblogos, logosSdk, logosBlockchainModule, logosCapabilityModule, logosBlockchainUI, logosDesignSystem }:
|
||||
|
||||
pkgs.stdenv.mkDerivation rec {
|
||||
pname = "logos-blockchain-ui-app";
|
||||
version = common.version;
|
||||
|
||||
inherit src;
|
||||
inherit (common) buildInputs cmakeFlags meta;
|
||||
|
||||
# Add logosSdk to nativeBuildInputs for logos-cpp-generator
|
||||
nativeBuildInputs = common.nativeBuildInputs ++ [ logosSdk 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
|
||||
|
||||
# Set macOS deployment target to match Qt frameworks
|
||||
export MACOSX_DEPLOYMENT_TARGET=12.0
|
||||
|
||||
# Copy logos-cpp-sdk headers to expected location
|
||||
echo "Copying logos-cpp-sdk headers for app..."
|
||||
mkdir -p ./logos-cpp-sdk/include/cpp
|
||||
cp -r ${logosSdk}/include/cpp/* ./logos-cpp-sdk/include/cpp/
|
||||
|
||||
# Also copy core headers
|
||||
echo "Copying core headers..."
|
||||
mkdir -p ./logos-cpp-sdk/include/core
|
||||
cp -r ${logosSdk}/include/core/* ./logos-cpp-sdk/include/core/
|
||||
|
||||
# Copy SDK library files to lib directory
|
||||
echo "Copying SDK library files..."
|
||||
mkdir -p ./logos-cpp-sdk/lib
|
||||
if [ -f "${logosSdk}/lib/liblogos_sdk.dylib" ]; then
|
||||
cp "${logosSdk}/lib/liblogos_sdk.dylib" ./logos-cpp-sdk/lib/
|
||||
elif [ -f "${logosSdk}/lib/liblogos_sdk.so" ]; then
|
||||
cp "${logosSdk}/lib/liblogos_sdk.so" ./logos-cpp-sdk/lib/
|
||||
elif [ -f "${logosSdk}/lib/liblogos_sdk.a" ]; then
|
||||
cp "${logosSdk}/lib/liblogos_sdk.a" ./logos-cpp-sdk/lib/
|
||||
fi
|
||||
|
||||
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..."
|
||||
echo "liblogos: ${logosLiblogos}"
|
||||
echo "cpp-sdk: ${logosSdk}"
|
||||
echo "blockchain-module: ${logosBlockchainModule}"
|
||||
echo "capability-module: ${logosCapabilityModule}"
|
||||
echo "blockchain-ui: ${logosBlockchainUI}"
|
||||
echo "logos-design-system: ${logosDesignSystem}"
|
||||
|
||||
# Verify that the built components exist
|
||||
test -d "${logosLiblogos}" || (echo "liblogos not found" && exit 1)
|
||||
test -d "${logosSdk}" || (echo "cpp-sdk 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} \
|
||||
-DLOGOS_CPP_SDK_ROOT=$(pwd)/logos-cpp-sdk
|
||||
|
||||
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
|
||||
|
||||
# Copy SDK library if it exists
|
||||
if ls "${logosSdk}/lib/"liblogos_sdk.* >/dev/null 2>&1; then
|
||||
cp -L "${logosSdk}/lib/"liblogos_sdk.* "$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 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 module (Logos/DesignSystem) for runtime
|
||||
if [ -d "${logosDesignSystem}/lib/Logos/DesignSystem" ]; then
|
||||
mkdir -p "$out/lib/Logos"
|
||||
cp -R "${logosDesignSystem}/lib/Logos/DesignSystem" "$out/lib/Logos/"
|
||||
echo "Copied Logos Design System to lib/Logos/DesignSystem/"
|
||||
fi
|
||||
|
||||
# Create a README for reference
|
||||
cat > $out/README.txt <<EOF
|
||||
Logos Blockchain UI App - Build Information
|
||||
============================================
|
||||
liblogos: ${logosLiblogos}
|
||||
cpp-sdk: ${logosSdk}
|
||||
blockchain-module: ${logosBlockchainModule}
|
||||
capability-module: ${logosCapabilityModule}
|
||||
blockchain-ui: ${logosBlockchainUI}
|
||||
logos-design-system: ${logosDesignSystem}
|
||||
|
||||
Runtime Layout:
|
||||
- Binary: $out/bin/logos-blockchain-ui-app
|
||||
- Libraries: $out/lib
|
||||
- Modules: $out/modules
|
||||
- Qt Plugin: $out/blockchain_ui.$OS_EXT
|
||||
|
||||
Usage:
|
||||
$out/bin/logos-blockchain-ui-app
|
||||
EOF
|
||||
|
||||
runHook postInstall
|
||||
'';
|
||||
}
|
||||
43
nix/default.nix
Normal file
43
nix/default.nix
Normal file
@ -0,0 +1,43 @@
|
||||
# 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}"
|
||||
];
|
||||
|
||||
# Environment variables
|
||||
env = {
|
||||
LOGOS_CPP_SDK_ROOT = "${logosSdk}";
|
||||
LOGOS_LIBLOGOS_ROOT = "${logosLiblogos}";
|
||||
};
|
||||
|
||||
# Metadata
|
||||
meta = with pkgs.lib; {
|
||||
description = "Logos Blockchain UI - A Qt UI plugin for Logos Blockchain Module";
|
||||
platforms = platforms.unix;
|
||||
};
|
||||
}
|
||||
88
nix/lib.nix
Normal file
88
nix/lib.nix
Normal file
@ -0,0 +1,88 @@
|
||||
# Builds the logos-blockchain-ui library
|
||||
{ pkgs, common, src, logosBlockchainModule, logosSdk }:
|
||||
|
||||
pkgs.stdenv.mkDerivation {
|
||||
pname = "${common.pname}-lib";
|
||||
version = common.version;
|
||||
|
||||
inherit src;
|
||||
inherit (common) buildInputs cmakeFlags meta env;
|
||||
|
||||
# Add logosSdk to nativeBuildInputs for logos-cpp-generator
|
||||
nativeBuildInputs = common.nativeBuildInputs ++ [ logosSdk ];
|
||||
|
||||
preConfigure = ''
|
||||
runHook prePreConfigure
|
||||
|
||||
# Create generated_code directory for generated files
|
||||
mkdir -p ./generated_code
|
||||
|
||||
# Copy include files from logos-blockchain-module result
|
||||
echo "Copying include files from logos-blockchain-module..."
|
||||
if [ -d "${logosBlockchainModule}/include" ]; then
|
||||
echo "Found include directory in logos-blockchain-module"
|
||||
cp -r "${logosBlockchainModule}/include"/* ./generated_code/
|
||||
echo "Copied include files:"
|
||||
ls -la ./generated_code/
|
||||
else
|
||||
echo "Warning: No include directory found in logos-blockchain-module"
|
||||
fi
|
||||
|
||||
# Run logos-cpp-generator with metadata.json and --general-only flag
|
||||
echo "Running logos-cpp-generator..."
|
||||
logos-cpp-generator --metadata ${src}/metadata.json --general-only --output-dir ./generated_code
|
||||
|
||||
# Check what was generated by logos-cpp-generator
|
||||
echo "Checking generated files in generated_code:"
|
||||
ls -la ./generated_code/
|
||||
|
||||
# Create include directory and move generated files there if they exist
|
||||
if [ -f "./generated_code/core_manager_api.h" ] || [ -f "./generated_code/logos_sdk.h" ]; then
|
||||
echo "Creating include directory and moving generated files..."
|
||||
mkdir -p ./generated_code/include
|
||||
# Move generated header files to include directory
|
||||
for file in ./generated_code/*.h; do
|
||||
if [ -f "$file" ]; then
|
||||
mv "$file" ./generated_code/include/
|
||||
fi
|
||||
done
|
||||
# Also copy generated .cpp files to include directory
|
||||
for file in ./generated_code/*.cpp; do
|
||||
if [ -f "$file" ]; then
|
||||
cp "$file" ./generated_code/include/
|
||||
fi
|
||||
done
|
||||
echo "Generated include directory:"
|
||||
ls -la ./generated_code/include/
|
||||
else
|
||||
echo "Warning: No header files generated by logos-cpp-generator"
|
||||
fi
|
||||
|
||||
runHook postPreConfigure
|
||||
'';
|
||||
|
||||
installPhase = ''
|
||||
runHook preInstall
|
||||
|
||||
mkdir -p $out/lib
|
||||
# We are in build/; library is in build/modules/
|
||||
if [ -f modules/blockchain_ui.dylib ]; then
|
||||
cp modules/blockchain_ui.dylib $out/lib/
|
||||
elif [ -f modules/blockchain_ui.so ]; then
|
||||
cp modules/blockchain_ui.so $out/lib/
|
||||
else
|
||||
echo "Error: No library file found"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Also install the generated include files
|
||||
if [ -d "./generated_code/include" ]; then
|
||||
mkdir -p $out/include
|
||||
cp -r ./generated_code/include/* $out/include/
|
||||
echo "Installed generated include files:"
|
||||
ls -la $out/include/
|
||||
fi
|
||||
|
||||
runHook postInstall
|
||||
'';
|
||||
}
|
||||
148
src/BlockchainBackend.cpp
Normal file
148
src/BlockchainBackend.cpp
Normal file
@ -0,0 +1,148 @@
|
||||
#include "BlockchainBackend.h"
|
||||
#include <QDebug>
|
||||
#include <QDateTime>
|
||||
#include <QUrl>
|
||||
|
||||
BlockchainBackend::BlockchainBackend(LogosAPI* logosAPI, QObject* parent)
|
||||
: QObject(parent),
|
||||
m_status(NotStarted),
|
||||
m_configPath(""),
|
||||
m_logModel(new LogModel(this)),
|
||||
m_logos(nullptr),
|
||||
m_blockchainModule(nullptr)
|
||||
{
|
||||
|
||||
m_configPath = QString::fromUtf8(qgetenv("LB_CONFIG_PATH"));
|
||||
|
||||
if (!logosAPI) {
|
||||
logosAPI = new LogosAPI("core", this);
|
||||
}
|
||||
|
||||
m_logos = new LogosModules(logosAPI);
|
||||
|
||||
if (!m_logos) {
|
||||
setStatus(ErrorNotInitialized);
|
||||
return;
|
||||
}
|
||||
|
||||
m_blockchainModule = &m_logos->liblogos_blockchain_module;
|
||||
|
||||
if (m_blockchainModule && !m_blockchainModule->on("newBlock", [this](const QVariantList& data) {
|
||||
onNewBlock(data);
|
||||
})) {
|
||||
setStatus(ErrorSubscribeFailed);
|
||||
}
|
||||
}
|
||||
|
||||
BlockchainBackend::~BlockchainBackend()
|
||||
{
|
||||
stopBlockchain();
|
||||
}
|
||||
|
||||
void BlockchainBackend::setStatus(BlockchainStatus newStatus)
|
||||
{
|
||||
if (m_status != newStatus) {
|
||||
m_status = newStatus;
|
||||
emit statusChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void BlockchainBackend::setConfigPath(const QString& path)
|
||||
{
|
||||
const QString localPath = QUrl::fromUserInput(path).toLocalFile();
|
||||
if (m_configPath != localPath) {
|
||||
m_configPath = localPath;
|
||||
emit configPathChanged();
|
||||
}
|
||||
}
|
||||
|
||||
void BlockchainBackend::clearLogs()
|
||||
{
|
||||
m_logModel->clear();
|
||||
}
|
||||
|
||||
QString BlockchainBackend::getBalance(const QString& addressHex)
|
||||
{
|
||||
if (!m_blockchainModule) {
|
||||
return QStringLiteral("Error: Module not initialized.");
|
||||
}
|
||||
// The generated proxy converts C pointer params (uint8_t*, BalanceResult*) to QVariant,
|
||||
// which cannot carry raw C pointers. The module needs to expose a QString-based
|
||||
// wrapper (e.g. getWalletBalanceQ) for this to work through the proxy.
|
||||
Q_UNUSED(addressHex)
|
||||
return QStringLiteral("Not yet available: module needs Qt-friendly wallet API.");
|
||||
}
|
||||
|
||||
QString BlockchainBackend::transferFunds(const QString& fromKeyHex, const QString& toKeyHex, const QString& amountStr)
|
||||
{
|
||||
if (!m_blockchainModule) {
|
||||
return QStringLiteral("Error: Module not initialized.");
|
||||
}
|
||||
// Same limitation: TransferFundsArguments and Hash are C types that cannot
|
||||
// pass through the QVariant-based generated proxy.
|
||||
Q_UNUSED(fromKeyHex)
|
||||
Q_UNUSED(toKeyHex)
|
||||
Q_UNUSED(amountStr)
|
||||
return QStringLiteral("Not yet available: module needs Qt-friendly wallet API.");
|
||||
}
|
||||
|
||||
void BlockchainBackend::startBlockchain()
|
||||
{
|
||||
if (!m_blockchainModule) {
|
||||
setStatus(ErrorNotInitialized);
|
||||
return;
|
||||
}
|
||||
|
||||
setStatus(Starting);
|
||||
|
||||
int result = m_blockchainModule->start(m_configPath, QString());
|
||||
|
||||
if (result == 0 || result == 1) {
|
||||
setStatus(Running);
|
||||
} else if (result == 2) {
|
||||
setStatus(ErrorConfigMissing);
|
||||
} else if (result == 3) {
|
||||
setStatus(ErrorStartFailed);
|
||||
} else {
|
||||
setStatus(ErrorStartFailed);
|
||||
}
|
||||
}
|
||||
|
||||
void BlockchainBackend::stopBlockchain()
|
||||
{
|
||||
if (m_status != Running && m_status != Starting) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!m_blockchainModule) {
|
||||
setStatus(ErrorNotInitialized);
|
||||
return;
|
||||
}
|
||||
|
||||
setStatus(Stopping);
|
||||
|
||||
int result = m_blockchainModule->stop();
|
||||
|
||||
if (result == 0 || result == 1) {
|
||||
setStatus(Stopped);
|
||||
} else {
|
||||
setStatus(ErrorStopFailed);
|
||||
}
|
||||
}
|
||||
|
||||
void BlockchainBackend::onNewBlock(const QVariantList& data)
|
||||
{
|
||||
QString timestamp = QDateTime::currentDateTime().toString("HH:mm:ss");
|
||||
QString line;
|
||||
if (!data.isEmpty()) {
|
||||
QString blockInfo = data.first().toString();
|
||||
QString shortInfo = blockInfo.left(80);
|
||||
if (blockInfo.length() > 80) {
|
||||
shortInfo += "...";
|
||||
}
|
||||
line = QString("[%1] 📦 New block: %2").arg(timestamp, shortInfo);
|
||||
} else {
|
||||
line = QString("[%1] 📦 New block (no data)").arg(timestamp);
|
||||
}
|
||||
m_logModel->append(line);
|
||||
}
|
||||
67
src/BlockchainBackend.h
Normal file
67
src/BlockchainBackend.h
Normal file
@ -0,0 +1,67 @@
|
||||
#pragma once
|
||||
|
||||
#include <QObject>
|
||||
#include <QString>
|
||||
#include <utility>
|
||||
#include "logos_api.h"
|
||||
#include "logos_api_client.h"
|
||||
#include "logos_sdk.h"
|
||||
#include "LogModel.h"
|
||||
|
||||
// Type of the blockchain module proxy (has start(), stop(), on() etc.)
|
||||
using BlockchainModuleProxy = std::remove_reference_t<decltype(std::declval<LogosModules>().liblogos_blockchain_module)>;
|
||||
|
||||
class BlockchainBackend : public QObject {
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
enum BlockchainStatus {
|
||||
NotStarted = 0,
|
||||
Starting,
|
||||
Running,
|
||||
Stopping,
|
||||
Stopped,
|
||||
Error,
|
||||
ErrorNotInitialized,
|
||||
ErrorConfigMissing,
|
||||
ErrorStartFailed,
|
||||
ErrorStopFailed,
|
||||
ErrorSubscribeFailed
|
||||
};
|
||||
Q_ENUM(BlockchainStatus)
|
||||
|
||||
Q_PROPERTY(BlockchainStatus status READ status NOTIFY statusChanged)
|
||||
Q_PROPERTY(QString configPath READ configPath WRITE setConfigPath NOTIFY configPathChanged)
|
||||
Q_PROPERTY(LogModel* logModel READ logModel CONSTANT)
|
||||
|
||||
explicit BlockchainBackend(LogosAPI* logosAPI = nullptr, QObject* parent = nullptr);
|
||||
~BlockchainBackend();
|
||||
|
||||
BlockchainStatus status() const { return m_status; }
|
||||
QString configPath() const { return m_configPath; }
|
||||
LogModel* logModel() const { return m_logModel; }
|
||||
|
||||
void setConfigPath(const QString& path);
|
||||
Q_INVOKABLE void clearLogs();
|
||||
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();
|
||||
|
||||
public slots:
|
||||
void onNewBlock(const QVariantList& data);
|
||||
|
||||
signals:
|
||||
void statusChanged();
|
||||
void configPathChanged();
|
||||
|
||||
private:
|
||||
void setStatus(BlockchainStatus newStatus);
|
||||
|
||||
BlockchainStatus m_status;
|
||||
QString m_configPath;
|
||||
LogModel* m_logModel;
|
||||
|
||||
LogosModules* m_logos;
|
||||
BlockchainModuleProxy* m_blockchainModule;
|
||||
};
|
||||
54
src/BlockchainPlugin.cpp
Normal file
54
src/BlockchainPlugin.cpp
Normal file
@ -0,0 +1,54 @@
|
||||
#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;
|
||||
}
|
||||
|
||||
void BlockchainPlugin::destroyWidget(QWidget* widget) {
|
||||
delete widget;
|
||||
}
|
||||
14
src/BlockchainPlugin.h
Normal file
14
src/BlockchainPlugin.h
Normal file
@ -0,0 +1,14 @@
|
||||
#pragma once
|
||||
|
||||
#include <IComponent.h>
|
||||
#include <QObject>
|
||||
|
||||
class BlockchainPlugin : public QObject, public IComponent {
|
||||
Q_OBJECT
|
||||
Q_INTERFACES(IComponent)
|
||||
Q_PLUGIN_METADATA(IID IComponent_iid FILE "metadata.json")
|
||||
|
||||
public:
|
||||
Q_INVOKABLE QWidget* createWidget(LogosAPI* logosAPI = nullptr) override;
|
||||
void destroyWidget(QWidget* widget) override;
|
||||
};
|
||||
43
src/LogModel.cpp
Normal file
43
src/LogModel.cpp
Normal file
@ -0,0 +1,43 @@
|
||||
#include "LogModel.h"
|
||||
|
||||
int LogModel::rowCount(const QModelIndex& parent) const
|
||||
{
|
||||
if (parent.isValid())
|
||||
return 0;
|
||||
return m_lines.size();
|
||||
}
|
||||
|
||||
QVariant LogModel::data(const QModelIndex& index, int role) const
|
||||
{
|
||||
if (!index.isValid() || index.row() < 0 || index.row() >= m_lines.size())
|
||||
return QVariant();
|
||||
if (role == TextRole || role == Qt::DisplayRole)
|
||||
return m_lines.at(index.row());
|
||||
return QVariant();
|
||||
}
|
||||
|
||||
QHash<int, QByteArray> LogModel::roleNames() const
|
||||
{
|
||||
QHash<int, QByteArray> names;
|
||||
names[TextRole] = "text";
|
||||
return names;
|
||||
}
|
||||
|
||||
void LogModel::append(const QString& line)
|
||||
{
|
||||
const int row = m_lines.size();
|
||||
beginInsertRows(QModelIndex(), row, row);
|
||||
m_lines.append(line);
|
||||
endInsertRows();
|
||||
emit countChanged();
|
||||
}
|
||||
|
||||
void LogModel::clear()
|
||||
{
|
||||
if (m_lines.isEmpty())
|
||||
return;
|
||||
beginResetModel();
|
||||
m_lines.clear();
|
||||
endResetModel();
|
||||
emit countChanged();
|
||||
}
|
||||
26
src/LogModel.h
Normal file
26
src/LogModel.h
Normal file
@ -0,0 +1,26 @@
|
||||
#pragma once
|
||||
|
||||
#include <QAbstractListModel>
|
||||
#include <QStringList>
|
||||
|
||||
class LogModel : public QAbstractListModel {
|
||||
Q_OBJECT
|
||||
Q_PROPERTY(int count READ rowCount NOTIFY countChanged)
|
||||
public:
|
||||
enum Roles { TextRole = Qt::UserRole + 1 };
|
||||
|
||||
explicit LogModel(QObject* parent = nullptr) : QAbstractListModel(parent) {}
|
||||
|
||||
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
|
||||
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
|
||||
QHash<int, QByteArray> roleNames() const override;
|
||||
|
||||
Q_INVOKABLE void append(const QString& line);
|
||||
Q_INVOKABLE void clear();
|
||||
|
||||
signals:
|
||||
void countChanged();
|
||||
|
||||
private:
|
||||
QStringList m_lines;
|
||||
};
|
||||
11
src/blockchain_resources.qrc
Normal file
11
src/blockchain_resources.qrc
Normal file
@ -0,0 +1,11 @@
|
||||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>qml/BlockchainView.qml</file>
|
||||
<file>qml/controls/qmldir</file>
|
||||
<file>qml/controls/LogosButton.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>
|
||||
</qresource>
|
||||
</RCC>
|
||||
110
src/qml/BlockchainView.qml
Normal file
110
src/qml/BlockchainView.qml
Normal file
@ -0,0 +1,110 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
import QtQuick.Dialogs
|
||||
import QtCore
|
||||
|
||||
import BlockchainBackend
|
||||
import Logos.DesignSystem
|
||||
|
||||
import views
|
||||
|
||||
Rectangle {
|
||||
id: root
|
||||
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
color: Theme.palette.background
|
||||
|
||||
SplitView {
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacing.large
|
||||
orientation: Qt.Vertical
|
||||
|
||||
// Top: Status/Config + Wallet side-by-side
|
||||
RowLayout {
|
||||
SplitView.fillWidth: true
|
||||
SplitView.minimumHeight: 200
|
||||
|
||||
StatusConfigView {
|
||||
Layout.preferredWidth: parent.width / 2
|
||||
statusText: _d.getStatusString(backend.status)
|
||||
statusColor: _d.getStatusColor(backend.status)
|
||||
configPath: backend.configPath
|
||||
canStart: !!backend.configPath
|
||||
&& backend.status !== BlockchainBackend.Starting
|
||||
&& backend.status !== BlockchainBackend.Stopping
|
||||
isRunning: backend.status === BlockchainBackend.Running
|
||||
|
||||
onStartRequested: backend.startBlockchain()
|
||||
onStopRequested: backend.stopBlockchain()
|
||||
onChangeConfigRequested: fileDialog.open()
|
||||
}
|
||||
|
||||
WalletView {
|
||||
id: walletView
|
||||
Layout.preferredWidth: parent.width / 2
|
||||
|
||||
onGetBalanceRequested: function(addressHex) {
|
||||
walletView.setBalanceResult(backend.getBalance(addressHex))
|
||||
}
|
||||
onTransferRequested: function(fromKeyHex, toKeyHex, amount) {
|
||||
walletView.setTransferResult(backend.transferFunds(fromKeyHex, toKeyHex, amount))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Bottom: Logs
|
||||
LogsView {
|
||||
SplitView.fillWidth: true
|
||||
SplitView.minimumHeight: 150
|
||||
|
||||
logModel: backend.logModel
|
||||
onClearRequested: backend.clearLogs()
|
||||
}
|
||||
}
|
||||
|
||||
FileDialog {
|
||||
id: fileDialog
|
||||
modality: Qt.NonModal
|
||||
nameFilters: ["YAML files (*.yaml)"]
|
||||
currentFolder: StandardPaths.standardLocations(StandardPaths.DocumentsLocation)[0]
|
||||
onAccepted: {
|
||||
backend.configPath = selectedFile
|
||||
}
|
||||
}
|
||||
}
|
||||
20
src/qml/controls/LogosButton.qml
Normal file
20
src/qml/controls/LogosButton.qml
Normal file
@ -0,0 +1,20 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
|
||||
import Logos.DesignSystem
|
||||
|
||||
Button {
|
||||
implicitWidth: 200
|
||||
implicitHeight: 50
|
||||
|
||||
background: Rectangle {
|
||||
color: parent.pressed || parent.hovered ?
|
||||
Theme.palette.backgroundMuted :
|
||||
Theme.palette.backgroundSecondary
|
||||
radius: Theme.spacing.radiusXlarge
|
||||
border.color: parent.pressed || parent.hovered ?
|
||||
Theme.palette.overlayOrange :
|
||||
Theme.palette.border
|
||||
border.width: 1
|
||||
}
|
||||
}
|
||||
2
src/qml/controls/qmldir
Normal file
2
src/qml/controls/qmldir
Normal file
@ -0,0 +1,2 @@
|
||||
module controls
|
||||
LogosButton 1.0 LogosButton.qml
|
||||
92
src/qml/views/LogsView.qml
Normal file
92
src/qml/views/LogsView.qml
Normal file
@ -0,0 +1,92 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import Logos.DesignSystem
|
||||
|
||||
import controls
|
||||
|
||||
Control {
|
||||
id: root
|
||||
|
||||
// --- Public API ---
|
||||
required property var logModel // LogModel (QAbstractListModel with "text" role)
|
||||
|
||||
signal clearRequested()
|
||||
|
||||
background: Rectangle {
|
||||
color: Theme.palette.background
|
||||
}
|
||||
|
||||
ColumnLayout {
|
||||
anchors.fill: parent
|
||||
anchors.topMargin: Theme.spacing.large
|
||||
spacing: Theme.spacing.medium
|
||||
|
||||
// Header
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: implicitHeight
|
||||
spacing: Theme.spacing.medium
|
||||
|
||||
Text {
|
||||
text: qsTr("Logs")
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
font.bold: true
|
||||
color: Theme.palette.text
|
||||
}
|
||||
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
LogosButton {
|
||||
text: qsTr("Clear")
|
||||
Layout.preferredWidth: 80
|
||||
Layout.preferredHeight: 32
|
||||
onClicked: root.clearRequested()
|
||||
}
|
||||
}
|
||||
|
||||
// Log list
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
color: Theme.palette.backgroundSecondary
|
||||
radius: Theme.spacing.radiusLarge
|
||||
border.color: Theme.palette.border
|
||||
border.width: 1
|
||||
|
||||
ListView {
|
||||
id: logsListView
|
||||
anchors.fill: parent
|
||||
clip: true
|
||||
model: root.logModel
|
||||
spacing: 2
|
||||
|
||||
delegate: Text {
|
||||
text: model.text
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
font.family: Theme.typography.publicSans
|
||||
color: Theme.palette.text
|
||||
width: logsListView.width
|
||||
wrapMode: Text.Wrap
|
||||
}
|
||||
|
||||
Text {
|
||||
visible: !root.logModel || root.logModel.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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
117
src/qml/views/StatusConfigView.qml
Normal file
117
src/qml/views/StatusConfigView.qml
Normal file
@ -0,0 +1,117 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import Logos.DesignSystem
|
||||
import controls
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
// --- Public API ---
|
||||
required property string statusText
|
||||
required property color statusColor
|
||||
required property string configPath
|
||||
required property bool canStart
|
||||
required property bool isRunning
|
||||
|
||||
signal startRequested()
|
||||
signal stopRequested()
|
||||
signal changeConfigRequested()
|
||||
|
||||
spacing: Theme.spacing.large
|
||||
|
||||
// Status Card
|
||||
Rectangle {
|
||||
Layout.alignment: Qt.AlignTop
|
||||
Layout.preferredWidth: parent.width * 0.9
|
||||
Layout.preferredHeight: implicitHeight
|
||||
implicitHeight: statusContent.implicitHeight + 2 * Theme.spacing.large
|
||||
color: Theme.palette.backgroundTertiary
|
||||
radius: Theme.spacing.radiusLarge
|
||||
border.color: Theme.palette.border
|
||||
border.width: 1
|
||||
|
||||
ColumnLayout {
|
||||
id: statusContent
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.margins: Theme.spacing.large
|
||||
spacing: Theme.spacing.medium
|
||||
|
||||
Text {
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
font.pixelSize: Theme.typography.primaryText
|
||||
font.bold: true
|
||||
text: root.statusText
|
||||
color: root.statusColor
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.alignment: Qt.AlignLeft
|
||||
Layout.topMargin: -Theme.spacing.medium
|
||||
text: qsTr("Mainnet - chain ID 1")
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
color: Theme.palette.textSecondary
|
||||
}
|
||||
|
||||
LogosButton {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: 50
|
||||
enabled: root.canStart
|
||||
text: root.isRunning ? qsTr("Stop Node") : qsTr("Start Node")
|
||||
onClicked: root.isRunning ? root.stopRequested() : root.startRequested()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Config Card
|
||||
Rectangle {
|
||||
Layout.preferredWidth: parent.width * 0.9
|
||||
Layout.preferredHeight: implicitHeight
|
||||
implicitHeight: configContent.implicitHeight + 2 * Theme.spacing.large
|
||||
color: Theme.palette.backgroundTertiary
|
||||
radius: Theme.spacing.radiusLarge
|
||||
border.color: Theme.palette.border
|
||||
border.width: 1
|
||||
|
||||
ColumnLayout {
|
||||
id: configContent
|
||||
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.top: parent.top
|
||||
anchors.margins: Theme.spacing.large
|
||||
spacing: Theme.spacing.medium
|
||||
|
||||
Text {
|
||||
text: qsTr("Current Config: ")
|
||||
font.pixelSize: Theme.typography.primaryText
|
||||
font.bold: true
|
||||
color: Theme.palette.text
|
||||
}
|
||||
|
||||
Text {
|
||||
Layout.fillWidth: true
|
||||
Layout.topMargin: -Theme.spacing.medium
|
||||
text: root.configPath || qsTr("No file selected")
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
color: Theme.palette.textSecondary
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
LogosButton {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
Layout.preferredWidth: parent.width
|
||||
Layout.preferredHeight: 50
|
||||
text: qsTr("Change")
|
||||
onClicked: root.changeConfigRequested()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item { Layout.fillHeight: true }
|
||||
}
|
||||
141
src/qml/views/WalletView.qml
Normal file
141
src/qml/views/WalletView.qml
Normal file
@ -0,0 +1,141 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import Logos.DesignSystem
|
||||
|
||||
import controls
|
||||
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
// --- Public API ---
|
||||
signal getBalanceRequested(string addressHex)
|
||||
signal transferRequested(string fromKeyHex, string toKeyHex, string amount)
|
||||
|
||||
// Call these from the parent to display results
|
||||
function setBalanceResult(text) {
|
||||
balanceResultText.text = text
|
||||
}
|
||||
function setTransferResult(text) {
|
||||
transferResultText.text = text
|
||||
}
|
||||
|
||||
spacing: Theme.spacing.medium
|
||||
|
||||
// Get balance card
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: balanceCol.implicitHeight
|
||||
color: Theme.palette.backgroundTertiary
|
||||
radius: Theme.spacing.radiusLarge
|
||||
border.color: Theme.palette.border
|
||||
border.width: 1
|
||||
|
||||
ColumnLayout {
|
||||
id: balanceCol
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacing.large
|
||||
spacing: Theme.spacing.large
|
||||
|
||||
Text {
|
||||
text: qsTr("Get balance")
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
font.bold: true
|
||||
color: Theme.palette.text
|
||||
}
|
||||
|
||||
CustomTextFeild {
|
||||
id: balanceAddressField
|
||||
placeholderText: qsTr("Wallet address (64 hex chars)")
|
||||
}
|
||||
|
||||
LogosButton {
|
||||
text: qsTr("Get balance")
|
||||
Layout.alignment: Qt.AlignRight
|
||||
onClicked: root.getBalanceRequested(balanceAddressField.text)
|
||||
}
|
||||
|
||||
Text {
|
||||
id: balanceResultText
|
||||
Layout.fillWidth: true
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
color: Theme.palette.textSecondary
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Transfer funds card
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: transferCol.height
|
||||
color: Theme.palette.backgroundTertiary
|
||||
radius: Theme.spacing.radiusLarge
|
||||
border.color: Theme.palette.border
|
||||
border.width: 1
|
||||
|
||||
ColumnLayout {
|
||||
id: transferCol
|
||||
anchors.top: parent.top
|
||||
anchors.left: parent.left
|
||||
anchors.right: parent.right
|
||||
anchors.margins: Theme.spacing.large
|
||||
spacing: Theme.spacing.large
|
||||
|
||||
Text {
|
||||
text: qsTr("Transfer funds")
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
font.bold: true
|
||||
color: Theme.palette.text
|
||||
}
|
||||
|
||||
CustomTextFeild {
|
||||
placeholderText: qsTr("From key (64 hex chars)")
|
||||
}
|
||||
|
||||
CustomTextFeild {
|
||||
id: transferToField
|
||||
placeholderText: qsTr("To key (64 hex chars)")
|
||||
}
|
||||
|
||||
CustomTextFeild {
|
||||
placeholderText: qsTr("Amount")
|
||||
}
|
||||
|
||||
LogosButton {
|
||||
text: qsTr("Transfer")
|
||||
Layout.alignment: Qt.AlignRight
|
||||
onClicked: root.transferRequested(transferFromField.text, transferToField.text, transferAmountField.text)
|
||||
}
|
||||
|
||||
Text {
|
||||
id: transferResultText
|
||||
Layout.fillWidth: true
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
color: Theme.palette.textSecondary
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Item {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: Theme.spacing.small
|
||||
}
|
||||
|
||||
component CustomTextFeild: TextField {
|
||||
id: textField
|
||||
Layout.fillWidth: true
|
||||
placeholderText: qsTr("From key (64 hex chars)")
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
|
||||
background: Rectangle {
|
||||
radius: Theme.spacing.radiusSmall
|
||||
color: Theme.palette.backgroundSecondary
|
||||
border.color: textField.activeFocus ?
|
||||
Theme.palette.overlayOrange :
|
||||
Theme.palette.backgroundElevated
|
||||
}
|
||||
}
|
||||
}
|
||||
4
src/qml/views/qmldir
Normal file
4
src/qml/views/qmldir
Normal file
@ -0,0 +1,4 @@
|
||||
module views
|
||||
StatusConfigView 1.0 StatusConfigView.qml
|
||||
LogsView 1.0 LogsView.qml
|
||||
WalletView 1.0 WalletView.qml
|
||||
Loading…
x
Reference in New Issue
Block a user