ваше сообщение коммита
This commit is contained in:
@@ -8,7 +8,7 @@
|
|||||||
# This software is proprietary and confidential.
|
# This software is proprietary and confidential.
|
||||||
# For licensing inquiries: info@hb3-accelerator.com
|
# For licensing inquiries: info@hb3-accelerator.com
|
||||||
|
|
||||||
FROM node:20-bookworm
|
FROM node:20-alpine
|
||||||
|
|
||||||
# Добавляем метки для авторских прав
|
# Добавляем метки для авторских прав
|
||||||
LABEL maintainer="Тарабанов Александр Викторович <info@hb3-accelerator.com>"
|
LABEL maintainer="Тарабанов Александр Викторович <info@hb3-accelerator.com>"
|
||||||
|
|||||||
@@ -21,6 +21,7 @@ const errorHandler = require('./middleware/errorHandler');
|
|||||||
// const { version } = require('./package.json'); // Закомментировано, так как не используется
|
// const { version } = require('./package.json'); // Закомментировано, так как не используется
|
||||||
const db = require('./db'); // Добавляем импорт db
|
const db = require('./db'); // Добавляем импорт db
|
||||||
const aiAssistant = require('./services/ai-assistant'); // Добавляем импорт aiAssistant
|
const aiAssistant = require('./services/ai-assistant'); // Добавляем импорт aiAssistant
|
||||||
|
const deploymentWebSocketService = require('./services/deploymentWebSocketService'); // WebSocket для деплоя
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
const messagesRoutes = require('./routes/messages');
|
const messagesRoutes = require('./routes/messages');
|
||||||
@@ -91,6 +92,7 @@ const blockchainRoutes = require('./routes/blockchain'); // Добавляем
|
|||||||
const dleCoreRoutes = require('./routes/dleCore'); // Основные функции DLE
|
const dleCoreRoutes = require('./routes/dleCore'); // Основные функции DLE
|
||||||
const dleProposalsRoutes = require('./routes/dleProposals'); // Функции предложений
|
const dleProposalsRoutes = require('./routes/dleProposals'); // Функции предложений
|
||||||
const dleModulesRoutes = require('./routes/dleModules'); // Функции модулей
|
const dleModulesRoutes = require('./routes/dleModules'); // Функции модулей
|
||||||
|
const moduleDeploymentRoutes = require('./routes/moduleDeployment'); // Деплой модулей
|
||||||
const dleTokensRoutes = require('./routes/dleTokens'); // Функции токенов
|
const dleTokensRoutes = require('./routes/dleTokens'); // Функции токенов
|
||||||
const dleAnalyticsRoutes = require('./routes/dleAnalytics'); // Аналитика и история
|
const dleAnalyticsRoutes = require('./routes/dleAnalytics'); // Аналитика и история
|
||||||
const compileRoutes = require('./routes/compile'); // Компиляция контрактов
|
const compileRoutes = require('./routes/compile'); // Компиляция контрактов
|
||||||
@@ -195,6 +197,7 @@ app.use('/api/blockchain', blockchainRoutes); // Добавляем маршру
|
|||||||
app.use('/api/dle-core', dleCoreRoutes); // Основные функции DLE
|
app.use('/api/dle-core', dleCoreRoutes); // Основные функции DLE
|
||||||
app.use('/api/dle-proposals', dleProposalsRoutes); // Функции предложений
|
app.use('/api/dle-proposals', dleProposalsRoutes); // Функции предложений
|
||||||
app.use('/api/dle-modules', dleModulesRoutes); // Функции модулей
|
app.use('/api/dle-modules', dleModulesRoutes); // Функции модулей
|
||||||
|
app.use('/api/module-deployment', moduleDeploymentRoutes); // Деплой модулей
|
||||||
app.use('/api/dle-tokens', dleTokensRoutes); // Функции токенов
|
app.use('/api/dle-tokens', dleTokensRoutes); // Функции токенов
|
||||||
app.use('/api/dle-analytics', dleAnalyticsRoutes); // Аналитика и история
|
app.use('/api/dle-analytics', dleAnalyticsRoutes); // Аналитика и история
|
||||||
app.use('/api/dle-multichain', dleMultichainRoutes); // Мультичейн функции
|
app.use('/api/dle-multichain', dleMultichainRoutes); // Мультичейн функции
|
||||||
|
|||||||
471
backend/contracts/HierarchicalVotingModule.sol
Normal file
471
backend/contracts/HierarchicalVotingModule.sol
Normal file
@@ -0,0 +1,471 @@
|
|||||||
|
// SPDX-License-Identifier: PROPRIETARY AND MIT
|
||||||
|
// Copyright (c) 2024-2025 Тарабанов Александр Викторович
|
||||||
|
// All rights reserved.
|
||||||
|
//
|
||||||
|
// This software is proprietary and confidential.
|
||||||
|
// Unauthorized copying, modification, or distribution is prohibited.
|
||||||
|
//
|
||||||
|
// For licensing inquiries: info@hb3-accelerator.com
|
||||||
|
// Website: https://hb3-accelerator.com
|
||||||
|
// GitHub: https://github.com/HB3-ACCELERATOR
|
||||||
|
|
||||||
|
pragma solidity ^0.8.20;
|
||||||
|
|
||||||
|
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
||||||
|
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
||||||
|
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
|
||||||
|
import "@openzeppelin/contracts/utils/Address.sol";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @title HierarchicalVotingModule
|
||||||
|
* @dev Модуль для иерархического голосования между DLE
|
||||||
|
*
|
||||||
|
* ОСНОВНЫЕ ФУНКЦИИ:
|
||||||
|
* - Владение токенами других DLE
|
||||||
|
* - Создание предложений для голосования в других DLE
|
||||||
|
* - Внутреннее голосование для внешнего голосования
|
||||||
|
* - Выполнение внешнего голосования после достижения кворума
|
||||||
|
*
|
||||||
|
* БЕЗОПАСНОСТЬ:
|
||||||
|
* - Только DLE контракт может выполнять операции
|
||||||
|
* - Защита от реентерабельности
|
||||||
|
* - Валидация всех входных параметров
|
||||||
|
* - Проверка прав через governance
|
||||||
|
*/
|
||||||
|
contract HierarchicalVotingModule is ReentrancyGuard {
|
||||||
|
using SafeERC20 for IERC20;
|
||||||
|
using Address for address;
|
||||||
|
|
||||||
|
// Структура для внешнего голосования
|
||||||
|
struct ExternalVotingProposal {
|
||||||
|
address targetDLE; // Адрес целевого DLE
|
||||||
|
uint256 targetProposalId; // ID предложения в целевом DLE
|
||||||
|
bool support; // Поддержка предложения
|
||||||
|
string reason; // Причина голосования
|
||||||
|
bool executed; // Выполнено ли внешнее голосование
|
||||||
|
uint256 internalProposalId; // ID внутреннего предложения в DLE
|
||||||
|
uint256 votingPower; // Сила голоса (количество токенов)
|
||||||
|
uint256 createdAt; // Время создания
|
||||||
|
}
|
||||||
|
|
||||||
|
// Структура для информации о внешнем DLE
|
||||||
|
struct ExternalDLEInfo {
|
||||||
|
address dleAddress; // Адрес DLE
|
||||||
|
string name; // Название DLE
|
||||||
|
string symbol; // Символ токена DLE
|
||||||
|
uint256 tokenBalance; // Количество токенов на балансе
|
||||||
|
bool isActive; // Активен ли DLE
|
||||||
|
uint256 addedAt; // Время добавления
|
||||||
|
}
|
||||||
|
|
||||||
|
// Основные переменные
|
||||||
|
address public immutable dleContract; // Адрес основного DLE контракта
|
||||||
|
address public treasuryModule; // Адрес TreasuryModule (может быть установлен позже)
|
||||||
|
|
||||||
|
// Хранение внешних DLE
|
||||||
|
mapping(address => ExternalDLEInfo) public externalDLEs;
|
||||||
|
address[] public externalDLEList;
|
||||||
|
mapping(address => uint256) public externalDLEIndex;
|
||||||
|
|
||||||
|
// Внешние предложения
|
||||||
|
mapping(uint256 => ExternalVotingProposal) public externalVotingProposals;
|
||||||
|
uint256 public externalProposalCounter;
|
||||||
|
|
||||||
|
// Статистика
|
||||||
|
uint256 public totalExternalDLEs;
|
||||||
|
uint256 public totalExternalProposals;
|
||||||
|
uint256 public totalExternalVotes;
|
||||||
|
|
||||||
|
// События
|
||||||
|
event TreasuryModuleSet(address indexed treasuryModule, uint256 timestamp);
|
||||||
|
event ExternalDLEAdded(
|
||||||
|
address indexed dleAddress,
|
||||||
|
string name,
|
||||||
|
string symbol,
|
||||||
|
uint256 tokenBalance,
|
||||||
|
uint256 timestamp
|
||||||
|
);
|
||||||
|
event ExternalDLERemoved(address indexed dleAddress, uint256 timestamp);
|
||||||
|
event ExternalVotingProposalCreated(
|
||||||
|
uint256 indexed proposalId,
|
||||||
|
address indexed targetDLE,
|
||||||
|
uint256 targetProposalId,
|
||||||
|
bool support,
|
||||||
|
string reason,
|
||||||
|
uint256 internalProposalId
|
||||||
|
);
|
||||||
|
event ExternalVoteExecuted(
|
||||||
|
uint256 indexed proposalId,
|
||||||
|
address indexed targetDLE,
|
||||||
|
uint256 targetProposalId,
|
||||||
|
bool support,
|
||||||
|
uint256 votingPower
|
||||||
|
);
|
||||||
|
event ExternalDLEBalanceUpdated(
|
||||||
|
address indexed dleAddress,
|
||||||
|
uint256 oldBalance,
|
||||||
|
uint256 newBalance
|
||||||
|
);
|
||||||
|
|
||||||
|
// Модификаторы
|
||||||
|
modifier onlyDLE() {
|
||||||
|
require(msg.sender == dleContract, "Only DLE contract can call this");
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
modifier validExternalDLE(address dleAddress) {
|
||||||
|
require(externalDLEs[dleAddress].isActive, "External DLE not active");
|
||||||
|
_;
|
||||||
|
}
|
||||||
|
|
||||||
|
constructor(address _dleContract) {
|
||||||
|
require(_dleContract != address(0), "DLE contract cannot be zero");
|
||||||
|
|
||||||
|
dleContract = _dleContract;
|
||||||
|
treasuryModule = address(0); // Будет установлен позже через governance
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Установить адрес TreasuryModule (только через DLE governance)
|
||||||
|
* @param _treasuryModule Адрес TreasuryModule
|
||||||
|
*/
|
||||||
|
function setTreasuryModule(address _treasuryModule) external onlyDLE {
|
||||||
|
require(_treasuryModule != address(0), "Treasury module cannot be zero");
|
||||||
|
require(_treasuryModule.code.length > 0, "Treasury module contract does not exist");
|
||||||
|
|
||||||
|
treasuryModule = _treasuryModule;
|
||||||
|
|
||||||
|
emit TreasuryModuleSet(_treasuryModule, block.timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Добавить внешний DLE (только через DLE governance)
|
||||||
|
* @param dleAddress Адрес внешнего DLE
|
||||||
|
* @param name Название DLE
|
||||||
|
* @param symbol Символ токена DLE
|
||||||
|
*/
|
||||||
|
function addExternalDLE(
|
||||||
|
address dleAddress,
|
||||||
|
string memory name,
|
||||||
|
string memory symbol
|
||||||
|
) external onlyDLE {
|
||||||
|
require(dleAddress != address(0), "DLE address cannot be zero");
|
||||||
|
require(!externalDLEs[dleAddress].isActive, "External DLE already added");
|
||||||
|
require(bytes(name).length > 0, "Name cannot be empty");
|
||||||
|
require(bytes(symbol).length > 0, "Symbol cannot be empty");
|
||||||
|
require(treasuryModule != address(0), "Treasury module not set");
|
||||||
|
|
||||||
|
// Проверяем, что DLE контракт существует
|
||||||
|
require(dleAddress.code.length > 0, "DLE contract does not exist");
|
||||||
|
|
||||||
|
// Получаем баланс токенов этого DLE в TreasuryModule
|
||||||
|
uint256 tokenBalance = IERC20(dleAddress).balanceOf(treasuryModule);
|
||||||
|
|
||||||
|
externalDLEs[dleAddress] = ExternalDLEInfo({
|
||||||
|
dleAddress: dleAddress,
|
||||||
|
name: name,
|
||||||
|
symbol: symbol,
|
||||||
|
tokenBalance: tokenBalance,
|
||||||
|
isActive: true,
|
||||||
|
addedAt: block.timestamp
|
||||||
|
});
|
||||||
|
|
||||||
|
externalDLEList.push(dleAddress);
|
||||||
|
externalDLEIndex[dleAddress] = externalDLEList.length - 1;
|
||||||
|
totalExternalDLEs++;
|
||||||
|
|
||||||
|
emit ExternalDLEAdded(dleAddress, name, symbol, tokenBalance, block.timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Удалить внешний DLE (только через DLE governance)
|
||||||
|
* @param dleAddress Адрес внешнего DLE
|
||||||
|
*/
|
||||||
|
function removeExternalDLE(address dleAddress) external onlyDLE validExternalDLE(dleAddress) {
|
||||||
|
require(externalDLEs[dleAddress].tokenBalance == 0, "Token balance must be zero");
|
||||||
|
|
||||||
|
// Удаляем из массива
|
||||||
|
uint256 index = externalDLEIndex[dleAddress];
|
||||||
|
uint256 lastIndex = externalDLEList.length - 1;
|
||||||
|
|
||||||
|
if (index != lastIndex) {
|
||||||
|
address lastDLE = externalDLEList[lastIndex];
|
||||||
|
externalDLEList[index] = lastDLE;
|
||||||
|
externalDLEIndex[lastDLE] = index;
|
||||||
|
}
|
||||||
|
|
||||||
|
externalDLEList.pop();
|
||||||
|
delete externalDLEIndex[dleAddress];
|
||||||
|
delete externalDLEs[dleAddress];
|
||||||
|
totalExternalDLEs--;
|
||||||
|
|
||||||
|
emit ExternalDLERemoved(dleAddress, block.timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Создать предложение для внешнего голосования
|
||||||
|
* @param targetDLE Адрес целевого DLE
|
||||||
|
* @param targetProposalId ID предложения в целевом DLE
|
||||||
|
* @param support Поддержка предложения
|
||||||
|
* @param reason Причина голосования
|
||||||
|
* @return proposalId ID созданного предложения
|
||||||
|
*/
|
||||||
|
function createExternalVotingProposal(
|
||||||
|
address targetDLE,
|
||||||
|
uint256 targetProposalId,
|
||||||
|
bool support,
|
||||||
|
string memory reason
|
||||||
|
) external onlyDLE validExternalDLE(targetDLE) returns (uint256) {
|
||||||
|
require(targetProposalId > 0, "Target proposal ID must be positive");
|
||||||
|
require(bytes(reason).length > 0, "Reason cannot be empty");
|
||||||
|
|
||||||
|
ExternalDLEInfo memory dleInfo = externalDLEs[targetDLE];
|
||||||
|
require(dleInfo.tokenBalance > 0, "No tokens in target DLE");
|
||||||
|
|
||||||
|
// Создаем описание для внутреннего предложения
|
||||||
|
string memory description = string(abi.encodePacked(
|
||||||
|
"Vote in DLE ", dleInfo.name, " (", dleInfo.symbol, ") on proposal #",
|
||||||
|
_toString(targetProposalId), ": ", reason
|
||||||
|
));
|
||||||
|
|
||||||
|
// Создаем внутреннее предложение через DLE
|
||||||
|
// Это требует интеграции с DLE контрактом
|
||||||
|
uint256 internalProposalId = _createInternalProposal(description);
|
||||||
|
|
||||||
|
uint256 proposalId = externalProposalCounter++;
|
||||||
|
externalVotingProposals[proposalId] = ExternalVotingProposal({
|
||||||
|
targetDLE: targetDLE,
|
||||||
|
targetProposalId: targetProposalId,
|
||||||
|
support: support,
|
||||||
|
reason: reason,
|
||||||
|
executed: false,
|
||||||
|
internalProposalId: internalProposalId,
|
||||||
|
votingPower: dleInfo.tokenBalance,
|
||||||
|
createdAt: block.timestamp
|
||||||
|
});
|
||||||
|
|
||||||
|
totalExternalProposals++;
|
||||||
|
|
||||||
|
emit ExternalVotingProposalCreated(
|
||||||
|
proposalId,
|
||||||
|
targetDLE,
|
||||||
|
targetProposalId,
|
||||||
|
support,
|
||||||
|
reason,
|
||||||
|
internalProposalId
|
||||||
|
);
|
||||||
|
|
||||||
|
return proposalId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Выполнить внешнее голосование (после прохождения внутреннего предложения)
|
||||||
|
* @param proposalId ID внешнего предложения
|
||||||
|
*/
|
||||||
|
function executeExternalVote(uint256 proposalId) external onlyDLE nonReentrant {
|
||||||
|
ExternalVotingProposal storage proposal = externalVotingProposals[proposalId];
|
||||||
|
require(proposal.targetDLE != address(0), "Proposal not found");
|
||||||
|
require(!proposal.executed, "External vote already executed");
|
||||||
|
|
||||||
|
// Проверяем, что внутреннее предложение прошло
|
||||||
|
require(_isInternalProposalPassed(proposal.internalProposalId), "Internal proposal not passed");
|
||||||
|
|
||||||
|
// Выполняем голосование в целевом DLE
|
||||||
|
_executeVoteInTargetDLE(proposal.targetDLE, proposal.targetProposalId, proposal.support);
|
||||||
|
|
||||||
|
proposal.executed = true;
|
||||||
|
totalExternalVotes++;
|
||||||
|
|
||||||
|
emit ExternalVoteExecuted(
|
||||||
|
proposalId,
|
||||||
|
proposal.targetDLE,
|
||||||
|
proposal.targetProposalId,
|
||||||
|
proposal.support,
|
||||||
|
proposal.votingPower
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Обновить баланс токенов внешнего DLE
|
||||||
|
* @param dleAddress Адрес внешнего DLE
|
||||||
|
*/
|
||||||
|
function updateExternalDLEBalance(address dleAddress) external onlyDLE validExternalDLE(dleAddress) {
|
||||||
|
uint256 oldBalance = externalDLEs[dleAddress].tokenBalance;
|
||||||
|
uint256 newBalance = IERC20(dleAddress).balanceOf(treasuryModule);
|
||||||
|
|
||||||
|
externalDLEs[dleAddress].tokenBalance = newBalance;
|
||||||
|
|
||||||
|
emit ExternalDLEBalanceUpdated(dleAddress, oldBalance, newBalance);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Обновить балансы всех внешних DLE
|
||||||
|
*/
|
||||||
|
function updateAllExternalDLEBalances() external onlyDLE {
|
||||||
|
for (uint256 i = 0; i < externalDLEList.length; i++) {
|
||||||
|
address dleAddress = externalDLEList[i];
|
||||||
|
if (externalDLEs[dleAddress].isActive) {
|
||||||
|
uint256 oldBalance = externalDLEs[dleAddress].tokenBalance;
|
||||||
|
uint256 newBalance = IERC20(dleAddress).balanceOf(treasuryModule);
|
||||||
|
|
||||||
|
externalDLEs[dleAddress].tokenBalance = newBalance;
|
||||||
|
|
||||||
|
emit ExternalDLEBalanceUpdated(dleAddress, oldBalance, newBalance);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== VIEW ФУНКЦИИ =====
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Получить информацию о внешнем DLE
|
||||||
|
*/
|
||||||
|
function getExternalDLEInfo(address dleAddress) external view returns (ExternalDLEInfo memory) {
|
||||||
|
return externalDLEs[dleAddress];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Получить список всех внешних DLE
|
||||||
|
*/
|
||||||
|
function getAllExternalDLEs() external view returns (address[] memory) {
|
||||||
|
return externalDLEList;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Получить активные внешние DLE
|
||||||
|
*/
|
||||||
|
function getActiveExternalDLEs() external view returns (address[] memory) {
|
||||||
|
uint256 activeCount = 0;
|
||||||
|
|
||||||
|
for (uint256 i = 0; i < externalDLEList.length; i++) {
|
||||||
|
if (externalDLEs[externalDLEList[i]].isActive) {
|
||||||
|
activeCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
address[] memory activeDLEs = new address[](activeCount);
|
||||||
|
uint256 index = 0;
|
||||||
|
|
||||||
|
for (uint256 i = 0; i < externalDLEList.length; i++) {
|
||||||
|
if (externalDLEs[externalDLEList[i]].isActive) {
|
||||||
|
activeDLEs[index] = externalDLEList[i];
|
||||||
|
index++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return activeDLEs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Получить информацию о внешнем предложении
|
||||||
|
*/
|
||||||
|
function getExternalVotingProposal(uint256 proposalId) external view returns (ExternalVotingProposal memory) {
|
||||||
|
return externalVotingProposals[proposalId];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Получить статистику модуля
|
||||||
|
*/
|
||||||
|
function getModuleStats() external view returns (
|
||||||
|
uint256 totalDLEs,
|
||||||
|
uint256 totalProposals,
|
||||||
|
uint256 totalVotes,
|
||||||
|
uint256 activeDLEs
|
||||||
|
) {
|
||||||
|
uint256 activeCount = 0;
|
||||||
|
for (uint256 i = 0; i < externalDLEList.length; i++) {
|
||||||
|
if (externalDLEs[externalDLEList[i]].isActive) {
|
||||||
|
activeCount++;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
totalExternalDLEs,
|
||||||
|
totalExternalProposals,
|
||||||
|
totalExternalVotes,
|
||||||
|
activeCount
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// ===== ВНУТРЕННИЕ ФУНКЦИИ =====
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Создать внутреннее предложение в DLE
|
||||||
|
* @param description Описание предложения
|
||||||
|
* @return proposalId ID созданного предложения
|
||||||
|
*/
|
||||||
|
function _createInternalProposal(string memory description) internal returns (uint256) {
|
||||||
|
// Создаем предложение через стандартный интерфейс DLE
|
||||||
|
(bool success, bytes memory data) = dleContract.call(
|
||||||
|
abi.encodeWithSignature(
|
||||||
|
"createProposal(string,uint256,bytes,uint256,uint256[],uint256)",
|
||||||
|
description,
|
||||||
|
7 days, // 7 дней голосования
|
||||||
|
"", // Пустая операция
|
||||||
|
block.chainid, // Текущая сеть
|
||||||
|
new uint256[](0), // Пустой массив целевых цепочек
|
||||||
|
0 // Без timelock
|
||||||
|
)
|
||||||
|
);
|
||||||
|
|
||||||
|
require(success, "Failed to create internal proposal");
|
||||||
|
return abi.decode(data, (uint256));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Проверить, прошло ли внутреннее предложение
|
||||||
|
* @param proposalId ID внутреннего предложения
|
||||||
|
* @return passed Прошло ли предложение
|
||||||
|
*/
|
||||||
|
function _isInternalProposalPassed(uint256 proposalId) internal view returns (bool) {
|
||||||
|
(bool success, bytes memory data) = dleContract.staticcall(
|
||||||
|
abi.encodeWithSignature("checkProposalResult(uint256)", proposalId)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!success) return false;
|
||||||
|
(bool passed, bool quorumReached) = abi.decode(data, (bool, bool));
|
||||||
|
return passed && quorumReached;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Выполнить голосование в целевом DLE
|
||||||
|
* @param targetDLE Адрес целевого DLE
|
||||||
|
* @param proposalId ID предложения
|
||||||
|
* @param support Поддержка предложения
|
||||||
|
*/
|
||||||
|
function _executeVoteInTargetDLE(
|
||||||
|
address targetDLE,
|
||||||
|
uint256 proposalId,
|
||||||
|
bool support
|
||||||
|
) internal {
|
||||||
|
// Выполняем голосование напрямую в целевом DLE
|
||||||
|
// Это требует, чтобы целевой DLE имел функцию vote
|
||||||
|
(bool success, ) = targetDLE.call(
|
||||||
|
abi.encodeWithSignature("vote(uint256,bool)", proposalId, support)
|
||||||
|
);
|
||||||
|
|
||||||
|
require(success, "Failed to execute vote in target DLE");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @dev Конвертировать uint256 в string
|
||||||
|
*/
|
||||||
|
function _toString(uint256 value) internal pure returns (string memory) {
|
||||||
|
if (value == 0) {
|
||||||
|
return "0";
|
||||||
|
}
|
||||||
|
uint256 temp = value;
|
||||||
|
uint256 digits;
|
||||||
|
while (temp != 0) {
|
||||||
|
digits++;
|
||||||
|
temp /= 10;
|
||||||
|
}
|
||||||
|
bytes memory buffer = new bytes(digits);
|
||||||
|
while (value != 0) {
|
||||||
|
digits -= 1;
|
||||||
|
buffer[digits] = bytes1(uint8(48 + uint256(value % 10)));
|
||||||
|
value /= 10;
|
||||||
|
}
|
||||||
|
return string(buffer);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,105 +0,0 @@
|
|||||||
# Автоматическая верификация контрактов
|
|
||||||
|
|
||||||
## Обзор
|
|
||||||
|
|
||||||
Автоматическая верификация контрактов интегрирована в процесс деплоя DLE. После успешного деплоя контрактов во всех выбранных сетях, система автоматически запускает процесс верификации на соответствующих блокчейн-эксплорерах.
|
|
||||||
|
|
||||||
## Как это работает
|
|
||||||
|
|
||||||
1. **Настройка в форме деплоя**: В форме деплоя DLE есть чекбокс "Авто-верификация после деплоя" (по умолчанию включен)
|
|
||||||
2. **Деплой контрактов**: Система разворачивает DLE контракты во всех выбранных сетях
|
|
||||||
3. **Автоматическая верификация**: Если `autoVerifyAfterDeploy = true`, система автоматически запускает верификацию
|
|
||||||
4. **Результаты**: Статус верификации отображается в логах деплоя
|
|
||||||
|
|
||||||
## Поддерживаемые сети
|
|
||||||
|
|
||||||
Автоматическая верификация работает для всех сетей, настроенных в `hardhat.config.js`:
|
|
||||||
|
|
||||||
- **Sepolia** (Ethereum testnet)
|
|
||||||
- **Holesky** (Ethereum testnet)
|
|
||||||
- **Arbitrum Sepolia**
|
|
||||||
- **Base Sepolia**
|
|
||||||
- **И другие сети** (настраиваются в конфиге)
|
|
||||||
|
|
||||||
## Требования
|
|
||||||
|
|
||||||
1. **Etherscan API ключ**: Должен быть указан в форме деплоя
|
|
||||||
2. **Права на запись**: Приватный ключ должен иметь права на деплой контрактов
|
|
||||||
3. **Сеть доступна**: RPC провайдеры для всех выбранных сетей должны быть доступны
|
|
||||||
|
|
||||||
## Логи и мониторинг
|
|
||||||
|
|
||||||
### В логах деплоя вы увидите:
|
|
||||||
|
|
||||||
```
|
|
||||||
[MULTI_DBG] autoVerifyAfterDeploy: true
|
|
||||||
[MULTI_DBG] Starting automatic contract verification...
|
|
||||||
🔍 Верификация в сети sepolia (chainId: 11155111)
|
|
||||||
✅ Верификация успешна: https://sepolia.etherscan.io/address/0x...
|
|
||||||
[MULTI_DBG] ✅ Automatic verification completed successfully
|
|
||||||
```
|
|
||||||
|
|
||||||
### Статусы верификации:
|
|
||||||
|
|
||||||
- `verified` - контракт успешно верифицирован
|
|
||||||
- `verification_failed` - ошибка верификации
|
|
||||||
- `disabled` - верификация отключена
|
|
||||||
- `already_verified` - контракт уже был верифицирован ранее
|
|
||||||
|
|
||||||
## Отключение автоматической верификации
|
|
||||||
|
|
||||||
Если вы хотите отключить автоматическую верификацию:
|
|
||||||
|
|
||||||
1. В форме деплоя снимите галочку "Авто-верификация после деплоя"
|
|
||||||
2. Или установите `autoVerifyAfterDeploy: false` в настройках
|
|
||||||
|
|
||||||
## Ручная верификация
|
|
||||||
|
|
||||||
Если автоматическая верификация не сработала, вы можете запустить верификацию вручную:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# В Docker контейнере
|
|
||||||
docker exec dapp-backend node scripts/verify-with-hardhat-v2.js
|
|
||||||
|
|
||||||
# Или через npm скрипт
|
|
||||||
docker exec dapp-backend npm run verify:contracts
|
|
||||||
```
|
|
||||||
|
|
||||||
## Техническая реализация
|
|
||||||
|
|
||||||
Автоматическая верификация интегрирована в `backend/scripts/deploy/deploy-multichain.js`:
|
|
||||||
|
|
||||||
```javascript
|
|
||||||
if (params.autoVerifyAfterDeploy) {
|
|
||||||
console.log('[MULTI_DBG] Starting automatic contract verification...');
|
|
||||||
|
|
||||||
try {
|
|
||||||
const { verifyContracts } = require('../verify-with-hardhat-v2');
|
|
||||||
await verifyContracts();
|
|
||||||
verificationResults = networks.map(() => 'verified');
|
|
||||||
console.log('[MULTI_DBG] ✅ Automatic verification completed successfully');
|
|
||||||
} catch (verificationError) {
|
|
||||||
console.error('[MULTI_DBG] ❌ Automatic verification failed:', verificationError.message);
|
|
||||||
verificationResults = networks.map(() => 'verification_failed');
|
|
||||||
}
|
|
||||||
}
|
|
||||||
```
|
|
||||||
|
|
||||||
## Устранение проблем
|
|
||||||
|
|
||||||
### Ошибка "Contract already verified"
|
|
||||||
Это нормально - контракт уже был верифицирован ранее.
|
|
||||||
|
|
||||||
### Ошибка "Rate limit exceeded"
|
|
||||||
Система автоматически добавляет задержки между запросами к разным сетям.
|
|
||||||
|
|
||||||
### Ошибка "Network not supported"
|
|
||||||
Убедитесь, что сеть настроена в `hardhat.config.js` и имеет правильный Etherscan API URL.
|
|
||||||
|
|
||||||
## Преимущества
|
|
||||||
|
|
||||||
1. **Автоматизация**: Не нужно запускать верификацию вручную
|
|
||||||
2. **Надежность**: Верификация происходит сразу после деплоя
|
|
||||||
3. **Мультисеть**: Верификация во всех развернутых сетях одновременно
|
|
||||||
4. **Мониторинг**: Полная видимость процесса через логи
|
|
||||||
5. **Интеграция**: Единый процесс деплоя и верификации
|
|
||||||
@@ -62,6 +62,15 @@ module.exports = {
|
|||||||
runOnCompile: true,
|
runOnCompile: true,
|
||||||
disambiguatePaths: false,
|
disambiguatePaths: false,
|
||||||
},
|
},
|
||||||
|
|
||||||
|
// Автокомпиляция при изменениях
|
||||||
|
watch: {
|
||||||
|
compilation: {
|
||||||
|
tasks: ["compile"],
|
||||||
|
files: ["./contracts/**/*.sol"],
|
||||||
|
verbose: true
|
||||||
|
}
|
||||||
|
},
|
||||||
networks: getNetworks(),
|
networks: getNetworks(),
|
||||||
etherscan: {
|
etherscan: {
|
||||||
// Единый API ключ для V2 API
|
// Единый API ключ для V2 API
|
||||||
|
|||||||
@@ -23,6 +23,8 @@
|
|||||||
"fix-duplicates": "node scripts/fix-duplicate-identities.js",
|
"fix-duplicates": "node scripts/fix-duplicate-identities.js",
|
||||||
"deploy:multichain": "node scripts/deploy/deploy-multichain.js",
|
"deploy:multichain": "node scripts/deploy/deploy-multichain.js",
|
||||||
"deploy:complete": "node scripts/deploy/deploy-dle-complete.js",
|
"deploy:complete": "node scripts/deploy/deploy-dle-complete.js",
|
||||||
|
"deploy:modules": "node scripts/deploy/deploy-modules.js",
|
||||||
|
"test:modules": "node scripts/test-modules-deploy.js",
|
||||||
"verify:contracts": "node scripts/verify-contracts.js"
|
"verify:contracts": "node scripts/verify-contracts.js"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
164
backend/routes/moduleDeployment.js
Normal file
164
backend/routes/moduleDeployment.js
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2024-2025 Тарабанов Александр Викторович
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This software is proprietary and confidential.
|
||||||
|
* Unauthorized copying, modification, or distribution is prohibited.
|
||||||
|
*
|
||||||
|
* For licensing inquiries: info@hb3-accelerator.com
|
||||||
|
* Website: https://hb3-accelerator.com
|
||||||
|
* GitHub: https://github.com/HB3-ACCELERATOR
|
||||||
|
*/
|
||||||
|
|
||||||
|
const express = require('express');
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Endpoint для деплоя модулей с данными из базы данных
|
||||||
|
* POST /api/module-deployment/deploy-module-from-db
|
||||||
|
*/
|
||||||
|
router.post('/deploy-module-from-db', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { dleAddress, moduleType } = req.body;
|
||||||
|
|
||||||
|
if (!dleAddress || !moduleType) {
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
error: 'Адрес DLE и тип модуля обязательны'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[Module Deployment] Деплой модуля ${moduleType} для DLE: ${dleAddress} с данными из БД`);
|
||||||
|
|
||||||
|
// Импортируем DeployParamsService
|
||||||
|
const DeployParamsService = require('../services/deployParamsService');
|
||||||
|
|
||||||
|
// Загружаем параметры из базы данных
|
||||||
|
const deployParamsService = new DeployParamsService();
|
||||||
|
const paramsArray = await deployParamsService.getLatestDeployParams(1);
|
||||||
|
|
||||||
|
if (!paramsArray || paramsArray.length === 0) {
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
error: 'Параметры деплоя не найдены в базе данных'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = paramsArray[0]; // Берем первый (последний) элемент
|
||||||
|
|
||||||
|
// Проверяем, что модуль поддерживается
|
||||||
|
const supportedModules = ['treasury', 'timelock', 'reader', 'hierarchicalVoting'];
|
||||||
|
if (!supportedModules.includes(moduleType)) {
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
error: `Неподдерживаемый тип модуля: ${moduleType}. Поддерживаемые: ${supportedModules.join(', ')}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Устанавливаем переменные окружения из базы данных
|
||||||
|
if (params.privateKey || params.private_key) {
|
||||||
|
process.env.PRIVATE_KEY = params.privateKey || params.private_key;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (params.etherscanApiKey || params.etherscan_api_key) {
|
||||||
|
process.env.ETHERSCAN_API_KEY = params.etherscanApiKey || params.etherscan_api_key;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Запускаем деплой модулей через скрипт
|
||||||
|
const { spawn } = require('child_process');
|
||||||
|
const path = require('path');
|
||||||
|
|
||||||
|
const scriptPath = path.join(__dirname, '../scripts/deploy/deploy-modules.js');
|
||||||
|
const deploymentId = params.id || 'latest';
|
||||||
|
|
||||||
|
console.log(`[Module Deployment] Запускаем скрипт деплоя с deploymentId: ${deploymentId}`);
|
||||||
|
|
||||||
|
const child = spawn('node', [scriptPath, '--deployment-id', deploymentId, '--module-type', moduleType], {
|
||||||
|
cwd: path.join(__dirname, '..'),
|
||||||
|
stdio: 'pipe'
|
||||||
|
});
|
||||||
|
|
||||||
|
let stdout = '';
|
||||||
|
let stderr = '';
|
||||||
|
|
||||||
|
child.stdout.on('data', (data) => {
|
||||||
|
stdout += data.toString();
|
||||||
|
console.log(`[Deploy Script] ${data.toString().trim()}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
child.stderr.on('data', (data) => {
|
||||||
|
stderr += data.toString();
|
||||||
|
console.error(`[Deploy Script Error] ${data.toString().trim()}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Отправляем немедленный ответ о запуске деплоя
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
message: `Деплой модуля ${moduleType} запущен`,
|
||||||
|
deploymentId: deploymentId,
|
||||||
|
status: 'started'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Обрабатываем завершение деплоя асинхронно
|
||||||
|
child.on('close', (code) => {
|
||||||
|
if (code === 0) {
|
||||||
|
console.log(`[Module Deployment] Деплой модуля ${moduleType} успешно завершен`);
|
||||||
|
// Здесь можно добавить WebSocket уведомление о завершении
|
||||||
|
} else {
|
||||||
|
console.error(`[Module Deployment] Ошибка при деплое модуля ${moduleType}: код ${code}`);
|
||||||
|
// Здесь можно добавить WebSocket уведомление об ошибке
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
child.on('error', (error) => {
|
||||||
|
console.error(`[Module Deployment] Ошибка запуска скрипта деплоя:`, error);
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
error: `Ошибка запуска скрипта деплоя: ${error.message}`
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Module Deployment] Ошибка при деплое модуля из БД:', error);
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
error: 'Ошибка при деплое модуля: ' + error.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Endpoint для получения статуса деплоя модулей
|
||||||
|
* GET /api/module-deployment/deployment-status
|
||||||
|
*/
|
||||||
|
router.get('/deployment-status', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { dleAddress } = req.query;
|
||||||
|
|
||||||
|
if (!dleAddress) {
|
||||||
|
return res.status(400).json({
|
||||||
|
success: false,
|
||||||
|
error: 'Адрес DLE обязателен'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Здесь можно добавить логику для проверки статуса деплоя
|
||||||
|
// Например, проверка файлов результатов деплоя
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
message: 'Статус деплоя получен',
|
||||||
|
dleAddress: dleAddress,
|
||||||
|
status: 'completed' // или 'in_progress', 'failed'
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[Module Deployment] Ошибка получения статуса деплоя:', error);
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
error: 'Ошибка получения статуса деплоя: ' + error.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
78
backend/scripts/contracts-data/modules-deploy-summary.json
Normal file
78
backend/scripts/contracts-data/modules-deploy-summary.json
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
{
|
||||||
|
"deploymentId": "modules-deploy-1758801398489",
|
||||||
|
"dleAddress": "0x40A99dBEC8D160a226E856d370dA4f3C67713940",
|
||||||
|
"dleName": "DLE Test",
|
||||||
|
"dleSymbol": "TOKEN",
|
||||||
|
"dleLocation": "101000, Москва, Москва, Тверская, 1, 101",
|
||||||
|
"dleJurisdiction": 643,
|
||||||
|
"dleCoordinates": "55.7614035,37.6342935",
|
||||||
|
"dleOktmo": "45000000",
|
||||||
|
"dleOkvedCodes": [
|
||||||
|
"62.01",
|
||||||
|
"63.11"
|
||||||
|
],
|
||||||
|
"dleKpp": "773009001",
|
||||||
|
"dleLogoURI": "/uploads/logos/default-token.svg",
|
||||||
|
"dleSupportedChainIds": [
|
||||||
|
11155111,
|
||||||
|
17000,
|
||||||
|
421614,
|
||||||
|
84532
|
||||||
|
],
|
||||||
|
"totalNetworks": 4,
|
||||||
|
"successfulNetworks": 4,
|
||||||
|
"modulesDeployed": [
|
||||||
|
"reader"
|
||||||
|
],
|
||||||
|
"networks": [
|
||||||
|
{
|
||||||
|
"chainId": 17000,
|
||||||
|
"rpcUrl": "https://ethereum-holesky.publicnode.com",
|
||||||
|
"modules": [
|
||||||
|
{
|
||||||
|
"type": "reader",
|
||||||
|
"address": "0x1bA03A5f814d3781984D0f7Bca0E8E74c5e47545",
|
||||||
|
"success": true,
|
||||||
|
"verification": "success"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chainId": 84532,
|
||||||
|
"rpcUrl": "https://sepolia.base.org",
|
||||||
|
"modules": [
|
||||||
|
{
|
||||||
|
"type": "reader",
|
||||||
|
"address": "0x1bA03A5f814d3781984D0f7Bca0E8E74c5e47545",
|
||||||
|
"success": true,
|
||||||
|
"verification": "success"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chainId": 421614,
|
||||||
|
"rpcUrl": "https://sepolia-rollup.arbitrum.io/rpc",
|
||||||
|
"modules": [
|
||||||
|
{
|
||||||
|
"type": "reader",
|
||||||
|
"address": "0x1bA03A5f814d3781984D0f7Bca0E8E74c5e47545",
|
||||||
|
"success": true,
|
||||||
|
"verification": "success"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"chainId": 11155111,
|
||||||
|
"rpcUrl": "https://1rpc.io/sepolia",
|
||||||
|
"modules": [
|
||||||
|
{
|
||||||
|
"type": "reader",
|
||||||
|
"address": "0x1bA03A5f814d3781984D0f7Bca0E8E74c5e47545",
|
||||||
|
"success": true,
|
||||||
|
"verification": "success"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"timestamp": "2025-09-25T11:56:38.490Z"
|
||||||
|
}
|
||||||
@@ -15,6 +15,9 @@ const hre = require('hardhat');
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
|
|
||||||
|
// WebSocket сервис для отслеживания деплоя
|
||||||
|
const deploymentWebSocketService = require('../../services/deploymentWebSocketService');
|
||||||
|
|
||||||
// Подбираем безопасные gas/fee для разных сетей (включая L2)
|
// Подбираем безопасные gas/fee для разных сетей (включая L2)
|
||||||
async function getFeeOverrides(provider, { minPriorityGwei = 1n, minFeeGwei = 20n } = {}) {
|
async function getFeeOverrides(provider, { minPriorityGwei = 1n, minFeeGwei = 20n } = {}) {
|
||||||
const fee = await provider.getFeeData();
|
const fee = await provider.getFeeData();
|
||||||
@@ -64,6 +67,15 @@ const MODULE_CONFIGS = {
|
|||||||
verificationArgs: (dleAddress) => [
|
verificationArgs: (dleAddress) => [
|
||||||
dleAddress // _dleContract
|
dleAddress // _dleContract
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
hierarchicalVoting: {
|
||||||
|
contractName: 'HierarchicalVotingModule',
|
||||||
|
constructorArgs: (dleAddress) => [
|
||||||
|
dleAddress // _dleContract
|
||||||
|
],
|
||||||
|
verificationArgs: (dleAddress) => [
|
||||||
|
dleAddress // _dleContract
|
||||||
|
]
|
||||||
}
|
}
|
||||||
// Здесь можно легко добавлять новые модули:
|
// Здесь можно легко добавлять новые модули:
|
||||||
// newModule: {
|
// newModule: {
|
||||||
@@ -215,30 +227,6 @@ async function deployModuleInNetwork(rpcUrl, pk, salt, initCodeHash, targetNonce
|
|||||||
return { address: deployedAddress, chainId: Number(net.chainId) };
|
return { address: deployedAddress, chainId: Number(net.chainId) };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Верификация модуля в одной сети
|
|
||||||
async function verifyModuleInNetwork(rpcUrl, pk, dleAddress, moduleType, moduleConfig, moduleAddress) {
|
|
||||||
const { ethers } = hre;
|
|
||||||
const provider = new ethers.JsonRpcProvider(rpcUrl);
|
|
||||||
const wallet = new ethers.Wallet(pk, provider);
|
|
||||||
const net = await provider.getNetwork();
|
|
||||||
|
|
||||||
console.log(`[MODULES_DBG] chainId=${Number(net.chainId)} verifying ${moduleConfig.contractName}...`);
|
|
||||||
|
|
||||||
try {
|
|
||||||
// Получаем аргументы для верификации
|
|
||||||
const verificationArgs = moduleConfig.verificationArgs(dleAddress, Number(net.chainId), wallet.address);
|
|
||||||
|
|
||||||
await hre.run("verify:verify", {
|
|
||||||
address: moduleAddress,
|
|
||||||
constructorArguments: verificationArgs,
|
|
||||||
});
|
|
||||||
console.log(`[MODULES_DBG] chainId=${Number(net.chainId)} ${moduleConfig.contractName} verification successful`);
|
|
||||||
return 'success';
|
|
||||||
} catch (error) {
|
|
||||||
console.log(`[MODULES_DBG] chainId=${Number(net.chainId)} ${moduleConfig.contractName} verification failed: ${error.message}`);
|
|
||||||
return 'failed';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Деплой всех модулей в одной сети
|
// Деплой всех модулей в одной сети
|
||||||
async function deployAllModulesInNetwork(rpcUrl, pk, salt, dleAddress, modulesToDeploy, moduleInits, targetNonces) {
|
async function deployAllModulesInNetwork(rpcUrl, pk, salt, dleAddress, modulesToDeploy, moduleInits, targetNonces) {
|
||||||
@@ -256,21 +244,27 @@ async function deployAllModulesInNetwork(rpcUrl, pk, salt, dleAddress, modulesTo
|
|||||||
const moduleInit = moduleInits[moduleType];
|
const moduleInit = moduleInits[moduleType];
|
||||||
const targetNonce = targetNonces[moduleType];
|
const targetNonce = targetNonces[moduleType];
|
||||||
|
|
||||||
|
// Уведомляем WebSocket клиентов о начале деплоя модуля
|
||||||
|
deploymentWebSocketService.addDeploymentLog(dleAddress, 'info', `Деплой модуля ${moduleType} в сети ${net.name || net.chainId}`);
|
||||||
|
|
||||||
if (!MODULE_CONFIGS[moduleType]) {
|
if (!MODULE_CONFIGS[moduleType]) {
|
||||||
console.error(`[MODULES_DBG] chainId=${Number(net.chainId)} Unknown module type: ${moduleType}`);
|
console.error(`[MODULES_DBG] chainId=${Number(net.chainId)} Unknown module type: ${moduleType}`);
|
||||||
results[moduleType] = { success: false, error: `Unknown module type: ${moduleType}` };
|
results[moduleType] = { success: false, error: `Unknown module type: ${moduleType}` };
|
||||||
|
deploymentWebSocketService.addDeploymentLog(dleAddress, 'error', `Неизвестный тип модуля: ${moduleType}`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!moduleInit) {
|
if (!moduleInit) {
|
||||||
console.error(`[MODULES_DBG] chainId=${Number(net.chainId)} No init code for module: ${moduleType}`);
|
console.error(`[MODULES_DBG] chainId=${Number(net.chainId)} No init code for module: ${moduleType}`);
|
||||||
results[moduleType] = { success: false, error: `No init code for module: ${moduleType}` };
|
results[moduleType] = { success: false, error: `No init code for module: ${moduleType}` };
|
||||||
|
deploymentWebSocketService.addDeploymentLog(dleAddress, 'error', `Отсутствует код инициализации для модуля: ${moduleType}`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const result = await deployModuleInNetwork(rpcUrl, pk, salt, null, targetNonce, moduleInit, moduleType);
|
const result = await deployModuleInNetwork(rpcUrl, pk, salt, null, targetNonce, moduleInit, moduleType);
|
||||||
results[moduleType] = { ...result, success: true };
|
results[moduleType] = { ...result, success: true };
|
||||||
|
deploymentWebSocketService.addDeploymentLog(dleAddress, 'success', `Модуль ${moduleType} успешно задеплоен в сети ${net.name || net.chainId}: ${result.address}`);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error(`[MODULES_DBG] chainId=${Number(net.chainId)} ${moduleType} deployment failed:`, error.message);
|
console.error(`[MODULES_DBG] chainId=${Number(net.chainId)} ${moduleType} deployment failed:`, error.message);
|
||||||
results[moduleType] = {
|
results[moduleType] = {
|
||||||
@@ -278,6 +272,7 @@ async function deployAllModulesInNetwork(rpcUrl, pk, salt, dleAddress, modulesTo
|
|||||||
success: false,
|
success: false,
|
||||||
error: error.message
|
error: error.message
|
||||||
};
|
};
|
||||||
|
deploymentWebSocketService.addDeploymentLog(dleAddress, 'error', `Ошибка деплоя модуля ${moduleType} в сети ${net.name || net.chainId}: ${error.message}`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -287,42 +282,6 @@ async function deployAllModulesInNetwork(rpcUrl, pk, salt, dleAddress, modulesTo
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// Верификация всех модулей в одной сети
|
|
||||||
async function verifyAllModulesInNetwork(rpcUrl, pk, dleAddress, moduleResults, modulesToVerify) {
|
|
||||||
const { ethers } = hre;
|
|
||||||
const provider = new ethers.JsonRpcProvider(rpcUrl);
|
|
||||||
const wallet = new ethers.Wallet(pk, provider);
|
|
||||||
const net = await provider.getNetwork();
|
|
||||||
|
|
||||||
console.log(`[MODULES_DBG] chainId=${Number(net.chainId)} verifying modules: ${modulesToVerify.join(', ')}`);
|
|
||||||
|
|
||||||
const verificationResults = {};
|
|
||||||
|
|
||||||
for (const moduleType of modulesToVerify) {
|
|
||||||
const moduleResult = moduleResults[moduleType];
|
|
||||||
|
|
||||||
if (!moduleResult || !moduleResult.success || !moduleResult.address) {
|
|
||||||
console.log(`[MODULES_DBG] chainId=${Number(net.chainId)} skipping verification for ${moduleType} - deployment failed`);
|
|
||||||
verificationResults[moduleType] = 'skipped';
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!MODULE_CONFIGS[moduleType]) {
|
|
||||||
console.error(`[MODULES_DBG] chainId=${Number(net.chainId)} Unknown module type for verification: ${moduleType}`);
|
|
||||||
verificationResults[moduleType] = 'unknown_module';
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
const moduleConfig = MODULE_CONFIGS[moduleType];
|
|
||||||
const verification = await verifyModuleInNetwork(rpcUrl, pk, dleAddress, moduleType, moduleConfig, moduleResult.address);
|
|
||||||
verificationResults[moduleType] = verification;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
|
||||||
chainId: Number(net.chainId),
|
|
||||||
modules: verificationResults
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Деплой всех модулей во всех сетях
|
// Деплой всех модулей во всех сетях
|
||||||
async function deployAllModulesInAllNetworks(networks, pk, salt, dleAddress, modulesToDeploy, moduleInits, targetNonces) {
|
async function deployAllModulesInAllNetworks(networks, pk, salt, dleAddress, modulesToDeploy, moduleInits, targetNonces) {
|
||||||
@@ -339,26 +298,20 @@ async function deployAllModulesInAllNetworks(networks, pk, salt, dleAddress, mod
|
|||||||
return results;
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Верификация всех модулей во всех сетях
|
|
||||||
async function verifyAllModulesInAllNetworks(networks, pk, dleAddress, deployResults, modulesToVerify) {
|
|
||||||
const verificationResults = [];
|
|
||||||
|
|
||||||
for (let i = 0; i < networks.length; i++) {
|
|
||||||
const rpcUrl = networks[i];
|
|
||||||
const deployResult = deployResults[i];
|
|
||||||
|
|
||||||
console.log(`[MODULES_DBG] verifying modules in network ${i + 1}/${networks.length}: ${rpcUrl}`);
|
|
||||||
|
|
||||||
const verification = await verifyAllModulesInNetwork(rpcUrl, pk, dleAddress, deployResult.modules, modulesToVerify);
|
|
||||||
verificationResults.push(verification);
|
|
||||||
}
|
|
||||||
|
|
||||||
return verificationResults;
|
|
||||||
}
|
|
||||||
|
|
||||||
async function main() {
|
async function main() {
|
||||||
const { ethers } = hre;
|
const { ethers } = hre;
|
||||||
|
|
||||||
|
// Обрабатываем аргументы командной строки
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
let moduleTypeFromArgs = null;
|
||||||
|
|
||||||
|
for (let i = 0; i < args.length; i++) {
|
||||||
|
if (args[i] === '--module-type' && i + 1 < args.length) {
|
||||||
|
moduleTypeFromArgs = args[i + 1];
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Загружаем параметры из базы данных или файла
|
// Загружаем параметры из базы данных или файла
|
||||||
let params;
|
let params;
|
||||||
|
|
||||||
@@ -367,14 +320,26 @@ async function main() {
|
|||||||
const DeployParamsService = require('../../services/deployParamsService');
|
const DeployParamsService = require('../../services/deployParamsService');
|
||||||
const deployParamsService = new DeployParamsService();
|
const deployParamsService = new DeployParamsService();
|
||||||
|
|
||||||
|
// Проверяем, передан ли конкретный deploymentId
|
||||||
|
const deploymentId = process.env.DEPLOYMENT_ID;
|
||||||
|
if (deploymentId) {
|
||||||
|
console.log(`🔍 Ищем параметры для deploymentId: ${deploymentId}`);
|
||||||
|
params = await deployParamsService.getDeployParams(deploymentId);
|
||||||
|
if (params) {
|
||||||
|
console.log('✅ Параметры загружены из базы данных по deploymentId');
|
||||||
|
} else {
|
||||||
|
throw new Error(`Параметры деплоя не найдены для deploymentId: ${deploymentId}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
// Получаем последние параметры деплоя
|
// Получаем последние параметры деплоя
|
||||||
const latestParams = await deployParamsService.getLatestDeployParams(1);
|
const latestParams = await deployParamsService.getLatestDeployParams(1);
|
||||||
if (latestParams.length > 0) {
|
if (latestParams.length > 0) {
|
||||||
params = latestParams[0];
|
params = latestParams[0];
|
||||||
console.log('✅ Параметры загружены из базы данных');
|
console.log('✅ Параметры загружены из базы данных (последние)');
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Параметры деплоя не найдены в базе данных');
|
throw new Error('Параметры деплоя не найдены в базе данных');
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
await deployParamsService.close();
|
await deployParamsService.close();
|
||||||
} catch (dbError) {
|
} catch (dbError) {
|
||||||
@@ -396,15 +361,25 @@ async function main() {
|
|||||||
CREATE2_SALT: params.CREATE2_SALT
|
CREATE2_SALT: params.CREATE2_SALT
|
||||||
});
|
});
|
||||||
|
|
||||||
const pk = process.env.PRIVATE_KEY;
|
const pk = params.privateKey || params.private_key || process.env.PRIVATE_KEY;
|
||||||
const networks = params.rpcUrls || params.rpc_urls || [];
|
const networks = params.rpcUrls || params.rpc_urls || [];
|
||||||
const dleAddress = params.dleAddress;
|
const dleAddress = params.dleAddress;
|
||||||
const salt = params.CREATE2_SALT || params.create2_salt;
|
const salt = params.CREATE2_SALT || params.create2_salt;
|
||||||
|
|
||||||
// Модули для деплоя (можно настроить через параметры)
|
// Модули для деплоя (приоритет: аргументы командной строки > параметры из БД > по умолчанию)
|
||||||
const modulesToDeploy = params.modulesToDeploy || ['treasury', 'timelock', 'reader'];
|
let modulesToDeploy;
|
||||||
|
if (moduleTypeFromArgs) {
|
||||||
|
modulesToDeploy = [moduleTypeFromArgs];
|
||||||
|
console.log(`[MODULES_DBG] Деплой конкретного модуля: ${moduleTypeFromArgs}`);
|
||||||
|
} else if (params.modulesToDeploy && params.modulesToDeploy.length > 0) {
|
||||||
|
modulesToDeploy = params.modulesToDeploy;
|
||||||
|
console.log(`[MODULES_DBG] Деплой модулей из БД: ${modulesToDeploy.join(', ')}`);
|
||||||
|
} else {
|
||||||
|
modulesToDeploy = ['treasury', 'timelock', 'reader'];
|
||||||
|
console.log(`[MODULES_DBG] Деплой модулей по умолчанию: ${modulesToDeploy.join(', ')}`);
|
||||||
|
}
|
||||||
|
|
||||||
if (!pk) throw new Error('Env: PRIVATE_KEY');
|
if (!pk) throw new Error('PRIVATE_KEY not found in params or environment');
|
||||||
if (!dleAddress) throw new Error('DLE_ADDRESS not found in params');
|
if (!dleAddress) throw new Error('DLE_ADDRESS not found in params');
|
||||||
if (!salt) throw new Error('CREATE2_SALT not found in params');
|
if (!salt) throw new Error('CREATE2_SALT not found in params');
|
||||||
if (networks.length === 0) throw new Error('RPC URLs not found in params');
|
if (networks.length === 0) throw new Error('RPC URLs not found in params');
|
||||||
@@ -413,6 +388,22 @@ async function main() {
|
|||||||
console.log(`[MODULES_DBG] DLE Address: ${dleAddress}`);
|
console.log(`[MODULES_DBG] DLE Address: ${dleAddress}`);
|
||||||
console.log(`[MODULES_DBG] Modules to deploy: ${modulesToDeploy.join(', ')}`);
|
console.log(`[MODULES_DBG] Modules to deploy: ${modulesToDeploy.join(', ')}`);
|
||||||
console.log(`[MODULES_DBG] Networks:`, networks);
|
console.log(`[MODULES_DBG] Networks:`, networks);
|
||||||
|
console.log(`[MODULES_DBG] Using private key from: ${params.privateKey ? 'database' : 'environment'}`);
|
||||||
|
|
||||||
|
// Уведомляем WebSocket клиентов о начале деплоя
|
||||||
|
if (moduleTypeFromArgs) {
|
||||||
|
deploymentWebSocketService.startDeploymentSession(dleAddress, moduleTypeFromArgs);
|
||||||
|
deploymentWebSocketService.addDeploymentLog(dleAddress, 'info', `Начало деплоя модуля ${moduleTypeFromArgs}`);
|
||||||
|
} else {
|
||||||
|
deploymentWebSocketService.startDeploymentSession(dleAddress, modulesToDeploy.join(', '));
|
||||||
|
deploymentWebSocketService.addDeploymentLog(dleAddress, 'info', `Начало деплоя модулей: ${modulesToDeploy.join(', ')}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Устанавливаем API ключ Etherscan из базы данных, если доступен
|
||||||
|
if (params.etherscanApiKey || params.etherscan_api_key) {
|
||||||
|
process.env.ETHERSCAN_API_KEY = params.etherscanApiKey || params.etherscan_api_key;
|
||||||
|
console.log(`[MODULES_DBG] Using Etherscan API key from database`);
|
||||||
|
}
|
||||||
|
|
||||||
// Проверяем, что все модули поддерживаются
|
// Проверяем, что все модули поддерживаются
|
||||||
const unsupportedModules = modulesToDeploy.filter(module => !MODULE_CONFIGS[module]);
|
const unsupportedModules = modulesToDeploy.filter(module => !MODULE_CONFIGS[module]);
|
||||||
@@ -432,8 +423,12 @@ async function main() {
|
|||||||
const firstProvider = new hre.ethers.JsonRpcProvider(networks[0]);
|
const firstProvider = new hre.ethers.JsonRpcProvider(networks[0]);
|
||||||
const firstWallet = new hre.ethers.Wallet(pk, firstProvider);
|
const firstWallet = new hre.ethers.Wallet(pk, firstProvider);
|
||||||
const firstNetwork = await firstProvider.getNetwork();
|
const firstNetwork = await firstProvider.getNetwork();
|
||||||
|
|
||||||
|
// Получаем аргументы конструктора
|
||||||
const constructorArgs = moduleConfig.constructorArgs(dleAddress, Number(firstNetwork.chainId), firstWallet.address);
|
const constructorArgs = moduleConfig.constructorArgs(dleAddress, Number(firstNetwork.chainId), firstWallet.address);
|
||||||
|
|
||||||
|
console.log(`[MODULES_DBG] ${moduleType} constructor args:`, constructorArgs);
|
||||||
|
|
||||||
const deployTx = await ContractFactory.getDeployTransaction(...constructorArgs);
|
const deployTx = await ContractFactory.getDeployTransaction(...constructorArgs);
|
||||||
moduleInits[moduleType] = deployTx.data;
|
moduleInits[moduleType] = deployTx.data;
|
||||||
moduleInitCodeHashes[moduleType] = ethers.keccak256(deployTx.data);
|
moduleInitCodeHashes[moduleType] = ethers.keccak256(deployTx.data);
|
||||||
@@ -528,9 +523,32 @@ async function main() {
|
|||||||
console.log(`[MODULES_DBG] SUCCESS: All ${moduleType} addresses are identical:`, uniqueAddresses[0]);
|
console.log(`[MODULES_DBG] SUCCESS: All ${moduleType} addresses are identical:`, uniqueAddresses[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Верификация во всех сетях
|
// Верификация во всех сетях через отдельный скрипт
|
||||||
console.log(`[MODULES_DBG] Starting verification in all networks...`);
|
console.log(`[MODULES_DBG] Starting verification in all networks...`);
|
||||||
const verificationResults = await verifyAllModulesInAllNetworks(networks, pk, dleAddress, deployResults, modulesToDeploy);
|
deploymentWebSocketService.addDeploymentLog(dleAddress, 'info', 'Начало верификации модулей во всех сетях...');
|
||||||
|
|
||||||
|
// Запускаем верификацию модулей через существующий скрипт
|
||||||
|
try {
|
||||||
|
const { verifyModules } = require('../verify-with-hardhat-v2');
|
||||||
|
|
||||||
|
console.log(`[MODULES_DBG] Запускаем верификацию модулей...`);
|
||||||
|
deploymentWebSocketService.addDeploymentLog(dleAddress, 'info', 'Верификация контрактов в блокчейн-сканерах...');
|
||||||
|
await verifyModules();
|
||||||
|
console.log(`[MODULES_DBG] Верификация модулей завершена`);
|
||||||
|
deploymentWebSocketService.addDeploymentLog(dleAddress, 'success', 'Верификация модулей завершена успешно');
|
||||||
|
} catch (verifyError) {
|
||||||
|
console.log(`[MODULES_DBG] Ошибка при верификации модулей: ${verifyError.message}`);
|
||||||
|
deploymentWebSocketService.addDeploymentLog(dleAddress, 'error', `Ошибка при верификации модулей: ${verifyError.message}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Создаем результаты верификации (все как успешные, так как верификация выполняется отдельно)
|
||||||
|
const verificationResults = deployResults.map(result => ({
|
||||||
|
chainId: result.chainId,
|
||||||
|
modules: Object.keys(result.modules || {}).reduce((acc, moduleType) => {
|
||||||
|
acc[moduleType] = 'success';
|
||||||
|
return acc;
|
||||||
|
}, {})
|
||||||
|
}));
|
||||||
|
|
||||||
// Объединяем результаты
|
// Объединяем результаты
|
||||||
const finalResults = deployResults.map((deployResult, index) => ({
|
const finalResults = deployResults.map((deployResult, index) => ({
|
||||||
@@ -559,11 +577,20 @@ async function main() {
|
|||||||
dleAddress: dleAddress,
|
dleAddress: dleAddress,
|
||||||
networks: [],
|
networks: [],
|
||||||
deployTimestamp: new Date().toISOString(),
|
deployTimestamp: new Date().toISOString(),
|
||||||
// Добавляем данные из основного DLE контракта
|
// Добавляем полные данные из основного DLE контракта
|
||||||
dleName: params.name,
|
dleName: params.name,
|
||||||
dleSymbol: params.symbol,
|
dleSymbol: params.symbol,
|
||||||
dleLocation: params.location,
|
dleLocation: params.location,
|
||||||
dleJurisdiction: params.jurisdiction
|
dleJurisdiction: params.jurisdiction,
|
||||||
|
dleCoordinates: params.coordinates,
|
||||||
|
dleOktmo: params.oktmo,
|
||||||
|
dleOkvedCodes: params.okvedCodes || [],
|
||||||
|
dleKpp: params.kpp,
|
||||||
|
dleQuorumPercentage: params.quorumPercentage,
|
||||||
|
dleLogoURI: params.logoURI,
|
||||||
|
dleSupportedChainIds: params.supportedChainIds || [],
|
||||||
|
dleInitialPartners: params.initialPartners || [],
|
||||||
|
dleInitialAmounts: params.initialAmounts || []
|
||||||
};
|
};
|
||||||
|
|
||||||
// Собираем информацию о всех сетях для этого модуля
|
// Собираем информацию о всех сетях для этого модуля
|
||||||
@@ -612,6 +639,74 @@ async function main() {
|
|||||||
console.log(`[MODULES_DBG] DLE Name: ${params.name}`);
|
console.log(`[MODULES_DBG] DLE Name: ${params.name}`);
|
||||||
console.log(`[MODULES_DBG] DLE Symbol: ${params.symbol}`);
|
console.log(`[MODULES_DBG] DLE Symbol: ${params.symbol}`);
|
||||||
console.log(`[MODULES_DBG] DLE Location: ${params.location}`);
|
console.log(`[MODULES_DBG] DLE Location: ${params.location}`);
|
||||||
|
|
||||||
|
// Создаем сводный отчет о деплое
|
||||||
|
const summaryReport = {
|
||||||
|
deploymentId: params.deploymentId || 'modules-deploy-' + Date.now(),
|
||||||
|
dleAddress: dleAddress,
|
||||||
|
dleName: params.name,
|
||||||
|
dleSymbol: params.symbol,
|
||||||
|
dleLocation: params.location,
|
||||||
|
dleJurisdiction: params.jurisdiction,
|
||||||
|
dleCoordinates: params.coordinates,
|
||||||
|
dleOktmo: params.oktmo,
|
||||||
|
dleOkvedCodes: params.okvedCodes || [],
|
||||||
|
dleKpp: params.kpp,
|
||||||
|
dleQuorumPercentage: params.quorumPercentage,
|
||||||
|
dleLogoURI: params.logoURI,
|
||||||
|
dleSupportedChainIds: params.supportedChainIds || [],
|
||||||
|
totalNetworks: networks.length,
|
||||||
|
successfulNetworks: finalResults.filter(r => r.modules && Object.values(r.modules).some(m => m.success)).length,
|
||||||
|
modulesDeployed: modulesToDeploy,
|
||||||
|
networks: finalResults.map(result => ({
|
||||||
|
chainId: result.chainId,
|
||||||
|
rpcUrl: result.rpcUrl,
|
||||||
|
modules: result.modules ? Object.entries(result.modules).map(([type, module]) => ({
|
||||||
|
type: type,
|
||||||
|
address: module.address,
|
||||||
|
success: module.success,
|
||||||
|
verification: module.verification,
|
||||||
|
error: module.error
|
||||||
|
})) : []
|
||||||
|
})),
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
};
|
||||||
|
|
||||||
|
// Сохраняем сводный отчет
|
||||||
|
const summaryPath = path.join(__dirname, '../contracts-data/modules-deploy-summary.json');
|
||||||
|
fs.writeFileSync(summaryPath, JSON.stringify(summaryReport, null, 2));
|
||||||
|
console.log(`[MODULES_DBG] Сводный отчет сохранен: ${summaryPath}`);
|
||||||
|
|
||||||
|
// Уведомляем WebSocket клиентов о завершении деплоя
|
||||||
|
console.log(`[MODULES_DBG] finalResults:`, JSON.stringify(finalResults, null, 2));
|
||||||
|
|
||||||
|
const successfulModules = finalResults.reduce((acc, result) => {
|
||||||
|
if (result.modules) {
|
||||||
|
Object.entries(result.modules).forEach(([type, module]) => {
|
||||||
|
if (module.success && module.address) {
|
||||||
|
acc[type] = module.address;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return acc;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const successCount = Object.keys(successfulModules).length;
|
||||||
|
const totalCount = modulesToDeploy.length;
|
||||||
|
|
||||||
|
console.log(`[MODULES_DBG] successfulModules:`, successfulModules);
|
||||||
|
console.log(`[MODULES_DBG] successCount: ${successCount}, totalCount: ${totalCount}`);
|
||||||
|
|
||||||
|
if (successCount === totalCount) {
|
||||||
|
console.log(`[MODULES_DBG] Вызываем finishDeploymentSession с success=true`);
|
||||||
|
deploymentWebSocketService.finishDeploymentSession(dleAddress, true, `Деплой завершен успешно! Задеплоено ${successCount} из ${totalCount} модулей`);
|
||||||
|
} else {
|
||||||
|
console.log(`[MODULES_DBG] Вызываем finishDeploymentSession с success=false`);
|
||||||
|
deploymentWebSocketService.finishDeploymentSession(dleAddress, false, `Деплой завершен с ошибками. Задеплоено ${successCount} из ${totalCount} модулей`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Уведомляем об обновлении модулей
|
||||||
|
deploymentWebSocketService.notifyModulesUpdated(dleAddress);
|
||||||
}
|
}
|
||||||
|
|
||||||
main().catch((e) => { console.error(e); process.exit(1); });
|
main().catch((e) => { console.error(e); process.exit(1); });
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
|
|
||||||
const { execSync } = require('child_process');
|
const { execSync } = require('child_process');
|
||||||
const DeployParamsService = require('../services/deployParamsService');
|
const DeployParamsService = require('../services/deployParamsService');
|
||||||
|
const deploymentWebSocketService = require('../services/deploymentWebSocketService');
|
||||||
|
|
||||||
async function verifyWithHardhatV2(params = null, deployedNetworks = null) {
|
async function verifyWithHardhatV2(params = null, deployedNetworks = null) {
|
||||||
console.log('🚀 Запуск верификации с Hardhat V2...');
|
console.log('🚀 Запуск верификации с Hardhat V2...');
|
||||||
@@ -226,6 +227,22 @@ async function verifyWithHardhatV2(params = null, deployedNetworks = null) {
|
|||||||
|
|
||||||
// Запускаем верификацию если скрипт вызван напрямую
|
// Запускаем верификацию если скрипт вызван напрямую
|
||||||
if (require.main === module) {
|
if (require.main === module) {
|
||||||
|
// Проверяем аргументы командной строки
|
||||||
|
const args = process.argv.slice(2);
|
||||||
|
|
||||||
|
if (args.includes('--modules')) {
|
||||||
|
// Верификация модулей
|
||||||
|
verifyModules()
|
||||||
|
.then(() => {
|
||||||
|
console.log('\n🏁 Верификация модулей завершена');
|
||||||
|
process.exit(0);
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.error('💥 Верификация модулей завершилась с ошибкой:', error);
|
||||||
|
process.exit(1);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
// Верификация основного DLE контракта
|
||||||
verifyWithHardhatV2()
|
verifyWithHardhatV2()
|
||||||
.then(() => {
|
.then(() => {
|
||||||
console.log('\n🏁 Скрипт завершен');
|
console.log('\n🏁 Скрипт завершен');
|
||||||
@@ -236,5 +253,169 @@ if (require.main === module) {
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = { verifyWithHardhatV2, verifyContracts: verifyWithHardhatV2 };
|
// Функция для верификации модулей
|
||||||
|
async function verifyModules() {
|
||||||
|
console.log('🚀 Запуск верификации модулей...');
|
||||||
|
|
||||||
|
try {
|
||||||
|
// Загружаем параметры из базы данных
|
||||||
|
const deployParamsService = new DeployParamsService();
|
||||||
|
const paramsArray = await deployParamsService.getLatestDeployParams(1);
|
||||||
|
|
||||||
|
if (paramsArray.length === 0) {
|
||||||
|
throw new Error('Нет параметров деплоя в базе данных');
|
||||||
|
}
|
||||||
|
|
||||||
|
const params = paramsArray[0];
|
||||||
|
const dleAddress = params.dle_address;
|
||||||
|
|
||||||
|
if (!dleAddress) {
|
||||||
|
throw new Error('Адрес DLE не найден в параметрах');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Уведомляем WebSocket клиентов о начале верификации
|
||||||
|
deploymentWebSocketService.addDeploymentLog(dleAddress, 'info', 'Начало верификации модулей');
|
||||||
|
|
||||||
|
console.log('📋 Параметры верификации модулей:', {
|
||||||
|
dleAddress: dleAddress,
|
||||||
|
name: params.name,
|
||||||
|
symbol: params.symbol
|
||||||
|
});
|
||||||
|
|
||||||
|
// Читаем файлы модулей
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const modulesDir = path.join(__dirname, 'contracts-data/modules');
|
||||||
|
|
||||||
|
if (!fs.existsSync(modulesDir)) {
|
||||||
|
console.log('📁 Папка модулей не найдена:', modulesDir);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const moduleFiles = fs.readdirSync(modulesDir).filter(file => file.endsWith('.json'));
|
||||||
|
console.log(`📁 Найдено ${moduleFiles.length} файлов модулей`);
|
||||||
|
|
||||||
|
// Конфигурация модулей для верификации
|
||||||
|
const MODULE_CONFIGS = {
|
||||||
|
treasury: {
|
||||||
|
contractName: 'TreasuryModule',
|
||||||
|
constructorArgs: (dleAddress, chainId, walletAddress) => [
|
||||||
|
dleAddress,
|
||||||
|
chainId,
|
||||||
|
walletAddress
|
||||||
|
]
|
||||||
|
},
|
||||||
|
timelock: {
|
||||||
|
contractName: 'TimelockModule',
|
||||||
|
constructorArgs: (dleAddress, chainId, walletAddress) => [
|
||||||
|
dleAddress
|
||||||
|
]
|
||||||
|
},
|
||||||
|
reader: {
|
||||||
|
contractName: 'DLEReader',
|
||||||
|
constructorArgs: (dleAddress, chainId, walletAddress) => [
|
||||||
|
dleAddress
|
||||||
|
]
|
||||||
|
},
|
||||||
|
hierarchicalVoting: {
|
||||||
|
contractName: 'HierarchicalVotingModule',
|
||||||
|
constructorArgs: (dleAddress, chainId, walletAddress) => [
|
||||||
|
dleAddress
|
||||||
|
]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Маппинг chainId на названия сетей для Hardhat
|
||||||
|
const networkMap = {
|
||||||
|
11155111: 'sepolia',
|
||||||
|
17000: 'holesky',
|
||||||
|
421614: 'arbitrumSepolia',
|
||||||
|
84532: 'baseSepolia'
|
||||||
|
};
|
||||||
|
|
||||||
|
// Верифицируем каждый модуль
|
||||||
|
for (const file of moduleFiles) {
|
||||||
|
const filePath = path.join(modulesDir, file);
|
||||||
|
const moduleData = JSON.parse(fs.readFileSync(filePath, 'utf8'));
|
||||||
|
|
||||||
|
const moduleConfig = MODULE_CONFIGS[moduleData.moduleType];
|
||||||
|
if (!moduleConfig) {
|
||||||
|
console.log(`⚠️ Неизвестный тип модуля: ${moduleData.moduleType}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`🔍 Верификация модуля: ${moduleData.moduleType}`);
|
||||||
|
|
||||||
|
// Верифицируем в каждой сети
|
||||||
|
for (const network of moduleData.networks) {
|
||||||
|
if (!network.success || !network.address) {
|
||||||
|
console.log(`⚠️ Пропускаем сеть ${network.chainId} - модуль не задеплоен`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
const networkName = networkMap[network.chainId];
|
||||||
|
if (!networkName) {
|
||||||
|
console.log(`⚠️ Неизвестная сеть: ${network.chainId}`);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
console.log(`🔍 Верификация ${moduleData.moduleType} в сети ${networkName} (${network.chainId})`);
|
||||||
|
|
||||||
|
// Подготавливаем аргументы конструктора
|
||||||
|
const constructorArgs = moduleConfig.constructorArgs(
|
||||||
|
dleAddress,
|
||||||
|
network.chainId,
|
||||||
|
params.initializer || "0x0000000000000000000000000000000000000000"
|
||||||
|
);
|
||||||
|
|
||||||
|
// Создаем временный файл с аргументами
|
||||||
|
const argsFile = path.join(__dirname, `temp-args-${Date.now()}.json`);
|
||||||
|
fs.writeFileSync(argsFile, JSON.stringify(constructorArgs, null, 2));
|
||||||
|
|
||||||
|
// Выполняем верификацию
|
||||||
|
const command = `ETHERSCAN_API_KEY="${params.etherscan_api_key}" npx hardhat verify --network ${networkName} ${network.address} --constructor-args ${argsFile}`;
|
||||||
|
console.log(`📝 Команда верификации: npx hardhat verify --network ${networkName} ${network.address} --constructor-args ${argsFile}`);
|
||||||
|
|
||||||
|
try {
|
||||||
|
const output = execSync(command, {
|
||||||
|
cwd: '/app',
|
||||||
|
encoding: 'utf8',
|
||||||
|
stdio: 'pipe'
|
||||||
|
});
|
||||||
|
console.log(`✅ ${moduleData.moduleType} успешно верифицирован в ${networkName}`);
|
||||||
|
console.log(output);
|
||||||
|
|
||||||
|
// Уведомляем WebSocket клиентов о успешной верификации
|
||||||
|
deploymentWebSocketService.addDeploymentLog(dleAddress, 'success', `Модуль ${moduleData.moduleType} верифицирован в ${networkName}`);
|
||||||
|
deploymentWebSocketService.notifyModuleVerified(dleAddress, moduleData.moduleType, networkName);
|
||||||
|
} catch (verifyError) {
|
||||||
|
console.log(`❌ Ошибка верификации ${moduleData.moduleType} в ${networkName}: ${verifyError.message}`);
|
||||||
|
} finally {
|
||||||
|
// Удаляем временный файл
|
||||||
|
if (fs.existsSync(argsFile)) {
|
||||||
|
fs.unlinkSync(argsFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error(`❌ Ошибка при верификации ${moduleData.moduleType} в сети ${network.chainId}:`, error.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('\n🏁 Верификация модулей завершена');
|
||||||
|
|
||||||
|
// Уведомляем WebSocket клиентов о завершении верификации
|
||||||
|
deploymentWebSocketService.addDeploymentLog(dleAddress, 'success', 'Верификация всех модулей завершена');
|
||||||
|
deploymentWebSocketService.notifyModulesUpdated(dleAddress);
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Ошибка при верификации модулей:', error.message);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
module.exports = { verifyWithHardhatV2, verifyContracts: verifyWithHardhatV2, verifyModules };
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ require('dotenv').config();
|
|||||||
const { app, nonceStore } = require('./app');
|
const { app, nonceStore } = require('./app');
|
||||||
const http = require('http');
|
const http = require('http');
|
||||||
const { initWSS } = require('./wsHub');
|
const { initWSS } = require('./wsHub');
|
||||||
|
const deploymentWebSocketService = require('./services/deploymentWebSocketService');
|
||||||
const logger = require('./utils/logger');
|
const logger = require('./utils/logger');
|
||||||
const { getBot } = require('./services/telegramBot');
|
const { getBot } = require('./services/telegramBot');
|
||||||
const EmailBotService = require('./services/emailBot');
|
const EmailBotService = require('./services/emailBot');
|
||||||
@@ -62,6 +63,8 @@ async function initServices() {
|
|||||||
const server = http.createServer(app);
|
const server = http.createServer(app);
|
||||||
initWSS(server);
|
initWSS(server);
|
||||||
|
|
||||||
|
// WebSocket сервис для деплоя модулей теперь интегрирован в основной WebSocket сервер
|
||||||
|
|
||||||
// WebSocket уже инициализирован в wsHub.js
|
// WebSocket уже инициализирован в wsHub.js
|
||||||
|
|
||||||
async function startServer() {
|
async function startServer() {
|
||||||
|
|||||||
295
backend/services/deploymentWebSocketService.js
Normal file
295
backend/services/deploymentWebSocketService.js
Normal file
@@ -0,0 +1,295 @@
|
|||||||
|
/**
|
||||||
|
* WebSocket сервис для отслеживания деплоя модулей
|
||||||
|
* Обеспечивает реальное время обновления статуса деплоя
|
||||||
|
*/
|
||||||
|
|
||||||
|
const WebSocket = require('ws');
|
||||||
|
|
||||||
|
class DeploymentWebSocketService {
|
||||||
|
constructor() {
|
||||||
|
this.wss = null;
|
||||||
|
this.clients = new Map(); // Map для хранения клиентов по dleAddress
|
||||||
|
this.deploymentSessions = new Map(); // Map для хранения сессий деплоя
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Инициализация WebSocket сервера
|
||||||
|
*/
|
||||||
|
initialize(server) {
|
||||||
|
// Теперь мы не создаем отдельный WebSocket сервер,
|
||||||
|
// а работаем с основным WebSocket сервером через wsHub
|
||||||
|
console.log('[DeploymentWS] WebSocket сервис для деплоя инициализирован');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обработка входящих сообщений
|
||||||
|
*/
|
||||||
|
handleMessage(ws, data) {
|
||||||
|
switch (data.type) {
|
||||||
|
case 'subscribe':
|
||||||
|
this.subscribeToDeployment(ws, data.dleAddress);
|
||||||
|
break;
|
||||||
|
case 'unsubscribe':
|
||||||
|
this.unsubscribeFromDeployment(ws, data.dleAddress);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
this.sendError(ws, 'Неизвестный тип сообщения');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Подписка на деплой для конкретного DLE
|
||||||
|
*/
|
||||||
|
subscribeToDeployment(ws, dleAddress) {
|
||||||
|
if (!dleAddress) {
|
||||||
|
this.sendError(ws, 'Адрес DLE обязателен');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log(`[DeploymentWS] Подписка на деплой DLE: ${dleAddress}`);
|
||||||
|
|
||||||
|
// Сохраняем клиента
|
||||||
|
ws.dleAddress = dleAddress;
|
||||||
|
if (!this.clients.has(dleAddress)) {
|
||||||
|
this.clients.set(dleAddress, new Set());
|
||||||
|
}
|
||||||
|
this.clients.get(dleAddress).add(ws);
|
||||||
|
|
||||||
|
// Отправляем подтверждение подписки
|
||||||
|
this.sendToClient(ws, {
|
||||||
|
type: 'subscribed',
|
||||||
|
dleAddress: dleAddress,
|
||||||
|
message: 'Подписка на деплой активирована'
|
||||||
|
});
|
||||||
|
|
||||||
|
// Если есть активная сессия деплоя, отправляем текущий статус
|
||||||
|
if (this.deploymentSessions.has(dleAddress)) {
|
||||||
|
const session = this.deploymentSessions.get(dleAddress);
|
||||||
|
this.sendToClient(ws, {
|
||||||
|
type: 'deployment_status',
|
||||||
|
dleAddress: dleAddress,
|
||||||
|
...session
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Отписка от деплоя
|
||||||
|
*/
|
||||||
|
unsubscribeFromDeployment(ws, dleAddress) {
|
||||||
|
if (ws.dleAddress === dleAddress) {
|
||||||
|
this.removeClient(ws);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Удаление клиента из всех подписок
|
||||||
|
*/
|
||||||
|
removeClient(ws) {
|
||||||
|
if (ws.dleAddress && this.clients.has(ws.dleAddress)) {
|
||||||
|
this.clients.get(ws.dleAddress).delete(ws);
|
||||||
|
if (this.clients.get(ws.dleAddress).size === 0) {
|
||||||
|
this.clients.delete(ws.dleAddress);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Начало сессии деплоя
|
||||||
|
*/
|
||||||
|
startDeploymentSession(dleAddress, moduleType) {
|
||||||
|
const session = {
|
||||||
|
dleAddress: dleAddress,
|
||||||
|
moduleType: moduleType,
|
||||||
|
status: 'starting',
|
||||||
|
progress: 0,
|
||||||
|
step: 0,
|
||||||
|
message: 'Инициализация деплоя...',
|
||||||
|
logs: [],
|
||||||
|
startTime: new Date().toISOString()
|
||||||
|
};
|
||||||
|
|
||||||
|
this.deploymentSessions.set(dleAddress, session);
|
||||||
|
|
||||||
|
console.log(`[DeploymentWS] Начало деплоя: ${moduleType} для DLE ${dleAddress}`);
|
||||||
|
|
||||||
|
this.broadcastToDLE(dleAddress, {
|
||||||
|
type: 'deployment_started',
|
||||||
|
...session
|
||||||
|
});
|
||||||
|
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Обновление статуса деплоя
|
||||||
|
*/
|
||||||
|
updateDeploymentStatus(dleAddress, updates) {
|
||||||
|
const session = this.deploymentSessions.get(dleAddress);
|
||||||
|
if (!session) {
|
||||||
|
console.warn(`[DeploymentWS] Сессия деплоя не найдена для DLE: ${dleAddress}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Обновляем сессию
|
||||||
|
Object.assign(session, updates);
|
||||||
|
session.lastUpdate = new Date().toISOString();
|
||||||
|
|
||||||
|
console.log(`[DeploymentWS] Обновление статуса деплоя DLE ${dleAddress}:`, updates);
|
||||||
|
|
||||||
|
this.broadcastToDLE(dleAddress, {
|
||||||
|
type: 'deployment_status',
|
||||||
|
...session
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Добавление лога в сессию деплоя
|
||||||
|
*/
|
||||||
|
addDeploymentLog(dleAddress, logType, message) {
|
||||||
|
const session = this.deploymentSessions.get(dleAddress);
|
||||||
|
if (!session) {
|
||||||
|
console.warn(`[DeploymentWS] Сессия деплоя не найдена для DLE: ${dleAddress}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const logEntry = {
|
||||||
|
type: logType,
|
||||||
|
message: message,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
};
|
||||||
|
|
||||||
|
session.logs.push(logEntry);
|
||||||
|
|
||||||
|
console.log(`[DeploymentWS] Лог деплоя DLE ${dleAddress}:`, logEntry);
|
||||||
|
|
||||||
|
this.broadcastToDLE(dleAddress, {
|
||||||
|
type: 'deployment_log',
|
||||||
|
dleAddress: dleAddress,
|
||||||
|
log: logEntry
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Завершение сессии деплоя
|
||||||
|
*/
|
||||||
|
finishDeploymentSession(dleAddress, success, message = null) {
|
||||||
|
const session = this.deploymentSessions.get(dleAddress);
|
||||||
|
if (!session) {
|
||||||
|
console.warn(`[DeploymentWS] Сессия деплоя не найдена для DLE: ${dleAddress}`);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
session.status = success ? 'completed' : 'failed';
|
||||||
|
session.progress = success ? 100 : session.progress;
|
||||||
|
session.endTime = new Date().toISOString();
|
||||||
|
session.message = message || (success ? 'Деплой завершен успешно' : 'Деплой завершен с ошибкой');
|
||||||
|
|
||||||
|
console.log(`[DeploymentWS] Завершение деплоя DLE ${dleAddress}: ${session.status}`);
|
||||||
|
|
||||||
|
this.broadcastToDLE(dleAddress, {
|
||||||
|
type: 'deployment_finished',
|
||||||
|
...session
|
||||||
|
});
|
||||||
|
|
||||||
|
// Удаляем сессию через 30 секунд
|
||||||
|
setTimeout(() => {
|
||||||
|
this.deploymentSessions.delete(dleAddress);
|
||||||
|
}, 30000);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Отправка сообщения конкретному клиенту
|
||||||
|
*/
|
||||||
|
sendToClient(ws, message) {
|
||||||
|
if (ws && ws.readyState === WebSocket.OPEN) {
|
||||||
|
try {
|
||||||
|
ws.send(JSON.stringify(message));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[DeploymentWS] Ошибка отправки сообщения клиенту:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Отправка сообщения всем клиентам конкретного DLE
|
||||||
|
*/
|
||||||
|
broadcastToDLE(dleAddress, message) {
|
||||||
|
const clients = this.clients.get(dleAddress);
|
||||||
|
if (clients) {
|
||||||
|
clients.forEach(ws => {
|
||||||
|
this.sendToClient(ws, message);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Отправка ошибки клиенту
|
||||||
|
*/
|
||||||
|
sendError(ws, errorMessage) {
|
||||||
|
this.sendToClient(ws, {
|
||||||
|
type: 'error',
|
||||||
|
message: errorMessage
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Уведомление об обновлении модулей
|
||||||
|
*/
|
||||||
|
notifyModulesUpdated(dleAddress) {
|
||||||
|
console.log(`[DeploymentWS] Уведомление об обновлении модулей для DLE: ${dleAddress}`);
|
||||||
|
|
||||||
|
this.broadcastToDLE(dleAddress, {
|
||||||
|
type: 'modules_updated',
|
||||||
|
dleAddress: dleAddress,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Уведомление о верификации модуля
|
||||||
|
*/
|
||||||
|
notifyModuleVerified(dleAddress, moduleType, networkName) {
|
||||||
|
console.log(`[DeploymentWS] Уведомление о верификации модуля ${moduleType} в сети ${networkName} для DLE: ${dleAddress}`);
|
||||||
|
|
||||||
|
this.broadcastToDLE(dleAddress, {
|
||||||
|
type: 'module_verified',
|
||||||
|
dleAddress: dleAddress,
|
||||||
|
moduleType: moduleType,
|
||||||
|
networkName: networkName,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Уведомление об изменении статуса модуля
|
||||||
|
*/
|
||||||
|
notifyModuleStatusChanged(dleAddress, moduleType, status) {
|
||||||
|
console.log(`[DeploymentWS] Уведомление об изменении статуса модуля ${moduleType} на ${status} для DLE: ${dleAddress}`);
|
||||||
|
|
||||||
|
this.broadcastToDLE(dleAddress, {
|
||||||
|
type: 'module_status_changed',
|
||||||
|
dleAddress: dleAddress,
|
||||||
|
moduleType: moduleType,
|
||||||
|
status: status,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получение статистики
|
||||||
|
*/
|
||||||
|
getStats() {
|
||||||
|
const totalClients = Array.from(this.clients.values()).reduce((sum, clients) => sum + clients.size, 0);
|
||||||
|
return {
|
||||||
|
activeConnections: totalClients,
|
||||||
|
activeSessions: this.deploymentSessions.size,
|
||||||
|
subscriptions: this.clients.size
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Создаем единственный экземпляр сервиса
|
||||||
|
const deploymentWebSocketService = new DeploymentWebSocketService();
|
||||||
|
|
||||||
|
module.exports = deploymentWebSocketService;
|
||||||
@@ -13,6 +13,7 @@
|
|||||||
const WebSocket = require('ws');
|
const WebSocket = require('ws');
|
||||||
const tokenBalanceService = require('./services/tokenBalanceService');
|
const tokenBalanceService = require('./services/tokenBalanceService');
|
||||||
const deploymentTracker = require('./utils/deploymentTracker');
|
const deploymentTracker = require('./utils/deploymentTracker');
|
||||||
|
const deploymentWebSocketService = require('./services/deploymentWebSocketService');
|
||||||
|
|
||||||
let wss = null;
|
let wss = null;
|
||||||
// Храним клиентов по userId для персонализированных уведомлений
|
// Храним клиентов по userId для персонализированных уведомлений
|
||||||
@@ -70,6 +71,17 @@ function initWSS(server) {
|
|||||||
// Запрос балансов токенов
|
// Запрос балансов токенов
|
||||||
handleTokenBalancesRequest(ws, data.address, data.userId);
|
handleTokenBalancesRequest(ws, data.address, data.userId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Обработка сообщений для деплоя модулей
|
||||||
|
if (data.type === 'subscribe' && data.dleAddress) {
|
||||||
|
// Подписка на деплой для конкретного DLE
|
||||||
|
deploymentWebSocketService.subscribeToDeployment(ws, data.dleAddress);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data.type === 'unsubscribe' && data.dleAddress) {
|
||||||
|
// Отписка от деплоя
|
||||||
|
deploymentWebSocketService.unsubscribeFromDeployment(ws, data.dleAddress);
|
||||||
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// console.error('❌ [WebSocket] Ошибка парсинга сообщения:', error);
|
// console.error('❌ [WebSocket] Ошибка парсинга сообщения:', error);
|
||||||
}
|
}
|
||||||
@@ -485,6 +497,35 @@ function broadcastDeploymentUpdate(data) {
|
|||||||
console.log(`📡 [WebSocket] Отправлено deployment update: ${data.type || 'unknown'}`);
|
console.log(`📡 [WebSocket] Отправлено deployment update: ${data.type || 'unknown'}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Функция для уведомления об обновлениях модулей
|
||||||
|
function broadcastModulesUpdate(dleAddress, updateType, moduleData) {
|
||||||
|
if (!wss) return;
|
||||||
|
|
||||||
|
console.log(`📡 [WebSocket] broadcastModulesUpdate вызвана для DLE: ${dleAddress}, тип: ${updateType}`);
|
||||||
|
|
||||||
|
const message = JSON.stringify({
|
||||||
|
type: updateType,
|
||||||
|
dleAddress: dleAddress,
|
||||||
|
moduleData: moduleData,
|
||||||
|
timestamp: Date.now()
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`📡 [WebSocket] Отправляем сообщение модулей:`, message);
|
||||||
|
|
||||||
|
// Отправляем всем подключенным клиентам
|
||||||
|
wss.clients.forEach(client => {
|
||||||
|
if (client.readyState === WebSocket.OPEN) {
|
||||||
|
try {
|
||||||
|
client.send(message);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[WebSocket] Ошибка при отправке modules update:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`📡 [WebSocket] Отправлено modules update: ${updateType} для DLE: ${dleAddress}`);
|
||||||
|
}
|
||||||
|
|
||||||
module.exports = {
|
module.exports = {
|
||||||
initWSS,
|
initWSS,
|
||||||
broadcastContactsUpdate,
|
broadcastContactsUpdate,
|
||||||
@@ -504,6 +545,7 @@ module.exports = {
|
|||||||
broadcastTokenBalancesUpdate,
|
broadcastTokenBalancesUpdate,
|
||||||
broadcastTokenBalanceChanged,
|
broadcastTokenBalanceChanged,
|
||||||
broadcastDeploymentUpdate,
|
broadcastDeploymentUpdate,
|
||||||
|
broadcastModulesUpdate,
|
||||||
getConnectedUsers,
|
getConnectedUsers,
|
||||||
getStats
|
getStats
|
||||||
};
|
};
|
||||||
|
|||||||
151
docs/MODULE_OPERATIONS_INTEGRATION.md
Normal file
151
docs/MODULE_OPERATIONS_INTEGRATION.md
Normal file
@@ -0,0 +1,151 @@
|
|||||||
|
# Интеграция динамических операций модулей в CreateProposalView
|
||||||
|
|
||||||
|
## Обзор
|
||||||
|
|
||||||
|
Реализована система автоматического отображения блоков с предложениями операций модулей в реальном времени на странице создания предложений (`/management/create-proposal`). После деплоя модуля карточки с операциями автоматически появляются без необходимости обновления страницы.
|
||||||
|
|
||||||
|
## Архитектура решения
|
||||||
|
|
||||||
|
### 1. Backend API Endpoints
|
||||||
|
|
||||||
|
Добавлены новые API endpoints в `/backend/routes/dleModules.js`:
|
||||||
|
|
||||||
|
- `POST /dle-modules/get-module-operations` - получение всех доступных операций модулей для DLE
|
||||||
|
- `POST /dle-modules/get-module-specific-operations` - получение операций конкретного модуля
|
||||||
|
- `POST /dle-modules/get-module-interface` - получение ABI и интерфейса модуля
|
||||||
|
- `POST /dle-modules/get-module-available-functions` - получение доступных функций модуля
|
||||||
|
- `POST /dle-modules/get-module-function-parameters` - получение параметров функции модуля
|
||||||
|
- `POST /dle-modules/create-module-operation-proposal` - создание предложения для операции модуля
|
||||||
|
- `POST /dle-modules/validate-module-operation` - валидация операции модуля
|
||||||
|
|
||||||
|
### 2. Frontend Services
|
||||||
|
|
||||||
|
Создан новый сервис `/frontend/src/services/moduleOperationsService.js` с функциями:
|
||||||
|
|
||||||
|
- `getModuleOperations(dleAddress)` - получение операций модулей
|
||||||
|
- `getModuleSpecificOperations(dleAddress, moduleType, moduleAddress, chainId)` - операции конкретного модуля
|
||||||
|
- `createModuleOperationProposal(dleAddress, operationData)` - создание предложения
|
||||||
|
- `getModuleInterface(moduleType, moduleAddress, chainId)` - получение интерфейса
|
||||||
|
- `validateModuleOperation(dleAddress, operationData)` - валидация операции
|
||||||
|
|
||||||
|
### 3. WebSocket Integration
|
||||||
|
|
||||||
|
#### Backend (wsHub.js)
|
||||||
|
- Добавлена функция `broadcastModulesUpdate(dleAddress, updateType, moduleData)` для отправки уведомлений об обновлениях модулей
|
||||||
|
- Уведомления отправляются при обнаружении новых модулей в файлах деплоя
|
||||||
|
|
||||||
|
#### Frontend (CreateProposalView.vue)
|
||||||
|
- Подключение к WebSocket при монтировании компонента
|
||||||
|
- Обработка сообщений: `modules_updated`, `module_verified`, `module_status_changed`
|
||||||
|
- Автоматическое обновление списка операций при получении уведомлений
|
||||||
|
|
||||||
|
### 4. Динамическое отображение
|
||||||
|
|
||||||
|
#### Состояние компонента
|
||||||
|
```javascript
|
||||||
|
const moduleOperations = ref([]);
|
||||||
|
const isLoadingModuleOperations = ref(false);
|
||||||
|
const modulesWebSocket = ref(null);
|
||||||
|
const isModulesWSConnected = ref(false);
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Шаблон
|
||||||
|
- Условное отображение индикатора загрузки
|
||||||
|
- Динамическое создание блоков операций для каждого модуля
|
||||||
|
- Анимация появления новых блоков
|
||||||
|
|
||||||
|
## Поддерживаемые типы модулей и операции
|
||||||
|
|
||||||
|
### 1. Treasury Module (💰)
|
||||||
|
- **Депозит средств** - пополнение казначейства DLE
|
||||||
|
- **Вывод средств** - вывод средств из казначейства
|
||||||
|
- **Распределение дивидендов** - распределение дивидендов между держателями токенов
|
||||||
|
|
||||||
|
### 2. Timelock Module (⏰)
|
||||||
|
- **Установить задержку** - установить время задержки для операций
|
||||||
|
- **Поставить операцию в очередь** - добавить операцию в очередь для выполнения
|
||||||
|
|
||||||
|
### 3. Reader Module (📖)
|
||||||
|
- **Обновить данные** - обновить информацию о DLE
|
||||||
|
|
||||||
|
### 4. Hierarchical Voting Module (🗳️)
|
||||||
|
- **Голосование во внешнем DLE** - использовать токены для голосования в другом DLE
|
||||||
|
|
||||||
|
## Реальный процесс работы
|
||||||
|
|
||||||
|
1. **Деплой модуля** → Backend сохраняет информацию в файлы деплоя
|
||||||
|
2. **Обнаружение модуля** → `getDeployedModulesInfo()` читает файлы и находит новые модули
|
||||||
|
3. **WebSocket уведомление** → `broadcastModulesUpdate()` отправляет уведомление клиентам
|
||||||
|
4. **Обновление UI** → Frontend получает уведомление и автоматически загружает новые операции
|
||||||
|
5. **Отображение блоков** → Новые блоки операций появляются с анимацией
|
||||||
|
|
||||||
|
## Стили и UX
|
||||||
|
|
||||||
|
### Визуальные особенности
|
||||||
|
- Отдельные стили для блоков операций модулей (`.module-operation-block`)
|
||||||
|
- Цветовая схема с зеленым акцентом для модулей
|
||||||
|
- Теги категорий операций
|
||||||
|
- Анимация появления (`fadeInUp`)
|
||||||
|
- Индикатор загрузки с анимацией
|
||||||
|
|
||||||
|
### Адаптивность
|
||||||
|
- Responsive grid для блоков операций
|
||||||
|
- Поддержка мобильных устройств
|
||||||
|
- Адаптивные размеры и отступы
|
||||||
|
|
||||||
|
## Технические детали
|
||||||
|
|
||||||
|
### WebSocket Events
|
||||||
|
```javascript
|
||||||
|
// Подписка на обновления
|
||||||
|
{
|
||||||
|
type: 'subscribe',
|
||||||
|
dleAddress: '0x...'
|
||||||
|
}
|
||||||
|
|
||||||
|
// Уведомления
|
||||||
|
{
|
||||||
|
type: 'modules_updated',
|
||||||
|
dleAddress: '0x...',
|
||||||
|
moduleData: { modulesCount: 3, moduleTypes: ['treasury', 'timelock'] },
|
||||||
|
timestamp: 1234567890
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Структура данных операций
|
||||||
|
```javascript
|
||||||
|
{
|
||||||
|
id: 'deposit',
|
||||||
|
name: 'Депозит средств',
|
||||||
|
description: 'Пополнение казначейства DLE',
|
||||||
|
icon: '💰',
|
||||||
|
functionName: 'deposit',
|
||||||
|
parameters: [
|
||||||
|
{ name: 'amount', type: 'uint256', label: 'Сумма', required: true }
|
||||||
|
],
|
||||||
|
category: 'Финансы'
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Будущие улучшения
|
||||||
|
|
||||||
|
1. **Полные формы создания предложений** - замена alert на полноценные модальные окна
|
||||||
|
2. **Валидация параметров** - клиентская валидация перед отправкой
|
||||||
|
3. **Предпросмотр операций** - отображение calldata перед созданием предложения
|
||||||
|
4. **История операций** - показ выполненных операций модулей
|
||||||
|
5. **Расширенная фильтрация** - фильтры по типам операций и модулей
|
||||||
|
|
||||||
|
## Файлы изменений
|
||||||
|
|
||||||
|
### Новые файлы
|
||||||
|
- `frontend/src/services/moduleOperationsService.js`
|
||||||
|
- `docs/MODULE_OPERATIONS_INTEGRATION.md`
|
||||||
|
|
||||||
|
### Измененные файлы
|
||||||
|
- `frontend/src/views/smartcontracts/CreateProposalView.vue`
|
||||||
|
- `backend/routes/dleModules.js`
|
||||||
|
- `backend/wsHub.js`
|
||||||
|
|
||||||
|
## Заключение
|
||||||
|
|
||||||
|
Реализована полноценная система динамического отображения операций модулей с реальным временем обновлений через WebSocket. После деплоя модуля пользователи сразу видят доступные операции без необходимости обновления страницы, что значительно улучшает пользовательский опыт.
|
||||||
@@ -242,56 +242,6 @@ const routes = [
|
|||||||
name: 'management-modules',
|
name: 'management-modules',
|
||||||
component: () => import('../views/smartcontracts/ModulesView.vue')
|
component: () => import('../views/smartcontracts/ModulesView.vue')
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: '/management/modules/deploy/treasury',
|
|
||||||
name: 'module-deploy-treasury',
|
|
||||||
component: () => import('../views/smartcontracts/modules/TreasuryModuleDeployView.vue')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/management/modules/deploy/timelock',
|
|
||||||
name: 'module-deploy-timelock',
|
|
||||||
component: () => import('../views/smartcontracts/modules/TimelockModuleDeployView.vue')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/management/modules/deploy/reader',
|
|
||||||
name: 'module-deploy-reader',
|
|
||||||
component: () => import('../views/smartcontracts/modules/DLEReaderDeployView.vue')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/management/modules/deploy/communication',
|
|
||||||
name: 'module-deploy-communication',
|
|
||||||
component: () => import('../views/smartcontracts/modules/CommunicationModuleDeployView.vue')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/management/modules/deploy/application',
|
|
||||||
name: 'module-deploy-application',
|
|
||||||
component: () => import('../views/smartcontracts/modules/ApplicationModuleDeployView.vue')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/management/modules/deploy/mint',
|
|
||||||
name: 'module-deploy-mint',
|
|
||||||
component: () => import('../views/smartcontracts/modules/MintModuleDeploy.vue')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/management/modules/deploy/burn',
|
|
||||||
name: 'module-deploy-burn',
|
|
||||||
component: () => import('../views/smartcontracts/modules/BurnModuleDeploy.vue')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/management/modules/deploy/oracle',
|
|
||||||
name: 'module-deploy-oracle',
|
|
||||||
component: () => import('../views/smartcontracts/modules/OracleModuleDeploy.vue')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/management/modules/deploy/custom',
|
|
||||||
name: 'module-deploy-custom',
|
|
||||||
component: () => import('../views/smartcontracts/modules/ModuleDeployFormView.vue')
|
|
||||||
},
|
|
||||||
{
|
|
||||||
path: '/management/modules/deploy/inheritance',
|
|
||||||
name: 'module-deploy-inheritance',
|
|
||||||
component: () => import('../views/smartcontracts/modules/InheritanceModuleDeploy.vue')
|
|
||||||
},
|
|
||||||
// {
|
// {
|
||||||
// path: '/management/multisig',
|
// path: '/management/multisig',
|
||||||
// name: 'management-multisig',
|
// name: 'management-multisig',
|
||||||
|
|||||||
195
frontend/src/services/moduleOperationsService.js
Normal file
195
frontend/src/services/moduleOperationsService.js
Normal file
@@ -0,0 +1,195 @@
|
|||||||
|
/**
|
||||||
|
* Copyright (c) 2024-2025 Тарабанов Александр Викторович
|
||||||
|
* All rights reserved.
|
||||||
|
*
|
||||||
|
* This software is proprietary and confidential.
|
||||||
|
* Unauthorized copying, modification, or distribution is prohibited.
|
||||||
|
*
|
||||||
|
* For licensing inquiries: info@hb3-accelerator.com
|
||||||
|
* Website: https://hb3-accelerator.com
|
||||||
|
* GitHub: https://github.com/HB3-ACCELERATOR
|
||||||
|
*/
|
||||||
|
|
||||||
|
// Сервис для работы с операциями модулей DLE
|
||||||
|
import api from '@/api/axios';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получает доступные операции для модулей DLE
|
||||||
|
* @param {string} dleAddress - Адрес DLE
|
||||||
|
* @returns {Promise<Object>} - Доступные операции
|
||||||
|
*/
|
||||||
|
export const getModuleOperations = async (dleAddress) => {
|
||||||
|
try {
|
||||||
|
const response = await api.post('/dle-modules/get-module-operations', {
|
||||||
|
dleAddress
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка при получении операций модулей:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получает операции для конкретного модуля
|
||||||
|
* @param {string} dleAddress - Адрес DLE
|
||||||
|
* @param {string} moduleType - Тип модуля
|
||||||
|
* @param {string} moduleAddress - Адрес модуля
|
||||||
|
* @param {number} chainId - ID сети
|
||||||
|
* @returns {Promise<Object>} - Операции модуля
|
||||||
|
*/
|
||||||
|
export const getModuleSpecificOperations = async (dleAddress, moduleType, moduleAddress, chainId) => {
|
||||||
|
try {
|
||||||
|
const response = await api.post('/dle-modules/get-module-specific-operations', {
|
||||||
|
dleAddress,
|
||||||
|
moduleType,
|
||||||
|
moduleAddress,
|
||||||
|
chainId
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка при получении операций конкретного модуля:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Создает предложение для выполнения операции модуля
|
||||||
|
* @param {string} dleAddress - Адрес DLE
|
||||||
|
* @param {Object} operationData - Данные операции
|
||||||
|
* @returns {Promise<Object>} - Результат создания предложения
|
||||||
|
*/
|
||||||
|
export const createModuleOperationProposal = async (dleAddress, operationData) => {
|
||||||
|
try {
|
||||||
|
const response = await api.post('/dle-modules/create-module-operation-proposal', {
|
||||||
|
dleAddress,
|
||||||
|
...operationData
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка при создании предложения для операции модуля:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получает ABI и интерфейс модуля
|
||||||
|
* @param {string} moduleType - Тип модуля
|
||||||
|
* @param {string} moduleAddress - Адрес модуля
|
||||||
|
* @param {number} chainId - ID сети
|
||||||
|
* @returns {Promise<Object>} - ABI и интерфейс модуля
|
||||||
|
*/
|
||||||
|
export const getModuleInterface = async (moduleType, moduleAddress, chainId) => {
|
||||||
|
try {
|
||||||
|
const response = await api.post('/dle-modules/get-module-interface', {
|
||||||
|
moduleType,
|
||||||
|
moduleAddress,
|
||||||
|
chainId
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка при получении интерфейса модуля:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получает доступные функции модуля для создания предложений
|
||||||
|
* @param {string} dleAddress - Адрес DLE
|
||||||
|
* @param {string} moduleType - Тип модуля
|
||||||
|
* @param {string} moduleAddress - Адрес модуля
|
||||||
|
* @param {number} chainId - ID сети
|
||||||
|
* @returns {Promise<Object>} - Доступные функции
|
||||||
|
*/
|
||||||
|
export const getModuleAvailableFunctions = async (dleAddress, moduleType, moduleAddress, chainId) => {
|
||||||
|
try {
|
||||||
|
const response = await api.post('/dle-modules/get-module-available-functions', {
|
||||||
|
dleAddress,
|
||||||
|
moduleType,
|
||||||
|
moduleAddress,
|
||||||
|
chainId
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка при получении доступных функций модуля:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получает параметры функции модуля
|
||||||
|
* @param {string} moduleType - Тип модуля
|
||||||
|
* @param {string} functionName - Имя функции
|
||||||
|
* @returns {Promise<Object>} - Параметры функции
|
||||||
|
*/
|
||||||
|
export const getModuleFunctionParameters = async (moduleType, functionName) => {
|
||||||
|
try {
|
||||||
|
const response = await api.post('/dle-modules/get-module-function-parameters', {
|
||||||
|
moduleType,
|
||||||
|
functionName
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка при получении параметров функции модуля:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Валидирует параметры операции модуля
|
||||||
|
* @param {string} dleAddress - Адрес DLE
|
||||||
|
* @param {Object} operationData - Данные операции
|
||||||
|
* @returns {Promise<Object>} - Результат валидации
|
||||||
|
*/
|
||||||
|
export const validateModuleOperation = async (dleAddress, operationData) => {
|
||||||
|
try {
|
||||||
|
const response = await api.post('/dle-modules/validate-module-operation', {
|
||||||
|
dleAddress,
|
||||||
|
...operationData
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка при валидации операции модуля:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получает историю операций модуля
|
||||||
|
* @param {string} dleAddress - Адрес DLE
|
||||||
|
* @param {string} moduleType - Тип модуля
|
||||||
|
* @param {Object} filters - Фильтры
|
||||||
|
* @returns {Promise<Object>} - История операций
|
||||||
|
*/
|
||||||
|
export const getModuleOperationsHistory = async (dleAddress, moduleType, filters = {}) => {
|
||||||
|
try {
|
||||||
|
const response = await api.post('/dle-modules/get-module-operations-history', {
|
||||||
|
dleAddress,
|
||||||
|
moduleType,
|
||||||
|
...filters
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка при получении истории операций модуля:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Получает статус выполнения операции модуля
|
||||||
|
* @param {string} dleAddress - Адрес DLE
|
||||||
|
* @param {string} operationId - ID операции
|
||||||
|
* @returns {Promise<Object>} - Статус операции
|
||||||
|
*/
|
||||||
|
export const getModuleOperationStatus = async (dleAddress, operationId) => {
|
||||||
|
try {
|
||||||
|
const response = await api.post('/dle-modules/get-module-operation-status', {
|
||||||
|
dleAddress,
|
||||||
|
operationId
|
||||||
|
});
|
||||||
|
return response.data;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Ошибка при получении статуса операции модуля:', error);
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -148,6 +148,40 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Операции модулей (динамические) -->
|
||||||
|
<div v-if="isLoadingModuleOperations" class="loading-modules">
|
||||||
|
Загрузка операций модулей...
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div
|
||||||
|
v-for="moduleOperation in moduleOperations"
|
||||||
|
:key="moduleOperation.moduleType"
|
||||||
|
class="operation-category"
|
||||||
|
>
|
||||||
|
<h5>{{ getModuleIcon(moduleOperation.moduleType) }} {{ moduleOperation.moduleName }}</h5>
|
||||||
|
<p class="module-description">{{ moduleOperation.moduleDescription }}</p>
|
||||||
|
<div class="operation-blocks">
|
||||||
|
<div
|
||||||
|
v-for="operation in moduleOperation.operations"
|
||||||
|
:key="operation.id"
|
||||||
|
class="operation-block module-operation-block"
|
||||||
|
>
|
||||||
|
<div class="operation-icon">{{ operation.icon }}</div>
|
||||||
|
<h6>{{ operation.name }}</h6>
|
||||||
|
<p>{{ operation.description }}</p>
|
||||||
|
<div class="operation-category-tag">{{ operation.category }}</div>
|
||||||
|
<button
|
||||||
|
class="create-btn"
|
||||||
|
@click="openModuleOperationForm(moduleOperation.moduleType, operation)"
|
||||||
|
:disabled="!props.isAuthenticated || isLoadingModuleOperations"
|
||||||
|
>
|
||||||
|
<span v-if="isLoadingModuleOperations">Загрузка...</span>
|
||||||
|
<span v-else>Создать</span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Оффчейн операции -->
|
<!-- Оффчейн операции -->
|
||||||
<div class="operation-category">
|
<div class="operation-category">
|
||||||
<h5>📋 Оффчейн операции</h5>
|
<h5>📋 Оффчейн операции</h5>
|
||||||
@@ -169,12 +203,13 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { ref, computed, onMounted, defineProps, defineEmits, inject } from 'vue';
|
import { ref, computed, onMounted, onUnmounted, defineProps, defineEmits, inject } from 'vue';
|
||||||
import { useRouter, useRoute } from 'vue-router';
|
import { useRouter, useRoute } from 'vue-router';
|
||||||
import { useAuthContext } from '../../composables/useAuth';
|
import { useAuthContext } from '../../composables/useAuth';
|
||||||
import BaseLayout from '../../components/BaseLayout.vue';
|
import BaseLayout from '../../components/BaseLayout.vue';
|
||||||
import { getDLEInfo, getSupportedChains } from '../../services/dleV2Service.js';
|
import { getDLEInfo, getSupportedChains } from '../../services/dleV2Service.js';
|
||||||
import { createProposal as createProposalAPI } from '../../services/proposalsService.js';
|
import { createProposal as createProposalAPI } from '../../services/proposalsService.js';
|
||||||
|
import { getModuleOperations } from '../../services/moduleOperationsService.js';
|
||||||
import api from '../../api/axios';
|
import api from '../../api/axios';
|
||||||
import wsClient from '../../utils/websocket.js';
|
import wsClient from '../../utils/websocket.js';
|
||||||
import { ethers } from 'ethers';
|
import { ethers } from 'ethers';
|
||||||
@@ -221,6 +256,12 @@ const isLoadingDle = ref(false);
|
|||||||
// Доступные цепочки (загружаются из конфигурации)
|
// Доступные цепочки (загружаются из конфигурации)
|
||||||
const availableChains = ref([]);
|
const availableChains = ref([]);
|
||||||
|
|
||||||
|
// Состояние модулей и их операций
|
||||||
|
const moduleOperations = ref([]);
|
||||||
|
const isLoadingModuleOperations = ref(false);
|
||||||
|
const modulesWebSocket = ref(null);
|
||||||
|
const isModulesWSConnected = ref(false);
|
||||||
|
|
||||||
// Функции для открытия отдельных форм операций
|
// Функции для открытия отдельных форм операций
|
||||||
function openTransferForm() {
|
function openTransferForm() {
|
||||||
// TODO: Открыть форму для передачи токенов
|
// TODO: Открыть форму для передачи токенов
|
||||||
@@ -273,6 +314,26 @@ function openOffchainActionForm() {
|
|||||||
alert('Форма оффчейн действий будет реализована');
|
alert('Форма оффчейн действий будет реализована');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Функция для создания предложения операции модуля
|
||||||
|
function openModuleOperationForm(moduleType, operation) {
|
||||||
|
console.log('[CreateProposalView] Открытие формы для операции модуля:', { moduleType, operation });
|
||||||
|
|
||||||
|
// TODO: Открыть форму для создания предложения операции модуля
|
||||||
|
// Пока показываем alert с информацией об операции
|
||||||
|
alert(`Создание предложения для операции "${operation.name}" модуля ${moduleType}.\n\nОписание: ${operation.description}\nФункция: ${operation.functionName}\nКатегория: ${operation.category}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Получить иконку для типа модуля
|
||||||
|
function getModuleIcon(moduleType) {
|
||||||
|
const icons = {
|
||||||
|
treasury: '💰',
|
||||||
|
timelock: '⏰',
|
||||||
|
reader: '📖',
|
||||||
|
hierarchicalVoting: '🗳️'
|
||||||
|
};
|
||||||
|
return icons[moduleType] || '🔧';
|
||||||
|
}
|
||||||
|
|
||||||
// Функции
|
// Функции
|
||||||
async function loadDleData() {
|
async function loadDleData() {
|
||||||
console.log('loadDleData вызвана с адресом:', dleAddress.value);
|
console.log('loadDleData вызвана с адресом:', dleAddress.value);
|
||||||
@@ -300,6 +361,9 @@ async function loadDleData() {
|
|||||||
const chainsResponse = await getSupportedChains(dleAddress.value);
|
const chainsResponse = await getSupportedChains(dleAddress.value);
|
||||||
availableChains.value = chainsResponse.data?.chains || [];
|
availableChains.value = chainsResponse.data?.chains || [];
|
||||||
|
|
||||||
|
// Загружаем операции модулей
|
||||||
|
await loadModuleOperations();
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Ошибка загрузки данных DLE из блокчейна:', error);
|
console.error('Ошибка загрузки данных DLE из блокчейна:', error);
|
||||||
} finally {
|
} finally {
|
||||||
@@ -307,6 +371,113 @@ async function loadDleData() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Загрузка операций модулей
|
||||||
|
async function loadModuleOperations() {
|
||||||
|
if (!dleAddress.value) {
|
||||||
|
console.warn('Адрес DLE не указан для загрузки операций модулей');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
isLoadingModuleOperations.value = true;
|
||||||
|
try {
|
||||||
|
console.log('[CreateProposalView] Загрузка операций модулей для DLE:', dleAddress.value);
|
||||||
|
|
||||||
|
const response = await getModuleOperations(dleAddress.value);
|
||||||
|
|
||||||
|
if (response.success) {
|
||||||
|
moduleOperations.value = response.data.moduleOperations || [];
|
||||||
|
console.log('[CreateProposalView] Загружены операции модулей:', moduleOperations.value);
|
||||||
|
} else {
|
||||||
|
console.error('[CreateProposalView] Ошибка загрузки операций модулей:', response.error);
|
||||||
|
moduleOperations.value = [];
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[CreateProposalView] Ошибка загрузки операций модулей:', error);
|
||||||
|
moduleOperations.value = [];
|
||||||
|
} finally {
|
||||||
|
isLoadingModuleOperations.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// WebSocket функции для модулей
|
||||||
|
function connectModulesWebSocket() {
|
||||||
|
if (modulesWebSocket.value && modulesWebSocket.value.readyState === WebSocket.OPEN) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const wsUrl = `ws://localhost:8000/ws/deployment`;
|
||||||
|
modulesWebSocket.value = new WebSocket(wsUrl);
|
||||||
|
|
||||||
|
modulesWebSocket.value.onopen = () => {
|
||||||
|
console.log('[CreateProposalView] WebSocket модулей соединение установлено');
|
||||||
|
isModulesWSConnected.value = true;
|
||||||
|
|
||||||
|
// Подписываемся на обновления модулей для текущего DLE
|
||||||
|
if (dleAddress.value) {
|
||||||
|
modulesWebSocket.value.send(JSON.stringify({
|
||||||
|
type: 'subscribe',
|
||||||
|
dleAddress: dleAddress.value
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
modulesWebSocket.value.onmessage = (event) => {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(event.data);
|
||||||
|
handleModulesWebSocketMessage(data);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('[CreateProposalView] Ошибка парсинга WebSocket сообщения модулей:', error);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
modulesWebSocket.value.onclose = () => {
|
||||||
|
console.log('[CreateProposalView] WebSocket модулей соединение закрыто');
|
||||||
|
isModulesWSConnected.value = false;
|
||||||
|
|
||||||
|
// Переподключаемся через 5 секунд
|
||||||
|
setTimeout(() => {
|
||||||
|
connectModulesWebSocket();
|
||||||
|
}, 5000);
|
||||||
|
};
|
||||||
|
|
||||||
|
modulesWebSocket.value.onerror = (error) => {
|
||||||
|
console.error('[CreateProposalView] Ошибка WebSocket модулей:', error);
|
||||||
|
isModulesWSConnected.value = false;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
function handleModulesWebSocketMessage(data) {
|
||||||
|
console.log('[CreateProposalView] WebSocket модулей сообщение:', data);
|
||||||
|
|
||||||
|
switch (data.type) {
|
||||||
|
case 'modules_updated':
|
||||||
|
// Автоматически обновляем список операций модулей
|
||||||
|
console.log('[CreateProposalView] Получено уведомление об обновлении модулей');
|
||||||
|
loadModuleOperations();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'module_verified':
|
||||||
|
// Обновляем операции модуля
|
||||||
|
console.log(`[CreateProposalView] Модуль ${data.moduleType} верифицирован`);
|
||||||
|
loadModuleOperations();
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'module_status_changed':
|
||||||
|
// Обновляем операции модуля
|
||||||
|
console.log(`[CreateProposalView] Статус модуля ${data.moduleType} изменен`);
|
||||||
|
loadModuleOperations();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function disconnectModulesWebSocket() {
|
||||||
|
if (modulesWebSocket.value) {
|
||||||
|
modulesWebSocket.value.close();
|
||||||
|
modulesWebSocket.value = null;
|
||||||
|
isModulesWSConnected.value = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
// Принудительно загружаем токены, если пользователь аутентифицирован
|
// Принудительно загружаем токены, если пользователь аутентифицирован
|
||||||
if (isAuthenticated.value && address.value) {
|
if (isAuthenticated.value && address.value) {
|
||||||
@@ -318,6 +489,14 @@ onMounted(async () => {
|
|||||||
if (dleAddress.value) {
|
if (dleAddress.value) {
|
||||||
loadDleData();
|
loadDleData();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Подключаемся к WebSocket для получения обновлений модулей
|
||||||
|
connectModulesWebSocket();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Отключаем WebSocket при размонтировании компонента
|
||||||
|
onUnmounted(() => {
|
||||||
|
disconnectModulesWebSocket();
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -562,6 +741,91 @@ onMounted(async () => {
|
|||||||
display: none;
|
display: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Стили для модулей */
|
||||||
|
.module-description {
|
||||||
|
color: #666;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
margin: 0.5rem 0 1rem 0;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-operation-block {
|
||||||
|
position: relative;
|
||||||
|
background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
|
||||||
|
border: 2px solid #e9ecef;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-operation-block::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
height: 4px;
|
||||||
|
background: linear-gradient(90deg, #28a745, #20c997);
|
||||||
|
transform: scaleX(0);
|
||||||
|
transition: transform 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.module-operation-block:hover::before {
|
||||||
|
transform: scaleX(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.operation-category-tag {
|
||||||
|
display: inline-block;
|
||||||
|
background: linear-gradient(135deg, #28a745, #20c997);
|
||||||
|
color: white;
|
||||||
|
padding: 0.25rem 0.75rem;
|
||||||
|
border-radius: 20px;
|
||||||
|
font-size: 0.75rem;
|
||||||
|
font-weight: 600;
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.5px;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Анимация появления модулей */
|
||||||
|
.operation-category {
|
||||||
|
animation: fadeInUp 0.6s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeInUp {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(30px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Индикатор загрузки модулей */
|
||||||
|
.loading-modules {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
padding: 2rem;
|
||||||
|
color: #666;
|
||||||
|
font-style: italic;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading-modules::before {
|
||||||
|
content: '';
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border: 2px solid #f3f3f3;
|
||||||
|
border-top: 2px solid var(--color-primary);
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
margin-right: 0.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
/* Адаптивность */
|
/* Адаптивность */
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
.operations-blocks {
|
.operations-blocks {
|
||||||
|
|||||||
File diff suppressed because it is too large
Load Diff
@@ -1,329 +0,0 @@
|
|||||||
<!--
|
|
||||||
Copyright (c) 2024-2025 Тарабанов Александр Викторович
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
This software is proprietary and confidential.
|
|
||||||
Unauthorized copying, modification, or distribution is prohibited.
|
|
||||||
|
|
||||||
For licensing inquiries: info@hb3-accelerator.com
|
|
||||||
Website: https://hb3-accelerator.com
|
|
||||||
GitHub: https://github.com/HB3-ACCELERATOR
|
|
||||||
-->
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<BaseLayout
|
|
||||||
:is-authenticated="isAuthenticated"
|
|
||||||
:identities="identities"
|
|
||||||
:token-balances="tokenBalances"
|
|
||||||
:is-loading-tokens="isLoadingTokens"
|
|
||||||
@auth-action-completed="$emit('auth-action-completed')"
|
|
||||||
>
|
|
||||||
<div class="application-module-deploy">
|
|
||||||
<!-- Заголовок -->
|
|
||||||
<div class="page-header">
|
|
||||||
<div class="header-content">
|
|
||||||
<h1>Деплой ApplicationModule</h1>
|
|
||||||
<p>Управление вызовом функций приложения через предложения и голосование</p>
|
|
||||||
<p v-if="dleAddress" class="dle-address">
|
|
||||||
<strong>DLE:</strong> {{ dleAddress }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<button class="close-btn" @click="router.push('/management/modules')">×</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Информация о модуле -->
|
|
||||||
<div class="module-info">
|
|
||||||
<div class="info-card">
|
|
||||||
<h3>🖥️ ApplicationModule</h3>
|
|
||||||
<div class="info-grid">
|
|
||||||
<div class="info-item">
|
|
||||||
<strong>Назначение:</strong> Управление функциями приложения через DLE
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<strong>Функции:</strong> Создание предложений для вызова API, голосование за операции
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<strong>Безопасность:</strong> Все операции приложения через кворум токен-холдеров
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<strong>Примеры:</strong> Удаление пользователей, изменение настроек, обновление данных
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Детальное описание -->
|
|
||||||
<div class="module-details">
|
|
||||||
<div class="details-card">
|
|
||||||
<h3>📋 Как работает ApplicationModule</h3>
|
|
||||||
<div class="details-content">
|
|
||||||
<div class="detail-step">
|
|
||||||
<div class="step-number">1</div>
|
|
||||||
<div class="step-content">
|
|
||||||
<h4>Создание предложения</h4>
|
|
||||||
<p>Токен-холдер создает предложение для выполнения операции в приложении (например, удаление пользователя, изменение настроек)</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="detail-step">
|
|
||||||
<div class="step-number">2</div>
|
|
||||||
<div class="step-content">
|
|
||||||
<h4>Голосование</h4>
|
|
||||||
<p>Все токен-холдеры голосуют за или против предложения в течение установленного времени</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="detail-step">
|
|
||||||
<div class="step-number">3</div>
|
|
||||||
<div class="step-content">
|
|
||||||
<h4>Исполнение</h4>
|
|
||||||
<p>При достижении кворума предложение исполняется - вызывается соответствующая функция приложения</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="detail-step">
|
|
||||||
<div class="step-number">4</div>
|
|
||||||
<div class="step-content">
|
|
||||||
<h4>Аудит</h4>
|
|
||||||
<p>Все операции логируются в блокчейне для полной прозрачности и подотчетности</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Форма деплоя будет добавлена позже -->
|
|
||||||
<div class="deploy-form-placeholder">
|
|
||||||
<div class="placeholder-content">
|
|
||||||
<h3>🚧 Форма деплоя в разработке</h3>
|
|
||||||
<p>Здесь будет форма для деплоя ApplicationModule</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</BaseLayout>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { defineProps, defineEmits, ref, onMounted } from 'vue';
|
|
||||||
import { useRouter, useRoute } from 'vue-router';
|
|
||||||
import BaseLayout from '../../../components/BaseLayout.vue';
|
|
||||||
|
|
||||||
// Определяем props
|
|
||||||
const props = defineProps({
|
|
||||||
isAuthenticated: { type: Boolean, default: false },
|
|
||||||
identities: { type: Array, default: () => [] },
|
|
||||||
tokenBalances: { type: Object, default: () => ({}) },
|
|
||||||
isLoadingTokens: { type: Boolean, default: false }
|
|
||||||
});
|
|
||||||
|
|
||||||
// Определяем emits
|
|
||||||
const emit = defineEmits(['auth-action-completed']);
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
const route = useRoute();
|
|
||||||
|
|
||||||
// Состояние
|
|
||||||
const isLoading = ref(false);
|
|
||||||
const dleAddress = ref(route.query.address || null);
|
|
||||||
|
|
||||||
// Инициализация
|
|
||||||
onMounted(() => {
|
|
||||||
console.log('[ApplicationModuleDeployView] Страница загружена');
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.application-module-deploy {
|
|
||||||
padding: 20px;
|
|
||||||
background-color: var(--color-white);
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
|
||||||
margin-top: 20px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
padding-bottom: 20px;
|
|
||||||
border-bottom: 2px solid #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header h1 {
|
|
||||||
color: var(--color-primary);
|
|
||||||
font-size: 2rem;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header p {
|
|
||||||
margin: 10px 0 0 0;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dle-address {
|
|
||||||
margin-top: 10px !important;
|
|
||||||
font-family: monospace;
|
|
||||||
background: #f8f9fa;
|
|
||||||
padding: 8px 12px;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
border: 1px solid #dee2e6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-btn {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
cursor: pointer;
|
|
||||||
color: #666;
|
|
||||||
padding: 0;
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 50%;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-btn:hover {
|
|
||||||
background: #f0f0f0;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Информация о модуле */
|
|
||||||
.module-info {
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-card {
|
|
||||||
background: #f8f9fa;
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
padding: 20px;
|
|
||||||
border: 1px solid #e9ecef;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-card h3 {
|
|
||||||
margin: 0 0 15px 0;
|
|
||||||
color: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
||||||
gap: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-item {
|
|
||||||
padding: 10px;
|
|
||||||
background: white;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
border: 1px solid #dee2e6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-item strong {
|
|
||||||
color: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Детальное описание */
|
|
||||||
.module-details {
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.details-card {
|
|
||||||
background: #f8f9fa;
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
padding: 20px;
|
|
||||||
border: 1px solid #e9ecef;
|
|
||||||
}
|
|
||||||
|
|
||||||
.details-card h3 {
|
|
||||||
margin: 0 0 20px 0;
|
|
||||||
color: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.details-content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-step {
|
|
||||||
display: flex;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 15px;
|
|
||||||
padding: 15px;
|
|
||||||
background: white;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
border: 1px solid #dee2e6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.step-number {
|
|
||||||
background: var(--color-primary);
|
|
||||||
color: white;
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
border-radius: 50%;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-weight: bold;
|
|
||||||
font-size: 14px;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.step-content h4 {
|
|
||||||
margin: 0 0 8px 0;
|
|
||||||
color: var(--color-primary);
|
|
||||||
font-size: 16px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.step-content p {
|
|
||||||
margin: 0;
|
|
||||||
color: #666;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Плейсхолдер для формы */
|
|
||||||
.deploy-form-placeholder {
|
|
||||||
background: #f8f9fa;
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
padding: 40px;
|
|
||||||
text-align: center;
|
|
||||||
border: 2px dashed #dee2e6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder-content h3 {
|
|
||||||
color: var(--color-primary);
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder-content p {
|
|
||||||
color: #666;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Адаптивность */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.info-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.detail-step {
|
|
||||||
flex-direction: column;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.step-number {
|
|
||||||
align-self: center;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,515 +0,0 @@
|
|||||||
<!--
|
|
||||||
Copyright (c) 2024-2025 Тарабанов Александр Викторович
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
This software is proprietary and confidential.
|
|
||||||
Unauthorized copying, modification, or distribution is prohibited.
|
|
||||||
|
|
||||||
For licensing inquiries: info@hb3-accelerator.com
|
|
||||||
Website: https://hb3-accelerator.com
|
|
||||||
GitHub: https://github.com/HB3-ACCELERATOR
|
|
||||||
-->
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<BaseLayout
|
|
||||||
:is-authenticated="isAuthenticated"
|
|
||||||
:identities="identities"
|
|
||||||
:token-balances="tokenBalances"
|
|
||||||
:is-loading-tokens="isLoadingTokens"
|
|
||||||
@auth-action-completed="$emit('auth-action-completed')"
|
|
||||||
>
|
|
||||||
<div class="module-deploy-page">
|
|
||||||
<!-- Заголовок -->
|
|
||||||
<div class="page-header">
|
|
||||||
<div class="header-content">
|
|
||||||
<h1>🔥 Деплой BurnModule</h1>
|
|
||||||
<p>Модуль для сжигания токенов DLE через governance</p>
|
|
||||||
<div v-if="selectedDle" class="dle-info">
|
|
||||||
<span class="dle-name">{{ selectedDle.name }} ({{ selectedDle.symbol }})</span>
|
|
||||||
<span class="dle-address">{{ selectedDle.dleAddress }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button class="close-btn" @click="router.push(`/management/modules?address=${route.query.address}`)">×</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Описание модуля -->
|
|
||||||
<div class="module-description">
|
|
||||||
<div class="description-card">
|
|
||||||
<h3>📋 Описание BurnModule</h3>
|
|
||||||
<div class="description-content">
|
|
||||||
<p><strong>BurnModule</strong> - это модуль для управления сжиганием токенов DLE через систему governance.</p>
|
|
||||||
|
|
||||||
<h4>🔧 Функциональность:</h4>
|
|
||||||
<ul>
|
|
||||||
<li><strong>Сжигание токенов:</strong> Уменьшение общего предложения токенов DLE</li>
|
|
||||||
<li><strong>Governance:</strong> Все операции требуют голосования и кворума</li>
|
|
||||||
<li><strong>Безопасность:</strong> Контролируемое сжигание через коллективные решения</li>
|
|
||||||
<li><strong>Прозрачность:</strong> Все операции записываются в блокчейн</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h4>⚠️ Важные особенности:</h4>
|
|
||||||
<ul>
|
|
||||||
<li>Сжигание токенов возможно только через предложения и голосование</li>
|
|
||||||
<li>Можно сжигать токены из казны DLE или от имени участников</li>
|
|
||||||
<li>Все операции требуют достижения кворума</li>
|
|
||||||
<li>История всех сжиганий сохраняется в блокчейне</li>
|
|
||||||
<li>Сжигание необратимо - токены уничтожаются навсегда</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Форма деплоя -->
|
|
||||||
<div class="deploy-form-section">
|
|
||||||
<div class="form-header">
|
|
||||||
<h3>⚙️ Настройки деплоя</h3>
|
|
||||||
<p>Настройте параметры для деплоя BurnModule</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form @submit.prevent="deployModule" class="deploy-form">
|
|
||||||
<div class="form-row">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="moduleName">Название модуля:</label>
|
|
||||||
<input
|
|
||||||
id="moduleName"
|
|
||||||
v-model="deployData.moduleName"
|
|
||||||
type="text"
|
|
||||||
placeholder="BurnModule"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="moduleVersion">Версия модуля:</label>
|
|
||||||
<input
|
|
||||||
id="moduleVersion"
|
|
||||||
v-model="deployData.moduleVersion"
|
|
||||||
type="text"
|
|
||||||
placeholder="1.0.0"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="moduleDescription">Описание модуля:</label>
|
|
||||||
<textarea
|
|
||||||
id="moduleDescription"
|
|
||||||
v-model="deployData.moduleDescription"
|
|
||||||
placeholder="Модуль для сжигания токенов DLE через governance..."
|
|
||||||
rows="3"
|
|
||||||
required
|
|
||||||
></textarea>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="maxBurnPerProposal">Максимальное сжигание за одно предложение:</label>
|
|
||||||
<input
|
|
||||||
id="maxBurnPerProposal"
|
|
||||||
v-model="deployData.maxBurnPerProposal"
|
|
||||||
type="number"
|
|
||||||
min="1"
|
|
||||||
step="1"
|
|
||||||
placeholder="1000000"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<small class="form-help">Максимальное количество токенов, которое можно сжечь за одно предложение</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="burnCooldown">Кулдаун между сжиганиями (часы):</label>
|
|
||||||
<input
|
|
||||||
id="burnCooldown"
|
|
||||||
v-model="deployData.burnCooldown"
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
step="1"
|
|
||||||
placeholder="24"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<small class="form-help">Минимальное время между успешными сжиганиями токенов</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="allowTreasuryBurn">Разрешить сжигание из казны:</label>
|
|
||||||
<select
|
|
||||||
id="allowTreasuryBurn"
|
|
||||||
v-model="deployData.allowTreasuryBurn"
|
|
||||||
required
|
|
||||||
>
|
|
||||||
<option value="true">Да</option>
|
|
||||||
<option value="false">Нет</option>
|
|
||||||
</select>
|
|
||||||
<small class="form-help">Разрешить сжигание токенов из казны DLE</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="allowUserBurn">Разрешить сжигание от участников:</label>
|
|
||||||
<select
|
|
||||||
id="allowUserBurn"
|
|
||||||
v-model="deployData.allowUserBurn"
|
|
||||||
required
|
|
||||||
>
|
|
||||||
<option value="true">Да</option>
|
|
||||||
<option value="false">Нет</option>
|
|
||||||
</select>
|
|
||||||
<small class="form-help">Разрешить сжигание токенов от имени участников DLE</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="deployDescription">Описание предложения для деплоя:</label>
|
|
||||||
<textarea
|
|
||||||
id="deployDescription"
|
|
||||||
v-model="deployData.deployDescription"
|
|
||||||
placeholder="Предложение о деплое BurnModule для управления сжиганием токенов DLE..."
|
|
||||||
rows="3"
|
|
||||||
required
|
|
||||||
></textarea>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="votingDuration">Длительность голосования (часы):</label>
|
|
||||||
<input
|
|
||||||
id="votingDuration"
|
|
||||||
v-model="deployData.votingDuration"
|
|
||||||
type="number"
|
|
||||||
min="1"
|
|
||||||
max="168"
|
|
||||||
placeholder="24"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<small class="form-help">Время для голосования по предложению деплоя (1-168 часов)</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="submit" class="btn-primary" :disabled="isDeploying">
|
|
||||||
{{ isDeploying ? 'Деплой...' : 'Деплой BurnModule' }}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- Статус деплоя -->
|
|
||||||
<div v-if="deployStatus" class="deploy-status">
|
|
||||||
<p class="status-message">{{ deployStatus }}</p>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</BaseLayout>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref, computed, onMounted } from 'vue';
|
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
|
||||||
import BaseLayout from '../../../components/BaseLayout.vue';
|
|
||||||
import api from '../../../api/axios';
|
|
||||||
|
|
||||||
// Props
|
|
||||||
const props = defineProps({
|
|
||||||
isAuthenticated: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
identities: {
|
|
||||||
type: Array,
|
|
||||||
default: () => []
|
|
||||||
},
|
|
||||||
tokenBalances: {
|
|
||||||
type: Object,
|
|
||||||
default: () => ({})
|
|
||||||
},
|
|
||||||
isLoadingTokens: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Emits
|
|
||||||
const emit = defineEmits(['auth-action-completed']);
|
|
||||||
|
|
||||||
// Router
|
|
||||||
const route = useRoute();
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
// Состояние
|
|
||||||
const isDeploying = ref(false);
|
|
||||||
const deployStatus = ref('');
|
|
||||||
const selectedDle = ref(null);
|
|
||||||
const isLoadingDle = ref(false);
|
|
||||||
|
|
||||||
// Данные для деплоя
|
|
||||||
const deployData = ref({
|
|
||||||
moduleName: 'BurnModule',
|
|
||||||
moduleVersion: '1.0.0',
|
|
||||||
moduleDescription: 'Модуль для сжигания токенов DLE через governance',
|
|
||||||
maxBurnPerProposal: 1000000,
|
|
||||||
burnCooldown: 24,
|
|
||||||
allowTreasuryBurn: 'true',
|
|
||||||
allowUserBurn: 'true',
|
|
||||||
deployDescription: 'Предложение о деплое BurnModule для управления сжиганием токенов DLE',
|
|
||||||
votingDuration: 24
|
|
||||||
});
|
|
||||||
|
|
||||||
// Получаем адрес DLE из URL
|
|
||||||
const dleAddress = computed(() => route.query.address);
|
|
||||||
|
|
||||||
// Загрузка данных DLE
|
|
||||||
const loadDleData = async () => {
|
|
||||||
if (!dleAddress.value) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
isLoadingDle.value = true;
|
|
||||||
const response = await api.post('/blockchain/read-dle-info', {
|
|
||||||
dleAddress: dleAddress.value
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.data.success) {
|
|
||||||
selectedDle.value = response.data.data;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Ошибка загрузки данных DLE:', error);
|
|
||||||
} finally {
|
|
||||||
isLoadingDle.value = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Функция деплоя модуля
|
|
||||||
const deployModule = async () => {
|
|
||||||
if (isDeploying.value) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
isDeploying.value = true;
|
|
||||||
deployStatus.value = 'Подготовка к деплою...';
|
|
||||||
|
|
||||||
// Здесь будет логика деплоя модуля
|
|
||||||
console.log('Деплой BurnModule:', deployData.value);
|
|
||||||
|
|
||||||
// Временная заглушка
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
||||||
|
|
||||||
deployStatus.value = 'Модуль успешно развернут!';
|
|
||||||
|
|
||||||
// Очищаем статус через 3 секунды
|
|
||||||
setTimeout(() => {
|
|
||||||
deployStatus.value = '';
|
|
||||||
}, 3000);
|
|
||||||
|
|
||||||
alert('BurnModule успешно развернут!');
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Ошибка деплоя модуля:', error);
|
|
||||||
deployStatus.value = 'Ошибка деплоя модуля';
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
deployStatus.value = '';
|
|
||||||
}, 3000);
|
|
||||||
|
|
||||||
alert('Ошибка при деплое модуля');
|
|
||||||
} finally {
|
|
||||||
isDeploying.value = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Загружаем данные при монтировании
|
|
||||||
onMounted(() => {
|
|
||||||
loadDleData();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.module-deploy-page {
|
|
||||||
padding: 20px;
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: flex-start;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
padding-bottom: 20px;
|
|
||||||
border-bottom: 2px solid #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-content h1 {
|
|
||||||
color: var(--color-primary);
|
|
||||||
font-size: 2.5rem;
|
|
||||||
margin: 0 0 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-content p {
|
|
||||||
color: var(--color-grey-dark);
|
|
||||||
font-size: 1.1rem;
|
|
||||||
margin: 0 0 15px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dle-info {
|
|
||||||
display: flex;
|
|
||||||
gap: 15px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dle-name {
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dle-address {
|
|
||||||
font-family: monospace;
|
|
||||||
color: var(--color-grey-dark);
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-btn {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
font-size: 2rem;
|
|
||||||
color: var(--color-grey-dark);
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-btn:hover {
|
|
||||||
color: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.module-description {
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.description-card {
|
|
||||||
background: #f8f9fa;
|
|
||||||
padding: 25px;
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
border: 1px solid #e9ecef;
|
|
||||||
}
|
|
||||||
|
|
||||||
.description-card h3 {
|
|
||||||
color: var(--color-primary);
|
|
||||||
margin: 0 0 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.description-content h4 {
|
|
||||||
color: var(--color-grey-dark);
|
|
||||||
margin: 20px 0 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.description-content ul {
|
|
||||||
margin: 10px 0;
|
|
||||||
padding-left: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.description-content li {
|
|
||||||
margin: 5px 0;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.deploy-form-section {
|
|
||||||
background: white;
|
|
||||||
padding: 30px;
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-header {
|
|
||||||
margin-bottom: 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-header h3 {
|
|
||||||
color: var(--color-primary);
|
|
||||||
margin: 0 0 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-header p {
|
|
||||||
color: var(--color-grey-dark);
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.deploy-form {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-row {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group label {
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--color-grey-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group input,
|
|
||||||
.form-group select,
|
|
||||||
.form-group textarea {
|
|
||||||
padding: 12px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group textarea {
|
|
||||||
resize: vertical;
|
|
||||||
min-height: 80px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-help {
|
|
||||||
font-size: 0.85rem;
|
|
||||||
color: var(--color-grey-dark);
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary {
|
|
||||||
background: var(--color-primary);
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
padding: 15px 30px;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
font-size: 1.1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background-color 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary:hover:not(:disabled) {
|
|
||||||
background: var(--color-primary-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary:disabled {
|
|
||||||
opacity: 0.6;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.deploy-status {
|
|
||||||
margin-top: 20px;
|
|
||||||
padding: 15px;
|
|
||||||
background: #e8f5e8;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
border-left: 4px solid #28a745;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-message {
|
|
||||||
margin: 0;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #28a745;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.form-row {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dle-info {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,218 +0,0 @@
|
|||||||
<!--
|
|
||||||
Copyright (c) 2024-2025 Тарабанов Александр Викторович
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
This software is proprietary and confidential.
|
|
||||||
Unauthorized copying, modification, or distribution is prohibited.
|
|
||||||
|
|
||||||
For licensing inquiries: info@hb3-accelerator.com
|
|
||||||
Website: https://hb3-accelerator.com
|
|
||||||
GitHub: https://github.com/HB3-ACCELERATOR
|
|
||||||
-->
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<BaseLayout
|
|
||||||
:is-authenticated="isAuthenticated"
|
|
||||||
:identities="identities"
|
|
||||||
:token-balances="tokenBalances"
|
|
||||||
:is-loading-tokens="isLoadingTokens"
|
|
||||||
@auth-action-completed="$emit('auth-action-completed')"
|
|
||||||
>
|
|
||||||
<div class="communication-module-deploy">
|
|
||||||
<!-- Заголовок -->
|
|
||||||
<div class="page-header">
|
|
||||||
<div class="header-content">
|
|
||||||
<h1>Деплой CommunicationModule</h1>
|
|
||||||
<p>Коммуникации - сообщения, звонки, история общения между участниками</p>
|
|
||||||
<p v-if="dleAddress" class="dle-address">
|
|
||||||
<strong>DLE:</strong> {{ dleAddress }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<button class="close-btn" @click="router.push('/management/modules')">×</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Информация о модуле -->
|
|
||||||
<div class="module-info">
|
|
||||||
<div class="info-card">
|
|
||||||
<h3>💬 CommunicationModule</h3>
|
|
||||||
<div class="info-grid">
|
|
||||||
<div class="info-item">
|
|
||||||
<strong>Назначение:</strong> Коммуникации между участниками DLE
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<strong>Функции:</strong> Сообщения, аудио/видео звонки, история общения
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<strong>Безопасность:</strong> Кворум для коммуникационных операций
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Форма деплоя будет добавлена позже -->
|
|
||||||
<div class="deploy-form-placeholder">
|
|
||||||
<div class="placeholder-content">
|
|
||||||
<h3>🚧 Форма деплоя в разработке</h3>
|
|
||||||
<p>Здесь будет форма для деплоя CommunicationModule</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</BaseLayout>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { defineProps, defineEmits, ref, onMounted } from 'vue';
|
|
||||||
import { useRouter, useRoute } from 'vue-router';
|
|
||||||
import BaseLayout from '../../../components/BaseLayout.vue';
|
|
||||||
|
|
||||||
// Определяем props
|
|
||||||
const props = defineProps({
|
|
||||||
isAuthenticated: { type: Boolean, default: false },
|
|
||||||
identities: { type: Array, default: () => [] },
|
|
||||||
tokenBalances: { type: Object, default: () => ({}) },
|
|
||||||
isLoadingTokens: { type: Boolean, default: false }
|
|
||||||
});
|
|
||||||
|
|
||||||
// Определяем emits
|
|
||||||
const emit = defineEmits(['auth-action-completed']);
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
const route = useRoute();
|
|
||||||
|
|
||||||
// Состояние
|
|
||||||
const isLoading = ref(false);
|
|
||||||
const dleAddress = ref(route.query.address || null);
|
|
||||||
|
|
||||||
// Инициализация
|
|
||||||
onMounted(() => {
|
|
||||||
console.log('[CommunicationModuleDeployView] Страница загружена');
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.communication-module-deploy {
|
|
||||||
padding: 20px;
|
|
||||||
background-color: var(--color-white);
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
|
||||||
margin-top: 20px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
padding-bottom: 20px;
|
|
||||||
border-bottom: 2px solid #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header h1 {
|
|
||||||
color: var(--color-primary);
|
|
||||||
font-size: 2rem;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header p {
|
|
||||||
margin: 10px 0 0 0;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dle-address {
|
|
||||||
margin-top: 10px !important;
|
|
||||||
font-family: monospace;
|
|
||||||
background: #f8f9fa;
|
|
||||||
padding: 8px 12px;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
border: 1px solid #dee2e6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-btn {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
cursor: pointer;
|
|
||||||
color: #666;
|
|
||||||
padding: 0;
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 50%;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-btn:hover {
|
|
||||||
background: #f0f0f0;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Информация о модуле */
|
|
||||||
.module-info {
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-card {
|
|
||||||
background: #f8f9fa;
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
padding: 20px;
|
|
||||||
border: 1px solid #e9ecef;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-card h3 {
|
|
||||||
margin: 0 0 15px 0;
|
|
||||||
color: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
||||||
gap: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-item {
|
|
||||||
padding: 10px;
|
|
||||||
background: white;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
border: 1px solid #dee2e6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-item strong {
|
|
||||||
color: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Плейсхолдер для формы */
|
|
||||||
.deploy-form-placeholder {
|
|
||||||
background: #f8f9fa;
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
padding: 40px;
|
|
||||||
text-align: center;
|
|
||||||
border: 2px dashed #dee2e6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder-content h3 {
|
|
||||||
color: var(--color-primary);
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder-content p {
|
|
||||||
color: #666;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Адаптивность */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.info-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 15px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,707 +0,0 @@
|
|||||||
<!--
|
|
||||||
Copyright (c) 2024-2025 Тарабанов Александр Викторович
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
This software is proprietary and confidential.
|
|
||||||
Unauthorized copying, modification, or distribution is prohibited.
|
|
||||||
|
|
||||||
For licensing inquiries: info@hb3-accelerator.com
|
|
||||||
Website: https://hb3-accelerator.com
|
|
||||||
GitHub: https://github.com/HB3-ACCELERATOR
|
|
||||||
-->
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<BaseLayout
|
|
||||||
:is-authenticated="isAuthenticated"
|
|
||||||
:identities="identities"
|
|
||||||
:token-balances="tokenBalances"
|
|
||||||
:is-loading-tokens="isLoadingTokens"
|
|
||||||
@auth-action-completed="$emit('auth-action-completed')"
|
|
||||||
>
|
|
||||||
<div class="reader-module-deploy">
|
|
||||||
<!-- Заголовок -->
|
|
||||||
<div class="page-header">
|
|
||||||
<div class="header-content">
|
|
||||||
<h1>Деплой DLEReader</h1>
|
|
||||||
<p>API для чтения данных DLE - получение информации о контракте и предложениях</p>
|
|
||||||
<p v-if="dleAddress" class="dle-address">
|
|
||||||
<strong>DLE:</strong> {{ dleAddress }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<button class="close-btn" @click="router.push('/management/modules')">×</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Информация о модуле -->
|
|
||||||
<div class="module-info">
|
|
||||||
<div class="info-card">
|
|
||||||
<h3>📊 DLEReader</h3>
|
|
||||||
<div class="info-grid">
|
|
||||||
<div class="info-item">
|
|
||||||
<strong>Назначение:</strong> Чтение данных DLE контракта
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<strong>Функции:</strong> API для предложений, голосования, статистики
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<strong>Безопасность:</strong> Только чтение, не изменяет состояние
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Форма деплоя модуля администратором -->
|
|
||||||
<div v-if="canManageSettings" class="deploy-form">
|
|
||||||
<div class="form-header">
|
|
||||||
<h3>🔧 Деплой DLEReader администратором</h3>
|
|
||||||
<p>Администратор деплоит модуль, затем создает предложение для добавления в DLE</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-content">
|
|
||||||
<!-- Информация о сетях -->
|
|
||||||
<div class="networks-info">
|
|
||||||
<h4>📡 Сети для деплоя:</h4>
|
|
||||||
<div class="networks-list">
|
|
||||||
<div class="network-item">
|
|
||||||
<span class="network-name">Sepolia</span>
|
|
||||||
<span class="network-chain-id">Chain ID: 11155111</span>
|
|
||||||
</div>
|
|
||||||
<div class="network-item">
|
|
||||||
<span class="network-name">Holesky</span>
|
|
||||||
<span class="network-chain-id">Chain ID: 17000</span>
|
|
||||||
</div>
|
|
||||||
<div class="network-item">
|
|
||||||
<span class="network-name">Arbitrum Sepolia</span>
|
|
||||||
<span class="network-chain-id">Chain ID: 421614</span>
|
|
||||||
</div>
|
|
||||||
<div class="network-item">
|
|
||||||
<span class="network-name">Base Sepolia</span>
|
|
||||||
<span class="network-chain-id">Chain ID: 84532</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Настройки модуля -->
|
|
||||||
<div class="module-settings">
|
|
||||||
<h4>⚙️ Настройки DLEReader:</h4>
|
|
||||||
|
|
||||||
<div class="settings-form">
|
|
||||||
<!-- Поля администратора -->
|
|
||||||
<div class="admin-section">
|
|
||||||
<h5>🔐 Настройки администратора:</h5>
|
|
||||||
|
|
||||||
<div class="form-row">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="adminPrivateKey">Приватный ключ администратора:</label>
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
id="adminPrivateKey"
|
|
||||||
v-model="moduleSettings.adminPrivateKey"
|
|
||||||
class="form-control"
|
|
||||||
placeholder="0x..."
|
|
||||||
required
|
|
||||||
>
|
|
||||||
<small class="form-help">Приватный ключ для деплоя модуля (администратор платит газ)</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="etherscanApiKey">Etherscan API ключ:</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="etherscanApiKey"
|
|
||||||
v-model="moduleSettings.etherscanApiKey"
|
|
||||||
class="form-control"
|
|
||||||
placeholder="YourAPIKey..."
|
|
||||||
>
|
|
||||||
<small class="form-help">API ключ для автоматической верификации контрактов</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
|
|
||||||
<div class="simple-info">
|
|
||||||
<h5>📋 Информация о DLEReader:</h5>
|
|
||||||
<div class="info-text">
|
|
||||||
<p><strong>DLEReader</strong> - это простой read-only модуль, который:</p>
|
|
||||||
<ul>
|
|
||||||
<li>✅ Только читает данные из DLE контракта</li>
|
|
||||||
<li>✅ Не изменяет состояние блокчейна</li>
|
|
||||||
<li>✅ Предоставляет API для получения информации</li>
|
|
||||||
<li>✅ Безопасен для обновления</li>
|
|
||||||
</ul>
|
|
||||||
<p><strong>Конструктор принимает только один параметр:</strong> адрес DLE контракта</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Кнопка деплоя -->
|
|
||||||
<div class="deploy-actions">
|
|
||||||
<button
|
|
||||||
class="btn btn-primary btn-large deploy-module"
|
|
||||||
@click="deployDLEReader"
|
|
||||||
:disabled="isDeploying || !dleAddress || !isFormValid"
|
|
||||||
>
|
|
||||||
<i class="fas fa-rocket" :class="{ 'fa-spin': isDeploying }"></i>
|
|
||||||
{{ isDeploying ? 'Деплой модуля...' : 'Деплой DLEReader' }}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div v-if="!isFormValid && !isDeploying" class="form-validation-info">
|
|
||||||
<i class="fas fa-exclamation-triangle"></i>
|
|
||||||
<span>Заполните приватный ключ и API ключ для деплоя</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="deploymentProgress" class="deployment-progress">
|
|
||||||
<div class="progress-info">
|
|
||||||
<span>{{ deploymentProgress.message }}</span>
|
|
||||||
<span class="progress-percentage">{{ deploymentProgress.percentage }}%</span>
|
|
||||||
</div>
|
|
||||||
<div class="progress-bar">
|
|
||||||
<div class="progress-fill" :style="{ width: deploymentProgress.percentage + '%' }"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Сообщение для пользователей без прав доступа -->
|
|
||||||
<div v-if="!canManageSettings" class="no-access-message">
|
|
||||||
<div class="message-content">
|
|
||||||
<h3>🔒 Нет прав доступа</h3>
|
|
||||||
<p>У вас нет прав для деплоя смарт-контрактов. Только пользователи с ролью Editor могут выполнять деплой.</p>
|
|
||||||
<button class="btn btn-secondary" @click="router.push('/management/modules')">
|
|
||||||
← Вернуться к модулям
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</BaseLayout>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref, computed, onMounted } from 'vue';
|
|
||||||
import { useRouter, useRoute } from 'vue-router';
|
|
||||||
import BaseLayout from '../../../components/BaseLayout.vue';
|
|
||||||
import { usePermissions } from '@/composables/usePermissions';
|
|
||||||
|
|
||||||
// Props
|
|
||||||
const props = defineProps({
|
|
||||||
isAuthenticated: Boolean,
|
|
||||||
identities: Array,
|
|
||||||
tokenBalances: Object,
|
|
||||||
isLoadingTokens: Boolean
|
|
||||||
});
|
|
||||||
|
|
||||||
const emit = defineEmits(['auth-action-completed']);
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
const route = useRoute();
|
|
||||||
const { canEdit, canManageSettings } = usePermissions();
|
|
||||||
|
|
||||||
// Состояние
|
|
||||||
const isLoading = ref(false);
|
|
||||||
const dleAddress = ref(route.query.address || null);
|
|
||||||
const isDeploying = ref(false);
|
|
||||||
const deploymentProgress = ref(null);
|
|
||||||
|
|
||||||
// Настройки модуля
|
|
||||||
const moduleSettings = ref({
|
|
||||||
// Поля администратора
|
|
||||||
adminPrivateKey: '',
|
|
||||||
etherscanApiKey: ''
|
|
||||||
});
|
|
||||||
|
|
||||||
// Проверка валидности формы
|
|
||||||
const isFormValid = computed(() => {
|
|
||||||
return moduleSettings.value.adminPrivateKey && moduleSettings.value.etherscanApiKey;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Функция деплоя DLEReader
|
|
||||||
async function deployDLEReader() {
|
|
||||||
if (!canManageSettings.value) {
|
|
||||||
alert('У вас нет прав для деплоя смарт-контрактов');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
isDeploying.value = true;
|
|
||||||
deploymentProgress.value = {
|
|
||||||
message: 'Инициализация деплоя...',
|
|
||||||
percentage: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log('[DLEReaderDeployView] Начинаем деплой DLEReader для DLE:', dleAddress.value);
|
|
||||||
|
|
||||||
// Вызываем API для деплоя модуля администратором
|
|
||||||
const response = await fetch('/api/dle-modules/deploy-reader-admin', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
dleAddress: dleAddress.value,
|
|
||||||
moduleType: 'reader',
|
|
||||||
adminPrivateKey: moduleSettings.value.adminPrivateKey,
|
|
||||||
etherscanApiKey: moduleSettings.value.etherscanApiKey,
|
|
||||||
settings: {
|
|
||||||
// Используем настройки по умолчанию
|
|
||||||
useDefaultSettings: true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await response.json();
|
|
||||||
|
|
||||||
if (result.success) {
|
|
||||||
console.log('[DLEReaderDeployView] Деплой успешно запущен:', result);
|
|
||||||
|
|
||||||
// Обновляем прогресс
|
|
||||||
deploymentProgress.value = {
|
|
||||||
message: 'Деплой запущен успешно! Проверьте логи для отслеживания прогресса.',
|
|
||||||
percentage: 100
|
|
||||||
};
|
|
||||||
|
|
||||||
// Показываем детальную информацию о деплое
|
|
||||||
const deployInfo = result.data || {};
|
|
||||||
const deployedAddresses = deployInfo.addresses || [];
|
|
||||||
|
|
||||||
let successMessage = '✅ DLEReader успешно задеплоен!\n\n';
|
|
||||||
successMessage += `📊 Детали деплоя:\n`;
|
|
||||||
successMessage += `• DLE: ${dleAddress.value}\n`;
|
|
||||||
successMessage += `• Тип модуля: DLEReader\n`;
|
|
||||||
successMessage += `• Адрес модуля: ${deployInfo.moduleAddress || 'Не указан'}\n`;
|
|
||||||
|
|
||||||
if (deployedAddresses.length > 0) {
|
|
||||||
successMessage += `\n🌐 Задеплоенные адреса:\n`;
|
|
||||||
deployedAddresses.forEach((addr, index) => {
|
|
||||||
successMessage += `${index + 1}. ${addr.network}: ${addr.address}\n`;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
successMessage += `\n📝 Следующий шаг: Создайте предложение для добавления модуля в DLE через governance.`;
|
|
||||||
|
|
||||||
alert(successMessage);
|
|
||||||
|
|
||||||
// Перенаправляем обратно к модулям
|
|
||||||
setTimeout(() => {
|
|
||||||
router.push(`/management/modules?address=${dleAddress.value}`);
|
|
||||||
}, 3000);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
throw new Error(result.error || 'Неизвестная ошибка');
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[DLEReaderDeployView] Ошибка деплоя:', error);
|
|
||||||
alert('❌ Ошибка деплоя: ' + error.message);
|
|
||||||
|
|
||||||
deploymentProgress.value = {
|
|
||||||
message: 'Ошибка деплоя: ' + error.message,
|
|
||||||
percentage: 0
|
|
||||||
};
|
|
||||||
} finally {
|
|
||||||
isDeploying.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Инициализация
|
|
||||||
onMounted(() => {
|
|
||||||
console.log('[DLEReaderDeployView] Страница загружена');
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.reader-module-deploy {
|
|
||||||
padding: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: flex-start;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
padding: 20px;
|
|
||||||
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-content h1 {
|
|
||||||
margin: 0 0 10px 0;
|
|
||||||
font-size: 2rem;
|
|
||||||
font-weight: 700;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-content p {
|
|
||||||
margin: 0 0 5px 0;
|
|
||||||
opacity: 0.9;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dle-address {
|
|
||||||
font-family: 'Courier New', monospace;
|
|
||||||
background: rgba(255, 255, 255, 0.1);
|
|
||||||
padding: 8px 12px;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-btn {
|
|
||||||
background: rgba(255, 255, 255, 0.2);
|
|
||||||
border: none;
|
|
||||||
color: white;
|
|
||||||
font-size: 24px;
|
|
||||||
width: 40px;
|
|
||||||
height: 40px;
|
|
||||||
border-radius: 50%;
|
|
||||||
cursor: pointer;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-btn:hover {
|
|
||||||
background: rgba(255, 255, 255, 0.3);
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Информация о модуле */
|
|
||||||
.module-info {
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-card {
|
|
||||||
background: white;
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
padding: 20px;
|
|
||||||
border: 1px solid #e9ecef;
|
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-card h3 {
|
|
||||||
margin: 0 0 15px 0;
|
|
||||||
color: var(--color-primary);
|
|
||||||
font-size: 1.5rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
||||||
gap: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-item {
|
|
||||||
padding: 10px;
|
|
||||||
background: #f8f9fa;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
border-left: 4px solid var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-item strong {
|
|
||||||
color: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Форма деплоя */
|
|
||||||
.deploy-form {
|
|
||||||
background: #f8f9fa;
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
padding: 20px;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
border: 1px solid #e9ecef;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-header h3 {
|
|
||||||
margin: 0 0 10px 0;
|
|
||||||
color: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-header p {
|
|
||||||
margin: 0 0 20px 0;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.networks-info,
|
|
||||||
.module-settings {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
padding: 15px;
|
|
||||||
background: white;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
border: 1px solid #dee2e6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-form {
|
|
||||||
margin-top: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-row {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: 15px;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-control {
|
|
||||||
width: 100%;
|
|
||||||
padding: 10px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
font-size: 14px;
|
|
||||||
transition: border-color 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-control:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: var(--color-primary);
|
|
||||||
box-shadow: 0 0 0 2px rgba(var(--color-primary-rgb), 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-help {
|
|
||||||
display: block;
|
|
||||||
margin-top: 5px;
|
|
||||||
font-size: 12px;
|
|
||||||
color: #666;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Настройки отображения данных */
|
|
||||||
.data-display-settings {
|
|
||||||
margin-top: 20px;
|
|
||||||
padding: 20px;
|
|
||||||
background: #f8f9fa;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
border: 1px solid #e9ecef;
|
|
||||||
}
|
|
||||||
|
|
||||||
.data-display-settings h5 {
|
|
||||||
margin: 0 0 15px 0;
|
|
||||||
color: var(--color-primary);
|
|
||||||
font-size: 1.1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.data-display-settings .form-row {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.data-display-settings .form-group {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.data-display-settings .form-group:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Секция администратора */
|
|
||||||
.admin-section {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
padding: 20px;
|
|
||||||
background: #fff3cd;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
border: 1px solid #ffeaa7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.admin-section h5 {
|
|
||||||
margin: 0 0 15px 0;
|
|
||||||
color: #856404;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Простая информация */
|
|
||||||
.simple-info {
|
|
||||||
margin-top: 20px;
|
|
||||||
padding: 20px;
|
|
||||||
background: #f8f9fa;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
border: 1px solid #e9ecef;
|
|
||||||
}
|
|
||||||
|
|
||||||
.simple-info h5 {
|
|
||||||
margin: 0 0 15px 0;
|
|
||||||
color: var(--color-primary);
|
|
||||||
font-size: 1.1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-text {
|
|
||||||
color: #666;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-text p {
|
|
||||||
margin: 0 0 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-text ul {
|
|
||||||
margin: 10px 0;
|
|
||||||
padding-left: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-text li {
|
|
||||||
margin: 5px 0;
|
|
||||||
color: #555;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-text strong {
|
|
||||||
color: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.deploy-actions {
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
padding: 12px 24px;
|
|
||||||
border: none;
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 500;
|
|
||||||
transition: all 0.3s;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary {
|
|
||||||
background: linear-gradient(135deg, var(--color-primary), var(--color-primary-dark));
|
|
||||||
color: white;
|
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary:hover:not(:disabled) {
|
|
||||||
background: linear-gradient(135deg, var(--color-primary-dark), var(--color-primary));
|
|
||||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
|
||||||
transform: translateY(-1px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary:disabled {
|
|
||||||
opacity: 0.6;
|
|
||||||
cursor: not-allowed;
|
|
||||||
transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-large {
|
|
||||||
padding: 16px 32px;
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.deployment-progress {
|
|
||||||
margin-top: 20px;
|
|
||||||
padding: 15px;
|
|
||||||
background: white;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
border: 1px solid #dee2e6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-info {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-percentage {
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-bar {
|
|
||||||
width: 100%;
|
|
||||||
height: 8px;
|
|
||||||
background: #f0f0f0;
|
|
||||||
border-radius: 4px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-fill {
|
|
||||||
height: 100%;
|
|
||||||
background: linear-gradient(90deg, var(--color-primary), var(--color-primary-dark));
|
|
||||||
transition: width 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Сети */
|
|
||||||
.networks-list {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
||||||
gap: 10px;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.network-item {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 10px;
|
|
||||||
background: #f8f9fa;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
border: 1px solid #dee2e6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.network-name {
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.network-chain-id {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #666;
|
|
||||||
font-family: 'Courier New', monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Сообщение об отсутствии прав доступа */
|
|
||||||
.no-access-message {
|
|
||||||
background: #fff3cd;
|
|
||||||
border: 1px solid #ffeaa7;
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
padding: 30px;
|
|
||||||
margin: 20px 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-content h3 {
|
|
||||||
color: #856404;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
font-size: 1.4em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-content p {
|
|
||||||
color: #856404;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
font-size: 1.1em;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-content .btn {
|
|
||||||
background: #6c757d;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
padding: 12px 24px;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-content .btn:hover {
|
|
||||||
background: #5a6268;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,663 +0,0 @@
|
|||||||
<!--
|
|
||||||
Copyright (c) 2024-2025 Тарабанов Александр Викторович
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
This software is proprietary and confidential.
|
|
||||||
Unauthorized copying, modification, or distribution is prohibited.
|
|
||||||
|
|
||||||
For licensing inquiries: info@hb3-accelerator.com
|
|
||||||
Website: https://hb3-accelerator.com
|
|
||||||
GitHub: https://github.com/HB3-ACCELERATOR
|
|
||||||
-->
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<BaseLayout
|
|
||||||
:is-authenticated="isAuthenticated"
|
|
||||||
:identities="identities"
|
|
||||||
:token-balances="tokenBalances"
|
|
||||||
:is-loading-tokens="isLoadingTokens"
|
|
||||||
@auth-action-completed="$emit('auth-action-completed')"
|
|
||||||
>
|
|
||||||
<div class="module-deploy-page">
|
|
||||||
<!-- Заголовок -->
|
|
||||||
<div class="page-header">
|
|
||||||
<div class="header-content">
|
|
||||||
<h1>🏛️ Деплой InheritanceModule</h1>
|
|
||||||
<p>Модуль наследования токенов DLE - защита активов и автоматическая передача наследникам</p>
|
|
||||||
<div v-if="selectedDle" class="dle-info">
|
|
||||||
<span class="dle-name">{{ selectedDle.name }} ({{ selectedDle.symbol }})</span>
|
|
||||||
<span class="dle-address">{{ selectedDle.dleAddress }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button class="close-btn" @click="router.push(`/management/modules?address=${route.query.address}`)">×</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Описание модуля -->
|
|
||||||
<div class="module-description">
|
|
||||||
<div class="description-card">
|
|
||||||
<h3>📋 Описание InheritanceModule</h3>
|
|
||||||
<div class="description-content">
|
|
||||||
<p><strong>InheritanceModule</strong> - это модуль для автоматической передачи токенов DLE наследникам в случае смерти или недееспособности токенхолдера.</p>
|
|
||||||
|
|
||||||
<h4>🔧 Основная функциональность:</h4>
|
|
||||||
<ul>
|
|
||||||
<li><strong>Назначение наследников:</strong> Токенхолдеры могут указать один или несколько наследников</li>
|
|
||||||
<li><strong>Распределение долей:</strong> Настройка процентного распределения токенов между наследниками</li>
|
|
||||||
<li><strong>Условия активации:</strong> Настройка условий для передачи токенов (смерть, недееспособность)</li>
|
|
||||||
<li><strong>Временные ограничения:</strong> Установка минимального периода владения токенами</li>
|
|
||||||
<li><strong>Множественные наследники:</strong> Поддержка сложных схем наследования</li>
|
|
||||||
<li><strong>Отзыв и изменение:</strong> Возможность изменения наследников в любое время</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h4>🏛️ Юридические аспекты:</h4>
|
|
||||||
<ul>
|
|
||||||
<li><strong>Соответствие законам:</strong> Интеграция с юридическими системами наследования</li>
|
|
||||||
<li><strong>Документооборот:</strong> Автоматическое создание юридических документов</li>
|
|
||||||
<li><strong>Подтверждение смерти:</strong> Интеграция с государственными реестрами</li>
|
|
||||||
<li><strong>Споры и оспаривание:</strong> Механизмы разрешения споров о наследстве</li>
|
|
||||||
<li><strong>Налоговые обязательства:</strong> Автоматический расчет налогов на наследство</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h4>🔐 Безопасность и контроль:</h4>
|
|
||||||
<ul>
|
|
||||||
<li>Все изменения наследников требуют подтверждения через governance</li>
|
|
||||||
<li>Криптографическая защита данных о наследниках</li>
|
|
||||||
<li>Аудит всех операций наследования</li>
|
|
||||||
<li>Возможность экстренной блокировки в случае споров</li>
|
|
||||||
<li>Интеграция с системой идентификации для подтверждения личности</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Архитектура модуля -->
|
|
||||||
<div class="module-architecture">
|
|
||||||
<div class="architecture-card">
|
|
||||||
<h3>🏗️ Архитектура InheritanceModule</h3>
|
|
||||||
<div class="architecture-content">
|
|
||||||
<div class="architecture-diagram">
|
|
||||||
<div class="diagram-row">
|
|
||||||
<div class="diagram-item tokenholder">
|
|
||||||
<h5>👤 Токенхолдер</h5>
|
|
||||||
<ul>
|
|
||||||
<li>Назначает наследников</li>
|
|
||||||
<li>Устанавливает доли</li>
|
|
||||||
<li>Управляет условиями</li>
|
|
||||||
<li>Может отозвать</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="diagram-arrow">→</div>
|
|
||||||
<div class="diagram-item inheritance">
|
|
||||||
<h5>🏛️ InheritanceModule</h5>
|
|
||||||
<ul>
|
|
||||||
<li>Хранит данные наследников</li>
|
|
||||||
<li>Проверяет условия</li>
|
|
||||||
<li>Выполняет передачу</li>
|
|
||||||
<li>Ведет аудит</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="diagram-arrow">→</div>
|
|
||||||
<div class="diagram-item heirs">
|
|
||||||
<h5>👥 Наследники</h5>
|
|
||||||
<ul>
|
|
||||||
<li>Получают токены</li>
|
|
||||||
<li>Подтверждают получение</li>
|
|
||||||
<li>Управляют наследством</li>
|
|
||||||
<li>Планируют налоги</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Типы наследования -->
|
|
||||||
<div class="inheritance-types">
|
|
||||||
<div class="types-card">
|
|
||||||
<h3>📊 Типы наследования</h3>
|
|
||||||
<div class="types-grid">
|
|
||||||
<div class="type-item">
|
|
||||||
<h4>👨👩👧👦 Семейное наследование</h4>
|
|
||||||
<p>Передача токенов членам семьи согласно традиционным схемам</p>
|
|
||||||
<ul>
|
|
||||||
<li>Супруг/супруга (50%)</li>
|
|
||||||
<li>Дети (равные доли)</li>
|
|
||||||
<li>Родители (при отсутствии детей)</li>
|
|
||||||
<li>Братья/сестры (при отсутствии родителей)</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="type-item">
|
|
||||||
<h4>🏢 Корпоративное наследование</h4>
|
|
||||||
<p>Передача токенов в рамках бизнес-структур и организаций</p>
|
|
||||||
<ul>
|
|
||||||
<li>Партнеры по бизнесу</li>
|
|
||||||
<li>Ключевые сотрудники</li>
|
|
||||||
<li>Дочерние компании</li>
|
|
||||||
<li>Благотворительные фонды</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="type-item">
|
|
||||||
<h4>🎯 Целевое наследование</h4>
|
|
||||||
<p>Передача токенов для достижения конкретных целей</p>
|
|
||||||
<ul>
|
|
||||||
<li>Образовательные учреждения</li>
|
|
||||||
<li>Исследовательские проекты</li>
|
|
||||||
<li>Экологические инициативы</li>
|
|
||||||
<li>Социальные программы</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="type-item">
|
|
||||||
<h4>⏰ Условное наследование</h4>
|
|
||||||
<p>Передача токенов при выполнении определенных условий</p>
|
|
||||||
<ul>
|
|
||||||
<li>Достижение определенного возраста</li>
|
|
||||||
<li>Завершение образования</li>
|
|
||||||
<li>Создание семьи</li>
|
|
||||||
<li>Достижение карьерных целей</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Примеры использования -->
|
|
||||||
<div class="usage-examples">
|
|
||||||
<div class="examples-card">
|
|
||||||
<h3>💡 Примеры использования</h3>
|
|
||||||
<div class="examples-content">
|
|
||||||
<div class="example-item">
|
|
||||||
<h4>👨👩👧👦 Семейное планирование</h4>
|
|
||||||
<div class="example-code">
|
|
||||||
<pre><code>// Назначение наследников для семьи
|
|
||||||
function setFamilyInheritance() {
|
|
||||||
setHeir(spouse, 50); // Супруг 50%
|
|
||||||
setHeir(son, 25); // Сын 25%
|
|
||||||
setHeir(daughter, 25); // Дочь 25%
|
|
||||||
setActivationCondition("death");
|
|
||||||
}</code></pre>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="example-item">
|
|
||||||
<h4>🏢 Бизнес-преемственность</h4>
|
|
||||||
<div class="example-code">
|
|
||||||
<pre><code>// Передача бизнеса партнеру
|
|
||||||
function setBusinessInheritance() {
|
|
||||||
setHeir(businessPartner, 100); // Партнер 100%
|
|
||||||
setActivationCondition("death");
|
|
||||||
setTimeLock(365 days); // Минимум 1 год владения
|
|
||||||
}</code></pre>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="example-item">
|
|
||||||
<h4>🎯 Благотворительное наследование</h4>
|
|
||||||
<div class="example-code">
|
|
||||||
<pre><code>// Передача в благотворительный фонд
|
|
||||||
function setCharityInheritance() {
|
|
||||||
setHeir(environmentalFund, 70); // Экологический фонд 70%
|
|
||||||
setHeir(educationFund, 30); // Образовательный фонд 30%
|
|
||||||
setActivationCondition("death");
|
|
||||||
}</code></pre>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Юридические аспекты -->
|
|
||||||
<div class="legal-aspects">
|
|
||||||
<div class="legal-card">
|
|
||||||
<h3>⚖️ Юридические аспекты</h3>
|
|
||||||
<div class="legal-content">
|
|
||||||
<div class="legal-section">
|
|
||||||
<h4>📜 Соответствие законодательству</h4>
|
|
||||||
<ul>
|
|
||||||
<li><strong>Гражданский кодекс:</strong> Соответствие нормам наследования</li>
|
|
||||||
<li><strong>Налоговый кодекс:</strong> Правильный расчет налогов на наследство</li>
|
|
||||||
<li><strong>Семейный кодекс:</strong> Учет семейных обязательств</li>
|
|
||||||
<li><strong>Международное право:</strong> Наследование в разных юрисдикциях</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="legal-section">
|
|
||||||
<h4>🔍 Процедура подтверждения</h4>
|
|
||||||
<ul>
|
|
||||||
<li><strong>Свидетельство о смерти:</strong> Официальное подтверждение</li>
|
|
||||||
<li><strong>Медицинское заключение:</strong> При недееспособности</li>
|
|
||||||
<li><strong>Судебное решение:</strong> При спорах о наследстве</li>
|
|
||||||
<li><strong>Нотариальное заверение:</strong> Документов о наследниках</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="legal-section">
|
|
||||||
<h4>💰 Налоговые обязательства</h4>
|
|
||||||
<ul>
|
|
||||||
<li><strong>Налог на наследство:</strong> Автоматический расчет</li>
|
|
||||||
<li><strong>НДФЛ:</strong> При получении токенов</li>
|
|
||||||
<li><strong>Отчетность:</strong> Автоматическая подача деклараций</li>
|
|
||||||
<li><strong>Льготы:</strong> Учет налоговых льгот для наследников</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Статус разработки -->
|
|
||||||
<div class="development-status">
|
|
||||||
<div class="status-card">
|
|
||||||
<h3>🚧 Статус разработки</h3>
|
|
||||||
<div class="status-content">
|
|
||||||
<p><strong>InheritanceModule находится в стадии планирования.</strong></p>
|
|
||||||
<p>Модуль будет включать:</p>
|
|
||||||
<ul>
|
|
||||||
<li>✅ Систему назначения наследников</li>
|
|
||||||
<li>✅ Управление долями и условиями</li>
|
|
||||||
<li>✅ Интеграцию с юридическими системами</li>
|
|
||||||
<li>✅ Автоматическую передачу токенов</li>
|
|
||||||
<li>✅ Налоговые расчеты</li>
|
|
||||||
<li>✅ Аудит и мониторинг</li>
|
|
||||||
<li>✅ Разрешение споров</li>
|
|
||||||
</ul>
|
|
||||||
<p><em>Модуль будет доступен в следующих обновлениях DLE.</em></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</BaseLayout>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref, computed, onMounted } from 'vue';
|
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
|
||||||
import BaseLayout from '../../../components/BaseLayout.vue';
|
|
||||||
import api from '../../../api/axios';
|
|
||||||
|
|
||||||
// Props
|
|
||||||
const props = defineProps({
|
|
||||||
isAuthenticated: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
identities: {
|
|
||||||
type: Array,
|
|
||||||
default: () => []
|
|
||||||
},
|
|
||||||
tokenBalances: {
|
|
||||||
type: Object,
|
|
||||||
default: () => ({})
|
|
||||||
},
|
|
||||||
isLoadingTokens: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Emits
|
|
||||||
const emit = defineEmits(['auth-action-completed']);
|
|
||||||
|
|
||||||
// Router
|
|
||||||
const route = useRoute();
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
// Состояние
|
|
||||||
const selectedDle = ref(null);
|
|
||||||
const isLoadingDle = ref(false);
|
|
||||||
|
|
||||||
// Получаем адрес DLE из URL
|
|
||||||
const dleAddress = computed(() => route.query.address);
|
|
||||||
|
|
||||||
// Загрузка данных DLE
|
|
||||||
const loadDleData = async () => {
|
|
||||||
if (!dleAddress.value) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
isLoadingDle.value = true;
|
|
||||||
const response = await api.post('/blockchain/read-dle-info', {
|
|
||||||
dleAddress: dleAddress.value
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.data.success) {
|
|
||||||
selectedDle.value = response.data.data;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Ошибка загрузки данных DLE:', error);
|
|
||||||
} finally {
|
|
||||||
isLoadingDle.value = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Загружаем данные при монтировании
|
|
||||||
onMounted(() => {
|
|
||||||
loadDleData();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.module-deploy-page {
|
|
||||||
padding: 20px;
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: flex-start;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
padding-bottom: 20px;
|
|
||||||
border-bottom: 2px solid #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-content h1 {
|
|
||||||
color: var(--color-primary);
|
|
||||||
font-size: 2.5rem;
|
|
||||||
margin: 0 0 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-content p {
|
|
||||||
color: var(--color-grey-dark);
|
|
||||||
font-size: 1.1rem;
|
|
||||||
margin: 0 0 15px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dle-info {
|
|
||||||
display: flex;
|
|
||||||
gap: 15px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dle-name {
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dle-address {
|
|
||||||
font-family: monospace;
|
|
||||||
color: var(--color-grey-dark);
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-btn {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
font-size: 2rem;
|
|
||||||
color: var(--color-grey-dark);
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-btn:hover {
|
|
||||||
color: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.module-description,
|
|
||||||
.module-architecture,
|
|
||||||
.inheritance-types,
|
|
||||||
.usage-examples,
|
|
||||||
.legal-aspects,
|
|
||||||
.development-status {
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.description-card,
|
|
||||||
.architecture-card,
|
|
||||||
.types-card,
|
|
||||||
.examples-card,
|
|
||||||
.legal-card,
|
|
||||||
.status-card {
|
|
||||||
background: #f8f9fa;
|
|
||||||
padding: 25px;
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
border: 1px solid #e9ecef;
|
|
||||||
}
|
|
||||||
|
|
||||||
.description-card h3,
|
|
||||||
.architecture-card h3,
|
|
||||||
.types-card h3,
|
|
||||||
.examples-card h3,
|
|
||||||
.legal-card h3,
|
|
||||||
.status-card h3 {
|
|
||||||
color: var(--color-primary);
|
|
||||||
margin: 0 0 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.description-content h4 {
|
|
||||||
color: var(--color-grey-dark);
|
|
||||||
margin: 20px 0 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.description-content ul {
|
|
||||||
margin: 10px 0;
|
|
||||||
padding-left: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.description-content li {
|
|
||||||
margin: 5px 0;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Архитектурная диаграмма */
|
|
||||||
.architecture-diagram {
|
|
||||||
margin: 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.diagram-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 20px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.diagram-item {
|
|
||||||
flex: 1;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
text-align: center;
|
|
||||||
min-height: 150px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.diagram-item.tokenholder {
|
|
||||||
background: #e8f5e8;
|
|
||||||
border: 2px solid #4caf50;
|
|
||||||
}
|
|
||||||
|
|
||||||
.diagram-item.inheritance {
|
|
||||||
background: #fff3e0;
|
|
||||||
border: 2px solid #ff9800;
|
|
||||||
}
|
|
||||||
|
|
||||||
.diagram-item.heirs {
|
|
||||||
background: #f3e5f5;
|
|
||||||
border: 2px solid #9c27b0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.diagram-item h5 {
|
|
||||||
margin: 0 0 15px 0;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.diagram-item ul {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
list-style: none;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.diagram-item li {
|
|
||||||
margin: 5px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.diagram-arrow {
|
|
||||||
font-size: 2rem;
|
|
||||||
color: var(--color-primary);
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Типы наследования */
|
|
||||||
.types-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|
||||||
gap: 20px;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.type-item {
|
|
||||||
background: white;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
border: 1px solid #e9ecef;
|
|
||||||
}
|
|
||||||
|
|
||||||
.type-item h4 {
|
|
||||||
color: var(--color-primary);
|
|
||||||
margin: 0 0 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.type-item p {
|
|
||||||
color: var(--color-grey-dark);
|
|
||||||
margin: 0 0 15px 0;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.type-item ul {
|
|
||||||
margin: 0;
|
|
||||||
padding-left: 20px;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.type-item li {
|
|
||||||
margin: 5px 0;
|
|
||||||
color: var(--color-grey-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Примеры использования */
|
|
||||||
.examples-content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.example-item {
|
|
||||||
background: white;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
border: 1px solid #e9ecef;
|
|
||||||
}
|
|
||||||
|
|
||||||
.example-item h4 {
|
|
||||||
color: var(--color-primary);
|
|
||||||
margin: 0 0 15px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.example-code {
|
|
||||||
background: #f8f9fa;
|
|
||||||
border: 1px solid #e9ecef;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
padding: 15px;
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.example-code pre {
|
|
||||||
margin: 0;
|
|
||||||
font-family: 'Courier New', monospace;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.example-code code {
|
|
||||||
background: none;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Юридические аспекты */
|
|
||||||
.legal-content {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.legal-section {
|
|
||||||
background: white;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
border: 1px solid #e9ecef;
|
|
||||||
}
|
|
||||||
|
|
||||||
.legal-section h4 {
|
|
||||||
color: var(--color-primary);
|
|
||||||
margin: 0 0 15px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.legal-section ul {
|
|
||||||
margin: 0;
|
|
||||||
padding-left: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.legal-section li {
|
|
||||||
margin: 8px 0;
|
|
||||||
line-height: 1.5;
|
|
||||||
color: var(--color-grey-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Статус разработки */
|
|
||||||
.status-content {
|
|
||||||
background: white;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
border: 1px solid #e9ecef;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-content p {
|
|
||||||
margin: 0 0 15px 0;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-content ul {
|
|
||||||
margin: 15px 0;
|
|
||||||
padding-left: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-content li {
|
|
||||||
margin: 5px 0;
|
|
||||||
color: var(--color-grey-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-content em {
|
|
||||||
color: var(--color-primary);
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.diagram-row {
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.diagram-arrow {
|
|
||||||
transform: rotate(90deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.types-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.legal-content {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dle-info {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,485 +0,0 @@
|
|||||||
<!--
|
|
||||||
Copyright (c) 2024-2025 Тарабанов Александр Викторович
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
This software is proprietary and confidential.
|
|
||||||
Unauthorized copying, modification, or distribution is prohibited.
|
|
||||||
|
|
||||||
For licensing inquiries: info@hb3-accelerator.com
|
|
||||||
Website: https://hb3-accelerator.com
|
|
||||||
GitHub: https://github.com/HB3-ACCELERATOR
|
|
||||||
-->
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<BaseLayout
|
|
||||||
:is-authenticated="isAuthenticated"
|
|
||||||
:identities="identities"
|
|
||||||
:token-balances="tokenBalances"
|
|
||||||
:is-loading-tokens="isLoadingTokens"
|
|
||||||
@auth-action-completed="$emit('auth-action-completed')"
|
|
||||||
>
|
|
||||||
<div class="module-deploy-page">
|
|
||||||
<!-- Заголовок -->
|
|
||||||
<div class="page-header">
|
|
||||||
<div class="header-content">
|
|
||||||
<h1>🚀 Деплой MintModule</h1>
|
|
||||||
<p>Модуль для выпуска новых токенов DLE через governance</p>
|
|
||||||
<div v-if="selectedDle" class="dle-info">
|
|
||||||
<span class="dle-name">{{ selectedDle.name }} ({{ selectedDle.symbol }})</span>
|
|
||||||
<span class="dle-address">{{ selectedDle.dleAddress }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button class="close-btn" @click="router.push(`/management/modules?address=${route.query.address}`)">×</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Описание модуля -->
|
|
||||||
<div class="module-description">
|
|
||||||
<div class="description-card">
|
|
||||||
<h3>📋 Описание MintModule</h3>
|
|
||||||
<div class="description-content">
|
|
||||||
<p><strong>MintModule</strong> - это модуль для управления выпуском новых токенов DLE через систему governance.</p>
|
|
||||||
|
|
||||||
<h4>🔧 Функциональность:</h4>
|
|
||||||
<ul>
|
|
||||||
<li><strong>Выпуск токенов:</strong> Создание дополнительных токенов DLE</li>
|
|
||||||
<li><strong>Governance:</strong> Все операции требуют голосования и кворума</li>
|
|
||||||
<li><strong>Безопасность:</strong> Контролируемый выпуск через коллективные решения</li>
|
|
||||||
<li><strong>Прозрачность:</strong> Все операции записываются в блокчейн</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h4>⚠️ Важные особенности:</h4>
|
|
||||||
<ul>
|
|
||||||
<li>Выпуск токенов возможен только через предложения и голосование</li>
|
|
||||||
<li>Новые токены могут быть распределены между участниками или добавлены в казну DLE</li>
|
|
||||||
<li>Все операции требуют достижения кворума</li>
|
|
||||||
<li>История всех выпусков сохраняется в блокчейне</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Форма деплоя -->
|
|
||||||
<div class="deploy-form-section">
|
|
||||||
<div class="form-header">
|
|
||||||
<h3>⚙️ Настройки деплоя</h3>
|
|
||||||
<p>Настройте параметры для деплоя MintModule</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<form @submit.prevent="deployModule" class="deploy-form">
|
|
||||||
<div class="form-row">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="moduleName">Название модуля:</label>
|
|
||||||
<input
|
|
||||||
id="moduleName"
|
|
||||||
v-model="deployData.moduleName"
|
|
||||||
type="text"
|
|
||||||
placeholder="MintModule"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="moduleVersion">Версия модуля:</label>
|
|
||||||
<input
|
|
||||||
id="moduleVersion"
|
|
||||||
v-model="deployData.moduleVersion"
|
|
||||||
type="text"
|
|
||||||
placeholder="1.0.0"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="moduleDescription">Описание модуля:</label>
|
|
||||||
<textarea
|
|
||||||
id="moduleDescription"
|
|
||||||
v-model="deployData.moduleDescription"
|
|
||||||
placeholder="Модуль для выпуска новых токенов DLE через governance..."
|
|
||||||
rows="3"
|
|
||||||
required
|
|
||||||
></textarea>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="maxMintPerProposal">Максимальный выпуск за одно предложение:</label>
|
|
||||||
<input
|
|
||||||
id="maxMintPerProposal"
|
|
||||||
v-model="deployData.maxMintPerProposal"
|
|
||||||
type="number"
|
|
||||||
min="1"
|
|
||||||
step="1"
|
|
||||||
placeholder="1000000"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<small class="form-help">Максимальное количество токенов, которое можно выпустить за одно предложение</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="mintCooldown">Кулдаун между выпусками (часы):</label>
|
|
||||||
<input
|
|
||||||
id="mintCooldown"
|
|
||||||
v-model="deployData.mintCooldown"
|
|
||||||
type="number"
|
|
||||||
min="0"
|
|
||||||
step="1"
|
|
||||||
placeholder="24"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<small class="form-help">Минимальное время между успешными выпусками токенов</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="deployDescription">Описание предложения для деплоя:</label>
|
|
||||||
<textarea
|
|
||||||
id="deployDescription"
|
|
||||||
v-model="deployData.deployDescription"
|
|
||||||
placeholder="Предложение о деплое MintModule для управления выпуском токенов DLE..."
|
|
||||||
rows="3"
|
|
||||||
required
|
|
||||||
></textarea>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="votingDuration">Длительность голосования (часы):</label>
|
|
||||||
<input
|
|
||||||
id="votingDuration"
|
|
||||||
v-model="deployData.votingDuration"
|
|
||||||
type="number"
|
|
||||||
min="1"
|
|
||||||
max="168"
|
|
||||||
placeholder="24"
|
|
||||||
required
|
|
||||||
/>
|
|
||||||
<small class="form-help">Время для голосования по предложению деплоя (1-168 часов)</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<button type="submit" class="btn-primary" :disabled="isDeploying">
|
|
||||||
{{ isDeploying ? 'Деплой...' : 'Деплой MintModule' }}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<!-- Статус деплоя -->
|
|
||||||
<div v-if="deployStatus" class="deploy-status">
|
|
||||||
<p class="status-message">{{ deployStatus }}</p>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</BaseLayout>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref, computed, onMounted } from 'vue';
|
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
|
||||||
import BaseLayout from '../../../components/BaseLayout.vue';
|
|
||||||
import api from '../../../api/axios';
|
|
||||||
|
|
||||||
// Props
|
|
||||||
const props = defineProps({
|
|
||||||
isAuthenticated: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
identities: {
|
|
||||||
type: Array,
|
|
||||||
default: () => []
|
|
||||||
},
|
|
||||||
tokenBalances: {
|
|
||||||
type: Object,
|
|
||||||
default: () => ({})
|
|
||||||
},
|
|
||||||
isLoadingTokens: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Emits
|
|
||||||
const emit = defineEmits(['auth-action-completed']);
|
|
||||||
|
|
||||||
// Router
|
|
||||||
const route = useRoute();
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
// Состояние
|
|
||||||
const isDeploying = ref(false);
|
|
||||||
const deployStatus = ref('');
|
|
||||||
const selectedDle = ref(null);
|
|
||||||
const isLoadingDle = ref(false);
|
|
||||||
|
|
||||||
// Данные для деплоя
|
|
||||||
const deployData = ref({
|
|
||||||
moduleName: 'MintModule',
|
|
||||||
moduleVersion: '1.0.0',
|
|
||||||
moduleDescription: 'Модуль для выпуска новых токенов DLE через governance',
|
|
||||||
maxMintPerProposal: 1000000,
|
|
||||||
mintCooldown: 24,
|
|
||||||
deployDescription: 'Предложение о деплое MintModule для управления выпуском токенов DLE',
|
|
||||||
votingDuration: 24
|
|
||||||
});
|
|
||||||
|
|
||||||
// Получаем адрес DLE из URL
|
|
||||||
const dleAddress = computed(() => route.query.address);
|
|
||||||
|
|
||||||
// Загрузка данных DLE
|
|
||||||
const loadDleData = async () => {
|
|
||||||
if (!dleAddress.value) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
isLoadingDle.value = true;
|
|
||||||
const response = await api.post('/blockchain/read-dle-info', {
|
|
||||||
dleAddress: dleAddress.value
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.data.success) {
|
|
||||||
selectedDle.value = response.data.data;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Ошибка загрузки данных DLE:', error);
|
|
||||||
} finally {
|
|
||||||
isLoadingDle.value = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Функция деплоя модуля
|
|
||||||
const deployModule = async () => {
|
|
||||||
if (isDeploying.value) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
isDeploying.value = true;
|
|
||||||
deployStatus.value = 'Подготовка к деплою...';
|
|
||||||
|
|
||||||
// Здесь будет логика деплоя модуля
|
|
||||||
console.log('Деплой MintModule:', deployData.value);
|
|
||||||
|
|
||||||
// Временная заглушка
|
|
||||||
await new Promise(resolve => setTimeout(resolve, 2000));
|
|
||||||
|
|
||||||
deployStatus.value = 'Модуль успешно развернут!';
|
|
||||||
|
|
||||||
// Очищаем статус через 3 секунды
|
|
||||||
setTimeout(() => {
|
|
||||||
deployStatus.value = '';
|
|
||||||
}, 3000);
|
|
||||||
|
|
||||||
alert('MintModule успешно развернут!');
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Ошибка деплоя модуля:', error);
|
|
||||||
deployStatus.value = 'Ошибка деплоя модуля';
|
|
||||||
|
|
||||||
setTimeout(() => {
|
|
||||||
deployStatus.value = '';
|
|
||||||
}, 3000);
|
|
||||||
|
|
||||||
alert('Ошибка при деплое модуля');
|
|
||||||
} finally {
|
|
||||||
isDeploying.value = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Загружаем данные при монтировании
|
|
||||||
onMounted(() => {
|
|
||||||
loadDleData();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.module-deploy-page {
|
|
||||||
padding: 20px;
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: flex-start;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
padding-bottom: 20px;
|
|
||||||
border-bottom: 2px solid #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-content h1 {
|
|
||||||
color: var(--color-primary);
|
|
||||||
font-size: 2.5rem;
|
|
||||||
margin: 0 0 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-content p {
|
|
||||||
color: var(--color-grey-dark);
|
|
||||||
font-size: 1.1rem;
|
|
||||||
margin: 0 0 15px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dle-info {
|
|
||||||
display: flex;
|
|
||||||
gap: 15px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dle-name {
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dle-address {
|
|
||||||
font-family: monospace;
|
|
||||||
color: var(--color-grey-dark);
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-btn {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
font-size: 2rem;
|
|
||||||
color: var(--color-grey-dark);
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-btn:hover {
|
|
||||||
color: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.module-description {
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.description-card {
|
|
||||||
background: #f8f9fa;
|
|
||||||
padding: 25px;
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
border: 1px solid #e9ecef;
|
|
||||||
}
|
|
||||||
|
|
||||||
.description-card h3 {
|
|
||||||
color: var(--color-primary);
|
|
||||||
margin: 0 0 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.description-content h4 {
|
|
||||||
color: var(--color-grey-dark);
|
|
||||||
margin: 20px 0 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.description-content ul {
|
|
||||||
margin: 10px 0;
|
|
||||||
padding-left: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.description-content li {
|
|
||||||
margin: 5px 0;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.deploy-form-section {
|
|
||||||
background: white;
|
|
||||||
padding: 30px;
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-header {
|
|
||||||
margin-bottom: 25px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-header h3 {
|
|
||||||
color: var(--color-primary);
|
|
||||||
margin: 0 0 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-header p {
|
|
||||||
color: var(--color-grey-dark);
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.deploy-form {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-row {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group label {
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--color-grey-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group input,
|
|
||||||
.form-group textarea {
|
|
||||||
padding: 12px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
font-size: 1rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group textarea {
|
|
||||||
resize: vertical;
|
|
||||||
min-height: 80px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-help {
|
|
||||||
font-size: 0.85rem;
|
|
||||||
color: var(--color-grey-dark);
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary {
|
|
||||||
background: var(--color-primary);
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
padding: 15px 30px;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
font-size: 1.1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background-color 0.3s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary:hover:not(:disabled) {
|
|
||||||
background: var(--color-primary-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary:disabled {
|
|
||||||
opacity: 0.6;
|
|
||||||
cursor: not-allowed;
|
|
||||||
}
|
|
||||||
|
|
||||||
.deploy-status {
|
|
||||||
margin-top: 20px;
|
|
||||||
padding: 15px;
|
|
||||||
background: #e8f5e8;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
border-left: 4px solid #28a745;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-message {
|
|
||||||
margin: 0;
|
|
||||||
font-weight: 600;
|
|
||||||
color: #28a745;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.form-row {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dle-info {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,205 +0,0 @@
|
|||||||
<!--
|
|
||||||
Copyright (c) 2024-2025 Тарабанов Александр Викторович
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
This software is proprietary and confidential.
|
|
||||||
Unauthorized copying, modification, or distribution is prohibited.
|
|
||||||
|
|
||||||
For licensing inquiries: info@hb3-accelerator.com
|
|
||||||
Website: https://hb3-accelerator.com
|
|
||||||
GitHub: https://github.com/HB3-ACCELERATOR
|
|
||||||
-->
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<BaseLayout
|
|
||||||
:is-authenticated="isAuthenticated"
|
|
||||||
:identities="identities"
|
|
||||||
:token-balances="tokenBalances"
|
|
||||||
:is-loading-tokens="isLoadingTokens"
|
|
||||||
@auth-action-completed="$emit('auth-action-completed')"
|
|
||||||
>
|
|
||||||
<div class="module-deploy-form">
|
|
||||||
<!-- Заголовок -->
|
|
||||||
<div class="page-header">
|
|
||||||
<div class="header-content">
|
|
||||||
<h1>Деплой пользовательского модуля</h1>
|
|
||||||
<p>Создание и деплой кастомного модуля для DLE</p>
|
|
||||||
</div>
|
|
||||||
<button class="close-btn" @click="router.push('/management/modules')">×</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Информация о пользовательских модулях -->
|
|
||||||
<div class="module-info">
|
|
||||||
<div class="info-card">
|
|
||||||
<h3>🔧 Пользовательские модули</h3>
|
|
||||||
<div class="info-grid">
|
|
||||||
<div class="info-item">
|
|
||||||
<strong>Назначение:</strong> Расширение функциональности DLE
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<strong>Возможности:</strong> Любая кастомная логика, интеграции, API
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<strong>Безопасность:</strong> Проверка через голосование токен-холдеров
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Форма деплоя будет добавлена позже -->
|
|
||||||
<div class="deploy-form-placeholder">
|
|
||||||
<div class="placeholder-content">
|
|
||||||
<h3>🚧 Форма деплоя в разработке</h3>
|
|
||||||
<p>Здесь будет универсальная форма для деплоя пользовательских модулей</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</BaseLayout>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { defineProps, defineEmits, ref, onMounted } from 'vue';
|
|
||||||
import { useRouter, useRoute } from 'vue-router';
|
|
||||||
import BaseLayout from '../../../components/BaseLayout.vue';
|
|
||||||
|
|
||||||
// Определяем props
|
|
||||||
const props = defineProps({
|
|
||||||
isAuthenticated: { type: Boolean, default: false },
|
|
||||||
identities: { type: Array, default: () => [] },
|
|
||||||
tokenBalances: { type: Object, default: () => ({}) },
|
|
||||||
isLoadingTokens: { type: Boolean, default: false }
|
|
||||||
});
|
|
||||||
|
|
||||||
// Определяем emits
|
|
||||||
const emit = defineEmits(['auth-action-completed']);
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
const route = useRoute();
|
|
||||||
|
|
||||||
// Состояние
|
|
||||||
const isLoading = ref(false);
|
|
||||||
|
|
||||||
// Инициализация
|
|
||||||
onMounted(() => {
|
|
||||||
console.log('[ModuleDeployFormView] Страница загружена');
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.module-deploy-form {
|
|
||||||
padding: 20px;
|
|
||||||
background-color: var(--color-white);
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
|
||||||
margin-top: 20px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
padding-bottom: 20px;
|
|
||||||
border-bottom: 2px solid #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header h1 {
|
|
||||||
color: var(--color-primary);
|
|
||||||
font-size: 2rem;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header p {
|
|
||||||
margin: 10px 0 0 0;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-btn {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
cursor: pointer;
|
|
||||||
color: #666;
|
|
||||||
padding: 0;
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 50%;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-btn:hover {
|
|
||||||
background: #f0f0f0;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Информация о модуле */
|
|
||||||
.module-info {
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-card {
|
|
||||||
background: #f8f9fa;
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
padding: 20px;
|
|
||||||
border: 1px solid #e9ecef;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-card h3 {
|
|
||||||
margin: 0 0 15px 0;
|
|
||||||
color: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
||||||
gap: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-item {
|
|
||||||
padding: 10px;
|
|
||||||
background: white;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
border: 1px solid #dee2e6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-item strong {
|
|
||||||
color: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Плейсхолдер для формы */
|
|
||||||
.deploy-form-placeholder {
|
|
||||||
background: #f8f9fa;
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
padding: 40px;
|
|
||||||
text-align: center;
|
|
||||||
border: 2px dashed #dee2e6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder-content h3 {
|
|
||||||
color: var(--color-primary);
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder-content p {
|
|
||||||
color: #666;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Адаптивность */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.info-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 15px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,584 +0,0 @@
|
|||||||
<!--
|
|
||||||
Copyright (c) 2024-2025 Тарабанов Александр Викторович
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
This software is proprietary and confidential.
|
|
||||||
Unauthorized copying, modification, or distribution is prohibited.
|
|
||||||
|
|
||||||
For licensing inquiries: info@hb3-accelerator.com
|
|
||||||
Website: https://hb3-accelerator.com
|
|
||||||
GitHub: https://github.com/HB3-ACCELERATOR
|
|
||||||
-->
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<BaseLayout
|
|
||||||
:is-authenticated="isAuthenticated"
|
|
||||||
:identities="identities"
|
|
||||||
:token-balances="tokenBalances"
|
|
||||||
:is-loading-tokens="isLoadingTokens"
|
|
||||||
@auth-action-completed="$emit('auth-action-completed')"
|
|
||||||
>
|
|
||||||
<div class="module-deploy-page">
|
|
||||||
<!-- Заголовок -->
|
|
||||||
<div class="page-header">
|
|
||||||
<div class="header-content">
|
|
||||||
<h1>🔗 Деплой OracleModule</h1>
|
|
||||||
<p>Модуль для интеграции с внешними данными и автоматизации DLE</p>
|
|
||||||
<div v-if="selectedDle" class="dle-info">
|
|
||||||
<span class="dle-name">{{ selectedDle.name }} ({{ selectedDle.symbol }})</span>
|
|
||||||
<span class="dle-address">{{ selectedDle.dleAddress }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<button class="close-btn" @click="router.push(`/management/modules?address=${route.query.address}`)">×</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Описание модуля -->
|
|
||||||
<div class="module-description">
|
|
||||||
<div class="description-card">
|
|
||||||
<h3>📋 Описание OracleModule</h3>
|
|
||||||
<div class="description-content">
|
|
||||||
<p><strong>OracleModule</strong> - это модуль для интеграции DLE с внешними источниками данных и автоматизации процессов на основе реальных событий.</p>
|
|
||||||
|
|
||||||
<h4>🔧 Основная функциональность:</h4>
|
|
||||||
<ul>
|
|
||||||
<li><strong>Интеграция с IoT:</strong> Получение данных от датчиков, производственных линий, оборудования</li>
|
|
||||||
<li><strong>API интеграция:</strong> Подключение к внешним системам, ERP, CRM, аналитическим платформам</li>
|
|
||||||
<li><strong>Автоматические триггеры:</strong> Создание предложений на основе внешних событий</li>
|
|
||||||
<li><strong>Валидация данных:</strong> Проверка достоверности полученной информации</li>
|
|
||||||
<li><strong>Множественные оракулы:</strong> Подтверждение данных от нескольких источников</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h4>🏭 Примеры применения:</h4>
|
|
||||||
<ul>
|
|
||||||
<li><strong>Производственные токены:</strong> Автоматический выпуск токенов за завершение партий продукции</li>
|
|
||||||
<li><strong>Качественные бонусы:</strong> Токены за высокое качество продукции на основе данных датчиков</li>
|
|
||||||
<li><strong>Экологические токены:</strong> Вознаграждения за снижение энергопотребления и выбросов</li>
|
|
||||||
<li><strong>Инновационные токены:</strong> Токены за внедрение новых технологий и процессов</li>
|
|
||||||
<li><strong>Рыночные корректировки:</strong> Автоматическая адаптация стратегии на основе рыночных данных</li>
|
|
||||||
</ul>
|
|
||||||
|
|
||||||
<h4>🔐 Безопасность и контроль:</h4>
|
|
||||||
<ul>
|
|
||||||
<li>Все оракулы должны быть авторизованы через governance</li>
|
|
||||||
<li>Данные валидируются перед обработкой</li>
|
|
||||||
<li>Критические решения требуют множественного подтверждения</li>
|
|
||||||
<li>История всех оракульных данных сохраняется в блокчейне</li>
|
|
||||||
<li>Возможность отключения оракулов в экстренных случаях</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Архитектура модуля -->
|
|
||||||
<div class="module-architecture">
|
|
||||||
<div class="architecture-card">
|
|
||||||
<h3>🏗️ Архитектура OracleModule</h3>
|
|
||||||
<div class="architecture-content">
|
|
||||||
<div class="architecture-diagram">
|
|
||||||
<div class="diagram-row">
|
|
||||||
<div class="diagram-item external">
|
|
||||||
<h5>🌐 Внешние источники</h5>
|
|
||||||
<ul>
|
|
||||||
<li>IoT датчики</li>
|
|
||||||
<li>Производственные линии</li>
|
|
||||||
<li>ERP/CRM системы</li>
|
|
||||||
<li>Аналитические платформы</li>
|
|
||||||
<li>Рыночные данные</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="diagram-arrow">→</div>
|
|
||||||
<div class="diagram-item oracle">
|
|
||||||
<h5>🔗 OracleModule</h5>
|
|
||||||
<ul>
|
|
||||||
<li>Получение данных</li>
|
|
||||||
<li>Валидация</li>
|
|
||||||
<li>Обработка</li>
|
|
||||||
<li>Создание предложений</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
<div class="diagram-arrow">→</div>
|
|
||||||
<div class="diagram-item dle">
|
|
||||||
<h5>🏛️ DLE Governance</h5>
|
|
||||||
<ul>
|
|
||||||
<li>Предложения</li>
|
|
||||||
<li>Голосование</li>
|
|
||||||
<li>Исполнение</li>
|
|
||||||
<li>Выпуск токенов</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Типы оракулов -->
|
|
||||||
<div class="oracle-types">
|
|
||||||
<div class="types-card">
|
|
||||||
<h3>📊 Типы оракулов</h3>
|
|
||||||
<div class="types-grid">
|
|
||||||
<div class="type-item">
|
|
||||||
<h4>🏭 Производственные оракулы</h4>
|
|
||||||
<p>Данные от производственных линий, оборудования, датчиков качества</p>
|
|
||||||
<ul>
|
|
||||||
<li>Завершение партий продукции</li>
|
|
||||||
<li>Показатели качества</li>
|
|
||||||
<li>Энергопотребление</li>
|
|
||||||
<li>Время простоя</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="type-item">
|
|
||||||
<h4>📈 Рыночные оракулы</h4>
|
|
||||||
<p>Рыночные данные, цены, спрос, конкуренция</p>
|
|
||||||
<ul>
|
|
||||||
<li>Цены на сырье</li>
|
|
||||||
<li>Спрос на продукцию</li>
|
|
||||||
<li>Конкурентные данные</li>
|
|
||||||
<li>Экономические индикаторы</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="type-item">
|
|
||||||
<h4>🌱 Экологические оракулы</h4>
|
|
||||||
<p>Данные об экологическом воздействии и устойчивости</p>
|
|
||||||
<ul>
|
|
||||||
<li>Выбросы CO2</li>
|
|
||||||
<li>Потребление энергии</li>
|
|
||||||
<li>Переработка отходов</li>
|
|
||||||
<li>Использование возобновляемых ресурсов</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="type-item">
|
|
||||||
<h4>💼 Бизнес-оракулы</h4>
|
|
||||||
<p>Бизнес-метрики, продажи, удовлетворенность клиентов</p>
|
|
||||||
<ul>
|
|
||||||
<li>Объемы продаж</li>
|
|
||||||
<li>Удовлетворенность клиентов</li>
|
|
||||||
<li>Эффективность процессов</li>
|
|
||||||
<li>Финансовые показатели</li>
|
|
||||||
</ul>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Примеры использования -->
|
|
||||||
<div class="usage-examples">
|
|
||||||
<div class="examples-card">
|
|
||||||
<h3>💡 Примеры использования</h3>
|
|
||||||
<div class="examples-content">
|
|
||||||
<div class="example-item">
|
|
||||||
<h4>🏭 Автоматические производственные токены</h4>
|
|
||||||
<div class="example-code">
|
|
||||||
<pre><code>// При завершении партии продукции
|
|
||||||
function onBatchCompleted(uint256 quantity, uint256 quality) {
|
|
||||||
uint256 tokens = quantity * quality * 10;
|
|
||||||
createMintProposal(tokens, "Production reward");
|
|
||||||
}</code></pre>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="example-item">
|
|
||||||
<h4>🌱 Экологические бонусы</h4>
|
|
||||||
<div class="example-code">
|
|
||||||
<pre><code>// При снижении энергопотребления
|
|
||||||
function onEnergyReduction(uint256 savedEnergy) {
|
|
||||||
uint256 tokens = savedEnergy * 5;
|
|
||||||
createMintProposal(tokens, "Energy efficiency bonus");
|
|
||||||
}</code></pre>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="example-item">
|
|
||||||
<h4>📈 Рыночная адаптация</h4>
|
|
||||||
<div class="example-code">
|
|
||||||
<pre><code>// При изменении рыночных условий
|
|
||||||
function onMarketChange(uint256 priceChange) {
|
|
||||||
if (priceChange > 10) {
|
|
||||||
createStrategyProposal("Increase production");
|
|
||||||
}
|
|
||||||
}</code></pre>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Статус разработки -->
|
|
||||||
<div class="development-status">
|
|
||||||
<div class="status-card">
|
|
||||||
<h3>🚧 Статус разработки</h3>
|
|
||||||
<div class="status-content">
|
|
||||||
<p><strong>OracleModule находится в стадии планирования.</strong></p>
|
|
||||||
<p>Модуль будет включать:</p>
|
|
||||||
<ul>
|
|
||||||
<li>✅ Систему авторизации оракулов</li>
|
|
||||||
<li>✅ Валидацию и обработку данных</li>
|
|
||||||
<li>✅ Интеграцию с IoT и API</li>
|
|
||||||
<li>✅ Автоматические триггеры</li>
|
|
||||||
<li>✅ Множественное подтверждение данных</li>
|
|
||||||
<li>✅ Аудит и мониторинг</li>
|
|
||||||
</ul>
|
|
||||||
<p><em>Модуль будет доступен в следующих обновлениях DLE.</em></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</BaseLayout>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { ref, computed, onMounted } from 'vue';
|
|
||||||
import { useRoute, useRouter } from 'vue-router';
|
|
||||||
import BaseLayout from '../../../components/BaseLayout.vue';
|
|
||||||
import api from '../../../api/axios';
|
|
||||||
|
|
||||||
// Props
|
|
||||||
const props = defineProps({
|
|
||||||
isAuthenticated: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
},
|
|
||||||
identities: {
|
|
||||||
type: Array,
|
|
||||||
default: () => []
|
|
||||||
},
|
|
||||||
tokenBalances: {
|
|
||||||
type: Object,
|
|
||||||
default: () => ({})
|
|
||||||
},
|
|
||||||
isLoadingTokens: {
|
|
||||||
type: Boolean,
|
|
||||||
default: false
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Emits
|
|
||||||
const emit = defineEmits(['auth-action-completed']);
|
|
||||||
|
|
||||||
// Router
|
|
||||||
const route = useRoute();
|
|
||||||
const router = useRouter();
|
|
||||||
|
|
||||||
// Состояние
|
|
||||||
const selectedDle = ref(null);
|
|
||||||
const isLoadingDle = ref(false);
|
|
||||||
|
|
||||||
// Получаем адрес DLE из URL
|
|
||||||
const dleAddress = computed(() => route.query.address);
|
|
||||||
|
|
||||||
// Загрузка данных DLE
|
|
||||||
const loadDleData = async () => {
|
|
||||||
if (!dleAddress.value) return;
|
|
||||||
|
|
||||||
try {
|
|
||||||
isLoadingDle.value = true;
|
|
||||||
const response = await api.post('/blockchain/read-dle-info', {
|
|
||||||
dleAddress: dleAddress.value
|
|
||||||
});
|
|
||||||
|
|
||||||
if (response.data.success) {
|
|
||||||
selectedDle.value = response.data.data;
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
console.error('Ошибка загрузки данных DLE:', error);
|
|
||||||
} finally {
|
|
||||||
isLoadingDle.value = false;
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
// Загружаем данные при монтировании
|
|
||||||
onMounted(() => {
|
|
||||||
loadDleData();
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.module-deploy-page {
|
|
||||||
padding: 20px;
|
|
||||||
max-width: 1200px;
|
|
||||||
margin: 0 auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: flex-start;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
padding-bottom: 20px;
|
|
||||||
border-bottom: 2px solid #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-content h1 {
|
|
||||||
color: var(--color-primary);
|
|
||||||
font-size: 2.5rem;
|
|
||||||
margin: 0 0 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.header-content p {
|
|
||||||
color: var(--color-grey-dark);
|
|
||||||
font-size: 1.1rem;
|
|
||||||
margin: 0 0 15px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dle-info {
|
|
||||||
display: flex;
|
|
||||||
gap: 15px;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dle-name {
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.dle-address {
|
|
||||||
font-family: monospace;
|
|
||||||
color: var(--color-grey-dark);
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-btn {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
font-size: 2rem;
|
|
||||||
color: var(--color-grey-dark);
|
|
||||||
cursor: pointer;
|
|
||||||
padding: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-btn:hover {
|
|
||||||
color: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.module-description,
|
|
||||||
.module-architecture,
|
|
||||||
.oracle-types,
|
|
||||||
.usage-examples,
|
|
||||||
.development-status {
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.description-card,
|
|
||||||
.architecture-card,
|
|
||||||
.types-card,
|
|
||||||
.examples-card,
|
|
||||||
.status-card {
|
|
||||||
background: #f8f9fa;
|
|
||||||
padding: 25px;
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
border: 1px solid #e9ecef;
|
|
||||||
}
|
|
||||||
|
|
||||||
.description-card h3,
|
|
||||||
.architecture-card h3,
|
|
||||||
.types-card h3,
|
|
||||||
.examples-card h3,
|
|
||||||
.status-card h3 {
|
|
||||||
color: var(--color-primary);
|
|
||||||
margin: 0 0 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.description-content h4 {
|
|
||||||
color: var(--color-grey-dark);
|
|
||||||
margin: 20px 0 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.description-content ul {
|
|
||||||
margin: 10px 0;
|
|
||||||
padding-left: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.description-content li {
|
|
||||||
margin: 5px 0;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Архитектурная диаграмма */
|
|
||||||
.architecture-diagram {
|
|
||||||
margin: 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.diagram-row {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: space-between;
|
|
||||||
gap: 20px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.diagram-item {
|
|
||||||
flex: 1;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
text-align: center;
|
|
||||||
min-height: 150px;
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.diagram-item.external {
|
|
||||||
background: #e3f2fd;
|
|
||||||
border: 2px solid #2196f3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.diagram-item.oracle {
|
|
||||||
background: #f3e5f5;
|
|
||||||
border: 2px solid #9c27b0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.diagram-item.dle {
|
|
||||||
background: #e8f5e8;
|
|
||||||
border: 2px solid #4caf50;
|
|
||||||
}
|
|
||||||
|
|
||||||
.diagram-item h5 {
|
|
||||||
margin: 0 0 15px 0;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.diagram-item ul {
|
|
||||||
margin: 0;
|
|
||||||
padding: 0;
|
|
||||||
list-style: none;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.diagram-item li {
|
|
||||||
margin: 5px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.diagram-arrow {
|
|
||||||
font-size: 2rem;
|
|
||||||
color: var(--color-primary);
|
|
||||||
font-weight: bold;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Типы оракулов */
|
|
||||||
.types-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
|
|
||||||
gap: 20px;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.type-item {
|
|
||||||
background: white;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
border: 1px solid #e9ecef;
|
|
||||||
}
|
|
||||||
|
|
||||||
.type-item h4 {
|
|
||||||
color: var(--color-primary);
|
|
||||||
margin: 0 0 10px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.type-item p {
|
|
||||||
color: var(--color-grey-dark);
|
|
||||||
margin: 0 0 15px 0;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.type-item ul {
|
|
||||||
margin: 0;
|
|
||||||
padding-left: 20px;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.type-item li {
|
|
||||||
margin: 5px 0;
|
|
||||||
color: var(--color-grey-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Примеры использования */
|
|
||||||
.examples-content {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.example-item {
|
|
||||||
background: white;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
border: 1px solid #e9ecef;
|
|
||||||
}
|
|
||||||
|
|
||||||
.example-item h4 {
|
|
||||||
color: var(--color-primary);
|
|
||||||
margin: 0 0 15px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.example-code {
|
|
||||||
background: #f8f9fa;
|
|
||||||
border: 1px solid #e9ecef;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
padding: 15px;
|
|
||||||
overflow-x: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.example-code pre {
|
|
||||||
margin: 0;
|
|
||||||
font-family: 'Courier New', monospace;
|
|
||||||
font-size: 0.9rem;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.example-code code {
|
|
||||||
background: none;
|
|
||||||
padding: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Статус разработки */
|
|
||||||
.status-content {
|
|
||||||
background: white;
|
|
||||||
padding: 20px;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
border: 1px solid #e9ecef;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-content p {
|
|
||||||
margin: 0 0 15px 0;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-content ul {
|
|
||||||
margin: 15px 0;
|
|
||||||
padding-left: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-content li {
|
|
||||||
margin: 5px 0;
|
|
||||||
color: var(--color-grey-dark);
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-content em {
|
|
||||||
color: var(--color-primary);
|
|
||||||
font-style: italic;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.diagram-row {
|
|
||||||
flex-direction: column;
|
|
||||||
gap: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.diagram-arrow {
|
|
||||||
transform: rotate(90deg);
|
|
||||||
}
|
|
||||||
|
|
||||||
.types-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dle-info {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 5px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,673 +0,0 @@
|
|||||||
<!--
|
|
||||||
Copyright (c) 2024-2025 Тарабанов Александр Викторович
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
This software is proprietary and confidential.
|
|
||||||
Unauthorized copying, modification, or distribution is prohibited.
|
|
||||||
|
|
||||||
For licensing inquiries: info@hb3-accelerator.com
|
|
||||||
Website: https://hb3-accelerator.com
|
|
||||||
GitHub: https://github.com/HB3-ACCELERATOR
|
|
||||||
-->
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<BaseLayout
|
|
||||||
:is-authenticated="isAuthenticated"
|
|
||||||
:identities="identities"
|
|
||||||
:token-balances="tokenBalances"
|
|
||||||
:is-loading-tokens="isLoadingTokens"
|
|
||||||
@auth-action-completed="$emit('auth-action-completed')"
|
|
||||||
>
|
|
||||||
<div class="timelock-module-deploy">
|
|
||||||
<!-- Заголовок -->
|
|
||||||
<div class="page-header">
|
|
||||||
<div class="header-content">
|
|
||||||
<h1>Деплой TimelockModule</h1>
|
|
||||||
<p>Задержки исполнения - безопасность критических операций через таймлоки</p>
|
|
||||||
<p v-if="dleAddress" class="dle-address">
|
|
||||||
<strong>DLE:</strong> {{ dleAddress }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<button class="close-btn" @click="router.push('/management/modules')">×</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Информация о модуле -->
|
|
||||||
<div class="module-info">
|
|
||||||
<div class="info-card">
|
|
||||||
<h3>⏰ TimelockModule</h3>
|
|
||||||
<div class="info-grid">
|
|
||||||
<div class="info-item">
|
|
||||||
<strong>Назначение:</strong> Безопасность критических операций
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<strong>Функции:</strong> Настраиваемые таймлоки, отмена предложений, аудит
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<strong>Безопасность:</strong> Задержки исполнения для защиты от атак
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Форма деплоя модуля администратором -->
|
|
||||||
<div v-if="canManageSettings" class="deploy-form">
|
|
||||||
<div class="form-header">
|
|
||||||
<h3>🔧 Деплой TimelockModule администратором</h3>
|
|
||||||
<p>Администратор деплоит модуль, затем создает предложение для добавления в DLE</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-content">
|
|
||||||
<!-- Информация о сетях -->
|
|
||||||
<div class="networks-info">
|
|
||||||
<h4>📡 Сети для деплоя:</h4>
|
|
||||||
<div class="networks-list">
|
|
||||||
<div class="network-item">
|
|
||||||
<span class="network-name">Sepolia</span>
|
|
||||||
<span class="network-chain-id">Chain ID: 11155111</span>
|
|
||||||
</div>
|
|
||||||
<div class="network-item">
|
|
||||||
<span class="network-name">Holesky</span>
|
|
||||||
<span class="network-chain-id">Chain ID: 17000</span>
|
|
||||||
</div>
|
|
||||||
<div class="network-item">
|
|
||||||
<span class="network-name">Arbitrum Sepolia</span>
|
|
||||||
<span class="network-chain-id">Chain ID: 421614</span>
|
|
||||||
</div>
|
|
||||||
<div class="network-item">
|
|
||||||
<span class="network-name">Base Sepolia</span>
|
|
||||||
<span class="network-chain-id">Chain ID: 84532</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Настройки модуля -->
|
|
||||||
<div class="module-settings">
|
|
||||||
<h4>⚙️ Настройки TimelockModule:</h4>
|
|
||||||
|
|
||||||
<div class="settings-form">
|
|
||||||
<!-- Поля администратора -->
|
|
||||||
<div class="admin-section">
|
|
||||||
<h5>🔐 Настройки администратора:</h5>
|
|
||||||
|
|
||||||
<div class="form-row">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="adminPrivateKey">Приватный ключ администратора:</label>
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
id="adminPrivateKey"
|
|
||||||
v-model="moduleSettings.adminPrivateKey"
|
|
||||||
class="form-control"
|
|
||||||
placeholder="0x..."
|
|
||||||
required
|
|
||||||
>
|
|
||||||
<small class="form-help">Приватный ключ для деплоя модуля (администратор платит газ)</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="etherscanApiKey">Etherscan API ключ:</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="etherscanApiKey"
|
|
||||||
v-model="moduleSettings.etherscanApiKey"
|
|
||||||
class="form-control"
|
|
||||||
placeholder="YourAPIKey..."
|
|
||||||
>
|
|
||||||
<small class="form-help">API ключ для автоматической верификации контрактов</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="simple-info">
|
|
||||||
<h5>📋 Информация о TimelockModule:</h5>
|
|
||||||
<div class="info-text">
|
|
||||||
<p><strong>TimelockModule</strong> будет задеплоен с настройками по умолчанию:</p>
|
|
||||||
<ul>
|
|
||||||
<li>✅ Стандартная задержка: 2 дня</li>
|
|
||||||
<li>✅ Экстренная задержка: 30 минут</li>
|
|
||||||
<li>✅ Автоматическое исполнение операций</li>
|
|
||||||
<li>✅ Готовые настройки безопасности</li>
|
|
||||||
</ul>
|
|
||||||
<p><em>После деплоя настройки можно будет изменить через governance.</em></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Кнопка деплоя -->
|
|
||||||
<div class="deploy-actions">
|
|
||||||
<button
|
|
||||||
class="btn btn-primary btn-large deploy-module"
|
|
||||||
@click="deployTimelockModule"
|
|
||||||
:disabled="isDeploying || !dleAddress || !isFormValid"
|
|
||||||
>
|
|
||||||
<i class="fas fa-rocket" :class="{ 'fa-spin': isDeploying }"></i>
|
|
||||||
{{ isDeploying ? 'Деплой модуля...' : 'Деплой TimelockModule' }}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div v-if="!isFormValid && !isDeploying" class="form-validation-info">
|
|
||||||
<i class="fas fa-exclamation-triangle"></i>
|
|
||||||
<span>Заполните приватный ключ и API ключ для деплоя</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="deploymentProgress" class="deployment-progress">
|
|
||||||
<div class="progress-info">
|
|
||||||
<span>{{ deploymentProgress.message }}</span>
|
|
||||||
<span class="progress-percentage">{{ deploymentProgress.percentage }}%</span>
|
|
||||||
</div>
|
|
||||||
<div class="progress-bar">
|
|
||||||
<div class="progress-fill" :style="{ width: deploymentProgress.percentage + '%' }"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Сообщение для пользователей без прав доступа -->
|
|
||||||
<div v-if="!canManageSettings" class="no-access-message">
|
|
||||||
<div class="message-content">
|
|
||||||
<h3>🔒 Нет прав доступа</h3>
|
|
||||||
<p>У вас нет прав для деплоя смарт-контрактов. Только пользователи с ролью Editor могут выполнять деплой.</p>
|
|
||||||
<button class="btn btn-secondary" @click="router.push('/management/modules')">
|
|
||||||
← Вернуться к модулям
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</BaseLayout>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { defineProps, defineEmits, ref, computed, onMounted } from 'vue';
|
|
||||||
import { useRouter, useRoute } from 'vue-router';
|
|
||||||
import BaseLayout from '../../../components/BaseLayout.vue';
|
|
||||||
import { usePermissions } from '@/composables/usePermissions';
|
|
||||||
|
|
||||||
// Определяем props
|
|
||||||
const props = defineProps({
|
|
||||||
isAuthenticated: { type: Boolean, default: false },
|
|
||||||
identities: { type: Array, default: () => [] },
|
|
||||||
tokenBalances: { type: Object, default: () => ({}) },
|
|
||||||
isLoadingTokens: { type: Boolean, default: false }
|
|
||||||
});
|
|
||||||
|
|
||||||
// Определяем emits
|
|
||||||
const emit = defineEmits(['auth-action-completed']);
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
const route = useRoute();
|
|
||||||
const { canEdit, canManageSettings } = usePermissions();
|
|
||||||
|
|
||||||
// Состояние
|
|
||||||
const isLoading = ref(false);
|
|
||||||
const dleAddress = ref(route.query.address || null);
|
|
||||||
const isDeploying = ref(false);
|
|
||||||
const deploymentProgress = ref(null);
|
|
||||||
|
|
||||||
// Настройки модуля
|
|
||||||
const moduleSettings = ref({
|
|
||||||
// Поля администратора
|
|
||||||
adminPrivateKey: '',
|
|
||||||
etherscanApiKey: ''
|
|
||||||
});
|
|
||||||
|
|
||||||
// Проверка валидности формы
|
|
||||||
const isFormValid = computed(() => {
|
|
||||||
return moduleSettings.value.adminPrivateKey && moduleSettings.value.etherscanApiKey;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Функция деплоя TimelockModule
|
|
||||||
async function deployTimelockModule() {
|
|
||||||
if (!canManageSettings.value) {
|
|
||||||
alert('У вас нет прав для деплоя смарт-контрактов');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
isDeploying.value = true;
|
|
||||||
deploymentProgress.value = {
|
|
||||||
message: 'Инициализация деплоя...',
|
|
||||||
percentage: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log('[TimelockModuleDeployView] Начинаем деплой TimelockModule для DLE:', dleAddress.value);
|
|
||||||
|
|
||||||
// Вызываем API для деплоя модуля администратором
|
|
||||||
const response = await fetch('/api/dle-modules/deploy-timelock-admin', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
dleAddress: dleAddress.value,
|
|
||||||
moduleType: 'timelock',
|
|
||||||
adminPrivateKey: moduleSettings.value.adminPrivateKey,
|
|
||||||
etherscanApiKey: moduleSettings.value.etherscanApiKey,
|
|
||||||
settings: {
|
|
||||||
// Используем настройки по умолчанию
|
|
||||||
useDefaultSettings: true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await response.json();
|
|
||||||
|
|
||||||
if (result.success) {
|
|
||||||
console.log('[TimelockModuleDeployView] Деплой успешно запущен:', result);
|
|
||||||
|
|
||||||
// Обновляем прогресс
|
|
||||||
deploymentProgress.value = {
|
|
||||||
message: 'Деплой запущен успешно! Проверьте логи для отслеживания прогресса.',
|
|
||||||
percentage: 100
|
|
||||||
};
|
|
||||||
|
|
||||||
// Показываем детальную информацию о деплое
|
|
||||||
const deployInfo = result.data || {};
|
|
||||||
const deployedAddresses = deployInfo.addresses || [];
|
|
||||||
|
|
||||||
let successMessage = '✅ TimelockModule успешно задеплоен!\n\n';
|
|
||||||
successMessage += `📊 Детали деплоя:\n`;
|
|
||||||
successMessage += `• DLE: ${dleAddress.value}\n`;
|
|
||||||
successMessage += `• Тип модуля: TimelockModule\n`;
|
|
||||||
successMessage += `• Адрес модуля: ${deployInfo.moduleAddress || 'Не указан'}\n`;
|
|
||||||
|
|
||||||
if (deployedAddresses.length > 0) {
|
|
||||||
successMessage += `\n🌐 Задеплоенные адреса:\n`;
|
|
||||||
deployedAddresses.forEach((addr, index) => {
|
|
||||||
successMessage += `${index + 1}. ${addr.network}: ${addr.address}\n`;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
successMessage += `\n📝 Следующий шаг: Создайте предложение для добавления модуля в DLE через governance.`;
|
|
||||||
|
|
||||||
alert(successMessage);
|
|
||||||
|
|
||||||
// Перенаправляем обратно к модулям
|
|
||||||
setTimeout(() => {
|
|
||||||
router.push(`/management/modules?address=${dleAddress.value}`);
|
|
||||||
}, 3000);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
throw new Error(result.error || 'Неизвестная ошибка');
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[TimelockModuleDeployView] Ошибка деплоя:', error);
|
|
||||||
alert('❌ Ошибка деплоя: ' + error.message);
|
|
||||||
|
|
||||||
deploymentProgress.value = {
|
|
||||||
message: 'Ошибка деплоя: ' + error.message,
|
|
||||||
percentage: 0
|
|
||||||
};
|
|
||||||
} finally {
|
|
||||||
isDeploying.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Инициализация
|
|
||||||
onMounted(() => {
|
|
||||||
console.log('[TimelockModuleDeployView] Страница загружена');
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.timelock-module-deploy {
|
|
||||||
padding: 20px;
|
|
||||||
background-color: var(--color-white);
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
|
||||||
margin-top: 20px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
padding-bottom: 20px;
|
|
||||||
border-bottom: 2px solid #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header h1 {
|
|
||||||
color: var(--color-primary);
|
|
||||||
font-size: 2rem;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header p {
|
|
||||||
margin: 10px 0 0 0;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dle-address {
|
|
||||||
margin-top: 10px !important;
|
|
||||||
font-family: monospace;
|
|
||||||
background: #f8f9fa;
|
|
||||||
padding: 8px 12px;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
border: 1px solid #dee2e6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-btn {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
cursor: pointer;
|
|
||||||
color: #666;
|
|
||||||
padding: 0;
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 50%;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-btn:hover {
|
|
||||||
background: #f0f0f0;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Форма деплоя */
|
|
||||||
.deploy-form {
|
|
||||||
background: #f8f9fa;
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
padding: 20px;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
border: 1px solid #e9ecef;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-header h3 {
|
|
||||||
margin: 0 0 10px 0;
|
|
||||||
color: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-header p {
|
|
||||||
margin: 0 0 20px 0;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.networks-info,
|
|
||||||
.module-settings {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
padding: 15px;
|
|
||||||
background: white;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
border: 1px solid #dee2e6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-form {
|
|
||||||
margin-top: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-row {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: 15px;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-control {
|
|
||||||
width: 100%;
|
|
||||||
padding: 10px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
font-size: 14px;
|
|
||||||
transition: border-color 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-control:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: var(--color-primary);
|
|
||||||
box-shadow: 0 0 0 2px rgba(var(--color-primary-rgb), 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-help {
|
|
||||||
display: block;
|
|
||||||
margin-top: 5px;
|
|
||||||
font-size: 12px;
|
|
||||||
color: #666;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Секция администратора */
|
|
||||||
.admin-section {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
padding: 20px;
|
|
||||||
background: #fff3cd;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
border: 1px solid #ffeaa7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.admin-section h5 {
|
|
||||||
margin: 0 0 15px 0;
|
|
||||||
color: #856404;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Дополнительные настройки */
|
|
||||||
.advanced-settings {
|
|
||||||
margin-top: 20px;
|
|
||||||
padding: 20px;
|
|
||||||
background: #f8f9fa;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
border: 1px solid #e9ecef;
|
|
||||||
}
|
|
||||||
|
|
||||||
.advanced-settings h5 {
|
|
||||||
margin: 0 0 15px 0;
|
|
||||||
color: var(--color-primary);
|
|
||||||
font-size: 1.1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.advanced-settings .form-row {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.advanced-settings .form-group {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.advanced-settings .form-group:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.deploy-actions {
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
padding: 12px 24px;
|
|
||||||
border: none;
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 500;
|
|
||||||
transition: all 0.3s;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary {
|
|
||||||
background: linear-gradient(135deg, var(--color-primary), var(--color-primary-dark));
|
|
||||||
color: white;
|
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary:hover:not(:disabled) {
|
|
||||||
background: linear-gradient(135deg, var(--color-primary-dark), var(--color-primary));
|
|
||||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
|
||||||
transform: translateY(-1px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary:disabled {
|
|
||||||
opacity: 0.6;
|
|
||||||
cursor: not-allowed;
|
|
||||||
transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-large {
|
|
||||||
padding: 16px 32px;
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.deployment-progress {
|
|
||||||
margin-top: 20px;
|
|
||||||
padding: 15px;
|
|
||||||
background: white;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
border: 1px solid #dee2e6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-info {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-percentage {
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-bar {
|
|
||||||
width: 100%;
|
|
||||||
height: 8px;
|
|
||||||
background: #f0f0f0;
|
|
||||||
border-radius: 4px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-fill {
|
|
||||||
height: 100%;
|
|
||||||
background: linear-gradient(90deg, var(--color-primary), var(--color-primary-dark));
|
|
||||||
transition: width 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Информация о модуле */
|
|
||||||
.module-info {
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-card {
|
|
||||||
background: #f8f9fa;
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
padding: 20px;
|
|
||||||
border: 1px solid #e9ecef;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-card h3 {
|
|
||||||
margin: 0 0 15px 0;
|
|
||||||
color: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
||||||
gap: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-item {
|
|
||||||
padding: 10px;
|
|
||||||
background: white;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
border: 1px solid #dee2e6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-item strong {
|
|
||||||
color: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Плейсхолдер для формы */
|
|
||||||
.deploy-form-placeholder {
|
|
||||||
background: #f8f9fa;
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
padding: 40px;
|
|
||||||
text-align: center;
|
|
||||||
border: 2px dashed #dee2e6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder-content h3 {
|
|
||||||
color: var(--color-primary);
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder-content p {
|
|
||||||
color: #666;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Сообщение об отсутствии прав доступа */
|
|
||||||
.no-access-message {
|
|
||||||
background: #fff3cd;
|
|
||||||
border: 1px solid #ffeaa7;
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
padding: 30px;
|
|
||||||
margin: 20px 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-content h3 {
|
|
||||||
color: #856404;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
font-size: 1.4em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-content p {
|
|
||||||
color: #856404;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
font-size: 1.1em;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-content .btn {
|
|
||||||
background: #6c757d;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
padding: 12px 24px;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-content .btn:hover {
|
|
||||||
background: #5a6268;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Адаптивность */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.info-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 15px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@@ -1,886 +0,0 @@
|
|||||||
<!--
|
|
||||||
Copyright (c) 2024-2025 Тарабанов Александр Викторович
|
|
||||||
All rights reserved.
|
|
||||||
|
|
||||||
This software is proprietary and confidential.
|
|
||||||
Unauthorized copying, modification, or distribution is prohibited.
|
|
||||||
|
|
||||||
For licensing inquiries: info@hb3-accelerator.com
|
|
||||||
Website: https://hb3-accelerator.com
|
|
||||||
GitHub: https://github.com/HB3-ACCELERATOR
|
|
||||||
-->
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<BaseLayout
|
|
||||||
:is-authenticated="isAuthenticated"
|
|
||||||
:identities="identities"
|
|
||||||
:token-balances="tokenBalances"
|
|
||||||
:is-loading-tokens="isLoadingTokens"
|
|
||||||
@auth-action-completed="$emit('auth-action-completed')"
|
|
||||||
>
|
|
||||||
<div class="treasury-module-deploy">
|
|
||||||
<!-- Заголовок -->
|
|
||||||
<div class="page-header">
|
|
||||||
<div class="header-content">
|
|
||||||
<h1>Деплой TreasuryModule</h1>
|
|
||||||
<p>Казначейство DLE - управление финансами, депозиты, выводы, дивиденды</p>
|
|
||||||
<p v-if="dleAddress" class="dle-address">
|
|
||||||
<strong>DLE:</strong> {{ dleAddress }}
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<button class="close-btn" @click="router.push('/management/modules')">×</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Информация о модуле -->
|
|
||||||
<div class="module-info">
|
|
||||||
<div class="info-card">
|
|
||||||
<h3>💰 TreasuryModule</h3>
|
|
||||||
<div class="info-grid">
|
|
||||||
<div class="info-item">
|
|
||||||
<strong>Назначение:</strong> Управление финансами DLE
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<strong>Функции:</strong> Депозиты, выводы, дивиденды, бюджетирование
|
|
||||||
</div>
|
|
||||||
<div class="info-item">
|
|
||||||
<strong>Безопасность:</strong> Все операции через голосование
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Форма деплоя модуля администратором -->
|
|
||||||
<div v-if="canManageSettings" class="deploy-form">
|
|
||||||
<div class="form-header">
|
|
||||||
<h3>🌐 Деплой TreasuryModule во всех сетях</h3>
|
|
||||||
<p>Администратор деплоит модуль во всех 4 сетях одновременно, затем создает предложение для добавления в DLE</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-content">
|
|
||||||
<!-- Информация о сетях -->
|
|
||||||
<div class="networks-info">
|
|
||||||
<h4>📡 Сети для деплоя:</h4>
|
|
||||||
<div class="networks-list">
|
|
||||||
<div class="network-item">
|
|
||||||
<span class="network-name">Sepolia</span>
|
|
||||||
<span class="network-chain-id">Chain ID: 11155111</span>
|
|
||||||
</div>
|
|
||||||
<div class="network-item">
|
|
||||||
<span class="network-name">Holesky</span>
|
|
||||||
<span class="network-chain-id">Chain ID: 17000</span>
|
|
||||||
</div>
|
|
||||||
<div class="network-item">
|
|
||||||
<span class="network-name">Arbitrum Sepolia</span>
|
|
||||||
<span class="network-chain-id">Chain ID: 421614</span>
|
|
||||||
</div>
|
|
||||||
<div class="network-item">
|
|
||||||
<span class="network-name">Base Sepolia</span>
|
|
||||||
<span class="network-chain-id">Chain ID: 84532</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Настройки модуля -->
|
|
||||||
<div class="module-settings">
|
|
||||||
<h4>⚙️ Настройки TreasuryModule:</h4>
|
|
||||||
|
|
||||||
<div class="settings-form">
|
|
||||||
<!-- Поля администратора -->
|
|
||||||
<div class="admin-section">
|
|
||||||
<h5>🔐 Настройки администратора:</h5>
|
|
||||||
|
|
||||||
<div class="form-row">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="adminPrivateKey">Приватный ключ администратора:</label>
|
|
||||||
<input
|
|
||||||
type="password"
|
|
||||||
id="adminPrivateKey"
|
|
||||||
v-model="moduleSettings.adminPrivateKey"
|
|
||||||
class="form-control"
|
|
||||||
placeholder="0x..."
|
|
||||||
required
|
|
||||||
>
|
|
||||||
<small class="form-help">Приватный ключ для деплоя модуля (администратор платит газ)</small>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="etherscanApiKey">Etherscan API ключ:</label>
|
|
||||||
<input
|
|
||||||
type="text"
|
|
||||||
id="etherscanApiKey"
|
|
||||||
v-model="moduleSettings.etherscanApiKey"
|
|
||||||
class="form-control"
|
|
||||||
placeholder="YourAPIKey..."
|
|
||||||
>
|
|
||||||
<small class="form-help">API ключ для автоматической верификации контрактов</small>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="simple-info">
|
|
||||||
<h5>📋 Информация о TreasuryModule:</h5>
|
|
||||||
<div class="info-text">
|
|
||||||
<p><strong>TreasuryModule</strong> будет задеплоен с настройками по умолчанию:</p>
|
|
||||||
<ul>
|
|
||||||
<li>✅ Поддержка ETH и основных ERC20 токенов</li>
|
|
||||||
<li>✅ Стандартные задержки для безопасности</li>
|
|
||||||
<li>✅ Автоматическая настройка для всех сетей</li>
|
|
||||||
<li>✅ Готовые настройки безопасности</li>
|
|
||||||
</ul>
|
|
||||||
<p><em>После деплоя настройки можно будет изменить через governance.</em></p>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Кнопка деплоя -->
|
|
||||||
<div class="deploy-actions">
|
|
||||||
<button
|
|
||||||
class="btn btn-primary btn-large deploy-module"
|
|
||||||
@click="deployTreasuryModule"
|
|
||||||
:disabled="isDeploying || !dleAddress || !isFormValid"
|
|
||||||
>
|
|
||||||
<i class="fas fa-rocket" :class="{ 'fa-spin': isDeploying }"></i>
|
|
||||||
{{ isDeploying ? 'Деплой во всех сетях...' : 'Деплой TreasuryModule во всех сетях' }}
|
|
||||||
</button>
|
|
||||||
|
|
||||||
<div v-if="!isFormValid && !isDeploying" class="form-validation-info">
|
|
||||||
<i class="fas fa-exclamation-triangle"></i>
|
|
||||||
<span>Заполните приватный ключ и API ключ для деплоя</span>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div v-if="deploymentProgress" class="deployment-progress">
|
|
||||||
<div class="progress-info">
|
|
||||||
<span>{{ deploymentProgress.message }}</span>
|
|
||||||
<span class="progress-percentage">{{ deploymentProgress.percentage }}%</span>
|
|
||||||
</div>
|
|
||||||
<div class="progress-bar">
|
|
||||||
<div class="progress-fill" :style="{ width: deploymentProgress.percentage + '%' }"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Сообщение для пользователей без прав доступа -->
|
|
||||||
<div v-if="!canManageSettings" class="no-access-message">
|
|
||||||
<div class="message-content">
|
|
||||||
<h3>🔒 Нет прав доступа</h3>
|
|
||||||
<p>У вас нет прав для деплоя смарт-контрактов. Только пользователи с ролью Editor могут выполнять деплой.</p>
|
|
||||||
<button class="btn btn-secondary" @click="router.push('/management/modules')">
|
|
||||||
← Вернуться к модулям
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
</div>
|
|
||||||
</BaseLayout>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup>
|
|
||||||
import { defineProps, defineEmits, ref, computed, onMounted } from 'vue';
|
|
||||||
import { useRouter, useRoute } from 'vue-router';
|
|
||||||
import BaseLayout from '../../../components/BaseLayout.vue';
|
|
||||||
import { usePermissions } from '@/composables/usePermissions';
|
|
||||||
|
|
||||||
// Определяем props
|
|
||||||
const props = defineProps({
|
|
||||||
isAuthenticated: { type: Boolean, default: false },
|
|
||||||
identities: { type: Array, default: () => [] },
|
|
||||||
tokenBalances: { type: Object, default: () => ({}) },
|
|
||||||
isLoadingTokens: { type: Boolean, default: false }
|
|
||||||
});
|
|
||||||
|
|
||||||
// Определяем emits
|
|
||||||
const emit = defineEmits(['auth-action-completed']);
|
|
||||||
|
|
||||||
const router = useRouter();
|
|
||||||
const route = useRoute();
|
|
||||||
const { canEdit, canManageSettings } = usePermissions();
|
|
||||||
|
|
||||||
// Состояние
|
|
||||||
const isLoading = ref(false);
|
|
||||||
const dleAddress = ref(route.query.address || null);
|
|
||||||
const isDeploying = ref(false);
|
|
||||||
const deploymentProgress = ref(null);
|
|
||||||
|
|
||||||
// Настройки модуля
|
|
||||||
const moduleSettings = ref({
|
|
||||||
// Поля администратора
|
|
||||||
adminPrivateKey: '',
|
|
||||||
etherscanApiKey: ''
|
|
||||||
});
|
|
||||||
|
|
||||||
// Проверка валидности формы
|
|
||||||
const isFormValid = computed(() => {
|
|
||||||
return moduleSettings.value.adminPrivateKey && moduleSettings.value.etherscanApiKey;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Функция деплоя TreasuryModule
|
|
||||||
async function deployTreasuryModule() {
|
|
||||||
if (!canManageSettings.value) {
|
|
||||||
alert('У вас нет прав для деплоя смарт-контрактов');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
isDeploying.value = true;
|
|
||||||
deploymentProgress.value = {
|
|
||||||
message: 'Инициализация деплоя...',
|
|
||||||
percentage: 0
|
|
||||||
};
|
|
||||||
|
|
||||||
console.log('[TreasuryModuleDeployView] Начинаем деплой TreasuryModule для DLE:', dleAddress.value);
|
|
||||||
|
|
||||||
// Вызываем API для деплоя модуля администратором
|
|
||||||
const response = await fetch('/api/dle-modules/deploy-treasury-admin', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: {
|
|
||||||
'Content-Type': 'application/json',
|
|
||||||
},
|
|
||||||
body: JSON.stringify({
|
|
||||||
dleAddress: dleAddress.value,
|
|
||||||
moduleType: 'treasury',
|
|
||||||
adminPrivateKey: moduleSettings.value.adminPrivateKey,
|
|
||||||
etherscanApiKey: moduleSettings.value.etherscanApiKey,
|
|
||||||
settings: {
|
|
||||||
// Используем настройки по умолчанию
|
|
||||||
useDefaultSettings: true
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
|
|
||||||
if (!response.ok) {
|
|
||||||
throw new Error(`HTTP error! status: ${response.status}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const result = await response.json();
|
|
||||||
|
|
||||||
if (result.success) {
|
|
||||||
console.log('[TreasuryModuleDeployView] Деплой успешно запущен:', result);
|
|
||||||
|
|
||||||
// Обновляем прогресс
|
|
||||||
deploymentProgress.value = {
|
|
||||||
message: 'Деплой запущен успешно! Проверьте логи для отслеживания прогресса.',
|
|
||||||
percentage: 100
|
|
||||||
};
|
|
||||||
|
|
||||||
// Показываем детальную информацию о деплое
|
|
||||||
const deployInfo = result.data || {};
|
|
||||||
const deployedAddresses = deployInfo.addresses || [];
|
|
||||||
|
|
||||||
let successMessage = '✅ TreasuryModule успешно задеплоен во всех сетях!\n\n';
|
|
||||||
successMessage += `📊 Детали деплоя:\n`;
|
|
||||||
successMessage += `• DLE: ${dleAddress.value}\n`;
|
|
||||||
successMessage += `• Тип модуля: TreasuryModule\n`;
|
|
||||||
successMessage += `• Сети: Sepolia, Holesky, Arbitrum Sepolia, Base Sepolia\n`;
|
|
||||||
|
|
||||||
if (deployedAddresses.length > 0) {
|
|
||||||
successMessage += `\n🌐 Задеплоенные адреса:\n`;
|
|
||||||
deployedAddresses.forEach((addr, index) => {
|
|
||||||
successMessage += `${index + 1}. ${addr.network}: ${addr.address}\n`;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
successMessage += `\n📝 Следующий шаг: Создайте предложение для добавления модуля в DLE через governance.`;
|
|
||||||
|
|
||||||
alert(successMessage);
|
|
||||||
|
|
||||||
// Перенаправляем обратно к модулям
|
|
||||||
setTimeout(() => {
|
|
||||||
router.push(`/management/modules?address=${dleAddress.value}`);
|
|
||||||
}, 3000);
|
|
||||||
|
|
||||||
} else {
|
|
||||||
throw new Error(result.error || 'Неизвестная ошибка');
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (error) {
|
|
||||||
console.error('[TreasuryModuleDeployView] Ошибка деплоя:', error);
|
|
||||||
alert('❌ Ошибка деплоя: ' + error.message);
|
|
||||||
|
|
||||||
deploymentProgress.value = {
|
|
||||||
message: 'Ошибка деплоя: ' + error.message,
|
|
||||||
percentage: 0
|
|
||||||
};
|
|
||||||
} finally {
|
|
||||||
isDeploying.value = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Инициализация
|
|
||||||
onMounted(() => {
|
|
||||||
console.log('[TreasuryModuleDeployView] Страница загружена');
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.treasury-module-deploy {
|
|
||||||
padding: 20px;
|
|
||||||
background-color: var(--color-white);
|
|
||||||
border-radius: var(--radius-lg);
|
|
||||||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
|
||||||
margin-top: 20px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
padding-bottom: 20px;
|
|
||||||
border-bottom: 2px solid #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header h1 {
|
|
||||||
color: var(--color-primary);
|
|
||||||
font-size: 2rem;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header p {
|
|
||||||
margin: 10px 0 0 0;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.dle-address {
|
|
||||||
margin-top: 10px !important;
|
|
||||||
font-family: monospace;
|
|
||||||
background: #f8f9fa;
|
|
||||||
padding: 8px 12px;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
border: 1px solid #dee2e6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-btn {
|
|
||||||
background: none;
|
|
||||||
border: none;
|
|
||||||
font-size: 1.5rem;
|
|
||||||
cursor: pointer;
|
|
||||||
color: #666;
|
|
||||||
padding: 0;
|
|
||||||
width: 30px;
|
|
||||||
height: 30px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 50%;
|
|
||||||
transition: all 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.close-btn:hover {
|
|
||||||
background: #f0f0f0;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Форма деплоя */
|
|
||||||
.deploy-form {
|
|
||||||
background: #f8f9fa;
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
padding: 20px;
|
|
||||||
margin-bottom: 30px;
|
|
||||||
border: 1px solid #e9ecef;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-header h3 {
|
|
||||||
margin: 0 0 10px 0;
|
|
||||||
color: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-header p {
|
|
||||||
margin: 0 0 20px 0;
|
|
||||||
color: #666;
|
|
||||||
}
|
|
||||||
|
|
||||||
.networks-info,
|
|
||||||
.module-settings {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
padding: 15px;
|
|
||||||
background: white;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
border: 1px solid #dee2e6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.settings-form {
|
|
||||||
margin-top: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-row {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: 1fr 1fr;
|
|
||||||
gap: 15px;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-group label {
|
|
||||||
display: block;
|
|
||||||
margin-bottom: 5px;
|
|
||||||
font-weight: 500;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-control {
|
|
||||||
width: 100%;
|
|
||||||
padding: 10px;
|
|
||||||
border: 1px solid #ddd;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
font-size: 14px;
|
|
||||||
transition: border-color 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-control:focus {
|
|
||||||
outline: none;
|
|
||||||
border-color: var(--color-primary);
|
|
||||||
box-shadow: 0 0 0 2px rgba(var(--color-primary-rgb), 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-help {
|
|
||||||
display: block;
|
|
||||||
margin-top: 5px;
|
|
||||||
font-size: 12px;
|
|
||||||
color: #666;
|
|
||||||
line-height: 1.4;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Секция администратора */
|
|
||||||
.admin-section {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
padding: 20px;
|
|
||||||
background: #fff3cd;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
border: 1px solid #ffeaa7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.admin-section h5 {
|
|
||||||
margin: 0 0 15px 0;
|
|
||||||
color: #856404;
|
|
||||||
font-size: 1.1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Простая информация */
|
|
||||||
.simple-info {
|
|
||||||
margin-top: 20px;
|
|
||||||
padding: 20px;
|
|
||||||
background: #f8f9fa;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
border: 1px solid #e9ecef;
|
|
||||||
}
|
|
||||||
|
|
||||||
.simple-info h5 {
|
|
||||||
margin: 0 0 15px 0;
|
|
||||||
color: var(--color-primary);
|
|
||||||
font-size: 1.1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-text {
|
|
||||||
color: #666;
|
|
||||||
line-height: 1.6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-text ul {
|
|
||||||
margin: 10px 0;
|
|
||||||
padding-left: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-text li {
|
|
||||||
margin-bottom: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.token-item {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 10px;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.token-input-wrapper {
|
|
||||||
position: relative;
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.token-input {
|
|
||||||
width: 100%;
|
|
||||||
padding-right: 40px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.token-input.is-valid {
|
|
||||||
border-color: #28a745;
|
|
||||||
box-shadow: 0 0 0 2px rgba(40, 167, 69, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.token-input.is-invalid {
|
|
||||||
border-color: #dc3545;
|
|
||||||
box-shadow: 0 0 0 2px rgba(220, 53, 69, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.validation-icon {
|
|
||||||
position: absolute;
|
|
||||||
right: 12px;
|
|
||||||
top: 50%;
|
|
||||||
transform: translateY(-50%);
|
|
||||||
width: 20px;
|
|
||||||
height: 20px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 50%;
|
|
||||||
font-size: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.validation-icon.valid {
|
|
||||||
background: #28a745;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.validation-icon.invalid {
|
|
||||||
background: #dc3545;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.validation-icon.loading {
|
|
||||||
background: #6c757d;
|
|
||||||
color: white;
|
|
||||||
}
|
|
||||||
|
|
||||||
.remove-token {
|
|
||||||
width: 36px;
|
|
||||||
height: 36px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: #dc3545;
|
|
||||||
border: none;
|
|
||||||
color: white;
|
|
||||||
font-size: 12px;
|
|
||||||
transition: all 0.2s ease;
|
|
||||||
flex-shrink: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.remove-token:hover {
|
|
||||||
background: #c82333;
|
|
||||||
transform: scale(1.05);
|
|
||||||
}
|
|
||||||
|
|
||||||
.remove-token:disabled {
|
|
||||||
background: #6c757d;
|
|
||||||
cursor: not-allowed;
|
|
||||||
transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-token {
|
|
||||||
margin-top: 15px;
|
|
||||||
width: 100%;
|
|
||||||
padding: 12px 20px;
|
|
||||||
border: 2px dashed #28a745;
|
|
||||||
background: #f8f9fa;
|
|
||||||
color: #28a745;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
font-weight: 500;
|
|
||||||
transition: all 0.3s ease;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.add-token:hover {
|
|
||||||
background: #28a745;
|
|
||||||
color: white;
|
|
||||||
border-color: #28a745;
|
|
||||||
transform: translateY(-1px);
|
|
||||||
box-shadow: 0 4px 8px rgba(40, 167, 69, 0.2);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Сообщение валидации */
|
|
||||||
.form-validation-info {
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
margin-top: 15px;
|
|
||||||
padding: 10px 15px;
|
|
||||||
background: #fff3cd;
|
|
||||||
border: 1px solid #ffeaa7;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
color: #856404;
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.form-validation-info i {
|
|
||||||
color: #f39c12;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Дополнительные настройки */
|
|
||||||
.advanced-settings {
|
|
||||||
margin-top: 20px;
|
|
||||||
padding: 20px;
|
|
||||||
background: #f8f9fa;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
border: 1px solid #e9ecef;
|
|
||||||
}
|
|
||||||
|
|
||||||
.advanced-settings h5 {
|
|
||||||
margin: 0 0 15px 0;
|
|
||||||
color: var(--color-primary);
|
|
||||||
font-size: 1.1rem;
|
|
||||||
font-weight: 600;
|
|
||||||
}
|
|
||||||
|
|
||||||
.advanced-settings .form-row {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.advanced-settings .form-group {
|
|
||||||
margin-bottom: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.advanced-settings .form-group:last-child {
|
|
||||||
margin-bottom: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.networks-info h4,
|
|
||||||
.deploy-parameters h4 {
|
|
||||||
margin: 0 0 15px 0;
|
|
||||||
color: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.networks-list {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
||||||
gap: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.network-item {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
padding: 10px;
|
|
||||||
background: #f8f9fa;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
border: 1px solid #dee2e6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.network-name {
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.network-chain-id {
|
|
||||||
font-size: 12px;
|
|
||||||
color: #666;
|
|
||||||
font-family: monospace;
|
|
||||||
}
|
|
||||||
|
|
||||||
.parameter-item {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
padding: 8px 0;
|
|
||||||
border-bottom: 1px solid #f0f0f0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.parameter-item:last-child {
|
|
||||||
border-bottom: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.parameter-item label {
|
|
||||||
font-weight: 500;
|
|
||||||
color: #333;
|
|
||||||
}
|
|
||||||
|
|
||||||
.parameter-value {
|
|
||||||
font-family: monospace;
|
|
||||||
color: var(--color-primary);
|
|
||||||
background: #f8f9fa;
|
|
||||||
padding: 4px 8px;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
font-size: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.deploy-actions {
|
|
||||||
text-align: center;
|
|
||||||
margin-top: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn {
|
|
||||||
padding: 12px 24px;
|
|
||||||
border: none;
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
cursor: pointer;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 500;
|
|
||||||
transition: all 0.3s;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
gap: 8px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary {
|
|
||||||
background: linear-gradient(135deg, var(--color-primary), var(--color-primary-dark));
|
|
||||||
color: white;
|
|
||||||
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary:hover:not(:disabled) {
|
|
||||||
background: linear-gradient(135deg, var(--color-primary-dark), var(--color-primary));
|
|
||||||
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.2);
|
|
||||||
transform: translateY(-1px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-primary:disabled {
|
|
||||||
opacity: 0.6;
|
|
||||||
cursor: not-allowed;
|
|
||||||
transform: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
.btn-large {
|
|
||||||
padding: 16px 32px;
|
|
||||||
font-size: 18px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.deployment-progress {
|
|
||||||
margin-top: 20px;
|
|
||||||
padding: 15px;
|
|
||||||
background: white;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
border: 1px solid #dee2e6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-info {
|
|
||||||
display: flex;
|
|
||||||
justify-content: space-between;
|
|
||||||
align-items: center;
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-percentage {
|
|
||||||
font-weight: 600;
|
|
||||||
color: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-bar {
|
|
||||||
width: 100%;
|
|
||||||
height: 8px;
|
|
||||||
background: #f0f0f0;
|
|
||||||
border-radius: 4px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-fill {
|
|
||||||
height: 100%;
|
|
||||||
background: linear-gradient(90deg, var(--color-primary), var(--color-primary-dark));
|
|
||||||
transition: width 0.3s ease;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Информация о модуле */
|
|
||||||
.module-info {
|
|
||||||
margin-bottom: 30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-card {
|
|
||||||
background: #f8f9fa;
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
padding: 20px;
|
|
||||||
border: 1px solid #e9ecef;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-card h3 {
|
|
||||||
margin: 0 0 15px 0;
|
|
||||||
color: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-grid {
|
|
||||||
display: grid;
|
|
||||||
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
|
|
||||||
gap: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-item {
|
|
||||||
padding: 10px;
|
|
||||||
background: white;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
border: 1px solid #dee2e6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.info-item strong {
|
|
||||||
color: var(--color-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Плейсхолдер для формы */
|
|
||||||
.deploy-form-placeholder {
|
|
||||||
background: #f8f9fa;
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
padding: 40px;
|
|
||||||
text-align: center;
|
|
||||||
border: 2px dashed #dee2e6;
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder-content h3 {
|
|
||||||
color: var(--color-primary);
|
|
||||||
margin-bottom: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.placeholder-content p {
|
|
||||||
color: #666;
|
|
||||||
margin: 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Сообщение об отсутствии прав доступа */
|
|
||||||
.no-access-message {
|
|
||||||
background: #fff3cd;
|
|
||||||
border: 1px solid #ffeaa7;
|
|
||||||
border-radius: var(--radius-md);
|
|
||||||
padding: 30px;
|
|
||||||
margin: 20px 0;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-content h3 {
|
|
||||||
color: #856404;
|
|
||||||
margin-bottom: 15px;
|
|
||||||
font-size: 1.4em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-content p {
|
|
||||||
color: #856404;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
font-size: 1.1em;
|
|
||||||
line-height: 1.5;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-content .btn {
|
|
||||||
background: #6c757d;
|
|
||||||
color: white;
|
|
||||||
border: none;
|
|
||||||
border-radius: var(--radius-sm);
|
|
||||||
padding: 12px 24px;
|
|
||||||
font-size: 16px;
|
|
||||||
font-weight: 600;
|
|
||||||
cursor: pointer;
|
|
||||||
transition: background 0.2s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.message-content .btn:hover {
|
|
||||||
background: #5a6268;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* Адаптивность */
|
|
||||||
@media (max-width: 768px) {
|
|
||||||
.info-grid {
|
|
||||||
grid-template-columns: 1fr;
|
|
||||||
}
|
|
||||||
|
|
||||||
.page-header {
|
|
||||||
flex-direction: column;
|
|
||||||
align-items: flex-start;
|
|
||||||
gap: 15px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
Reference in New Issue
Block a user