From cde35ac57618ac9fd7e8dfb8bba2f27608a12b42 Mon Sep 17 00:00:00 2001 From: Alex Date: Wed, 6 Aug 2025 11:37:58 +0300 Subject: [PATCH] =?UTF-8?q?=D0=B2=D0=B0=D1=88=D0=B5=20=D1=81=D0=BE=D0=BE?= =?UTF-8?q?=D0=B1=D1=89=D0=B5=D0=BD=D0=B8=D0=B5=20=D0=BA=D0=BE=D0=BC=D0=BC?= =?UTF-8?q?=D0=B8=D1=82=D0=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- backend/routes/auth.js | 6 +- backend/routes/blockchain.js | 296 ++++++ backend/routes/dleV2.js | 4 +- backend/server.js | 2 + backend/services/auth-service.js | 1 + backend/services/authTokenService.js | 9 +- backend/services/encryptedDatabaseService.js | 71 +- backend/wsHub.js | 55 ++ frontend/nginx-tunnel.conf | 28 +- frontend/src/components/BaseLayout.vue | 8 +- frontend/src/composables/useAuth.js | 9 +- frontend/src/router/index.js | 51 +- frontend/src/utils/dle-contract.js | 467 +++++++++ frontend/src/utils/websocket.js | 135 +++ frontend/src/views/ManagementView.vue | 47 +- .../src/views/settings/AuthTokensSettings.vue | 9 +- .../views/smartcontracts/AnalyticsView.vue | 35 +- .../views/smartcontracts/DleMultisigView.vue | 726 -------------- .../views/smartcontracts/DleProposalsView.vue | 884 ++++++++++++++---- .../src/views/smartcontracts/HistoryView.vue | 81 +- .../src/views/smartcontracts/ModulesView.vue | 766 --------------- .../views/smartcontracts/ProposalsView.vue | 611 ------------ .../src/views/smartcontracts/QuorumView.vue | 23 +- .../src/views/smartcontracts/SettingsView.vue | 10 +- .../src/views/smartcontracts/TokensView.vue | 57 +- .../src/views/smartcontracts/TreasuryView.vue | 73 +- security-monitor.sh | 20 + 27 files changed, 1868 insertions(+), 2616 deletions(-) create mode 100644 frontend/src/utils/dle-contract.js create mode 100644 frontend/src/utils/websocket.js delete mode 100644 frontend/src/views/smartcontracts/DleMultisigView.vue delete mode 100644 frontend/src/views/smartcontracts/ModulesView.vue delete mode 100644 frontend/src/views/smartcontracts/ProposalsView.vue diff --git a/backend/routes/auth.js b/backend/routes/auth.js index 0de2a1e..43a7078 100644 --- a/backend/routes/auth.js +++ b/backend/routes/auth.js @@ -934,12 +934,12 @@ router.get('/check-tokens/:address', async (req, res) => { try { const { address } = req.params; - // Получаем балансы токенов на всех сетях - const balances = await authService.getTokenBalances(address); + // Получаем балансы токенов с минимальными балансами + const balances = await authService.getUserTokenBalances(address); res.json({ success: true, - balances, + data: balances, }); } catch (error) { logger.error('Error checking token balances:', error); diff --git a/backend/routes/blockchain.js b/backend/routes/blockchain.js index fd9e11e..77db039 100644 --- a/backend/routes/blockchain.js +++ b/backend/routes/blockchain.js @@ -108,4 +108,300 @@ router.post('/read-dle-info', async (req, res) => { } }); +// Получение поддерживаемых сетей из смарт-контракта +router.post('/get-supported-chains', async (req, res) => { + try { + const { dleAddress } = req.body; + + if (!dleAddress) { + return res.status(400).json({ + success: false, + error: 'Адрес DLE обязателен' + }); + } + + console.log(`[Blockchain] Получение поддерживаемых сетей для DLE: ${dleAddress}`); + + // Получаем RPC URL для Sepolia + const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111); + if (!rpcUrl) { + return res.status(500).json({ + success: false, + error: 'RPC URL для Sepolia не найден' + }); + } + + const provider = new ethers.JsonRpcProvider(rpcUrl); + + // ABI для проверки поддерживаемых сетей + const dleAbi = [ + "function isChainSupported(uint256 _chainId) external view returns (bool)", + "function getCurrentChainId() external view returns (uint256)" + ]; + + const dle = new ethers.Contract(dleAddress, dleAbi, provider); + + // Список всех возможных сетей для проверки + const allChains = [ + { chainId: 1, name: 'Ethereum', description: 'Основная сеть Ethereum' }, + { chainId: 137, name: 'Polygon', description: 'Сеть Polygon' }, + { chainId: 56, name: 'BSC', description: 'Binance Smart Chain' }, + { chainId: 42161, name: 'Arbitrum', description: 'Arbitrum One' }, + { chainId: 10, name: 'Optimism', description: 'Optimism' }, + { chainId: 8453, name: 'Base', description: 'Base' }, + { chainId: 43114, name: 'Avalanche', description: 'Avalanche C-Chain' }, + { chainId: 250, name: 'Fantom', description: 'Fantom Opera' }, + { chainId: 11155111, name: 'Sepolia', description: 'Ethereum Testnet Sepolia' }, + { chainId: 80001, name: 'Mumbai', description: 'Polygon Testnet Mumbai' }, + { chainId: 97, name: 'BSC Testnet', description: 'Binance Smart Chain Testnet' }, + { chainId: 421613, name: 'Arbitrum Goerli', description: 'Arbitrum Testnet Goerli' } + ]; + + const supportedChains = []; + + // Проверяем каждую сеть через смарт-контракт + for (const chain of allChains) { + try { + const isSupported = await dle.isChainSupported(chain.chainId); + if (isSupported) { + supportedChains.push(chain); + } + } catch (error) { + console.log(`[Blockchain] Ошибка при проверке сети ${chain.chainId}:`, error.message); + // Продолжаем проверку других сетей + } + } + + console.log(`[Blockchain] Найдено поддерживаемых сетей: ${supportedChains.length}`); + + res.json({ + success: true, + data: { + chains: supportedChains, + totalCount: supportedChains.length + } + }); + + } catch (error) { + console.error('[Blockchain] Ошибка при получении поддерживаемых сетей:', error); + res.status(500).json({ + success: false, + error: 'Ошибка при получении поддерживаемых сетей: ' + error.message + }); + } +}); + +// Получение списка всех предложений +router.post('/get-proposals', async (req, res) => { + try { + const { dleAddress } = req.body; + + if (!dleAddress) { + return res.status(400).json({ + success: false, + error: 'Адрес DLE обязателен' + }); + } + + console.log(`[Blockchain] Получение списка предложений для DLE: ${dleAddress}`); + + // Получаем RPC URL для Sepolia + const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111); + if (!rpcUrl) { + return res.status(500).json({ + success: false, + error: 'RPC URL для Sepolia не найден' + }); + } + + const provider = new ethers.JsonRpcProvider(rpcUrl); + + // ABI для чтения предложений (только читаемые поля) + const dleAbi = [ + "function proposals(uint256) external view returns (uint256 id, string description, uint256 forVotes, uint256 againstVotes, bool executed, uint256 deadline, address initiator, bytes operation)", + "function checkProposalResult(uint256 _proposalId) external view returns (bool)", + "event ProposalCreated(uint256 proposalId, address initiator, string description)" + ]; + + const dle = new ethers.Contract(dleAddress, dleAbi, provider); + + // Получаем события ProposalCreated для определения количества предложений + const currentBlock = await provider.getBlockNumber(); + const fromBlock = Math.max(0, currentBlock - 10000); // Последние 10000 блоков + + const events = await dle.queryFilter('ProposalCreated', fromBlock, currentBlock); + + console.log(`[Blockchain] Найдено событий ProposalCreated: ${events.length}`); + console.log(`[Blockchain] Диапазон блоков: ${fromBlock} - ${currentBlock}`); + + const proposals = []; + + // Читаем информацию о каждом предложении + for (let i = 0; i < events.length; i++) { + try { + const proposalId = events[i].args.proposalId; + console.log(`[Blockchain] Читаем предложение ID: ${proposalId}`); + + // Пробуем несколько раз для новых предложений + let proposal, isPassed; + let retryCount = 0; + const maxRetries = 3; + + while (retryCount < maxRetries) { + try { + proposal = await dle.proposals(proposalId); + isPassed = await dle.checkProposalResult(proposalId); + break; // Успешно прочитали + } catch (error) { + retryCount++; + console.log(`[Blockchain] Попытка ${retryCount} чтения предложения ${proposalId} не удалась:`, error.message); + if (retryCount < maxRetries) { + await new Promise(resolve => setTimeout(resolve, 2000)); // Ждем 2 секунды + } else { + throw error; // Превышено количество попыток + } + } + } + + // governanceChainId не сохраняется в предложении, используем текущую цепочку + const governanceChainId = 11155111; // Sepolia chain ID + + console.log(`[Blockchain] Данные предложения ${proposalId}:`, { + id: Number(proposal.id), + description: proposal.description, + forVotes: Number(proposal.forVotes), + againstVotes: Number(proposal.againstVotes), + executed: proposal.executed, + deadline: Number(proposal.deadline), + initiator: proposal.initiator, + operation: proposal.operation, + governanceChainId: Number(governanceChainId) + }); + + const proposalInfo = { + id: Number(proposal.id), + description: proposal.description, + forVotes: Number(proposal.forVotes), + againstVotes: Number(proposal.againstVotes), + executed: proposal.executed, + deadline: Number(proposal.deadline), + initiator: proposal.initiator, + operation: proposal.operation, + governanceChainId: Number(governanceChainId), + isPassed: isPassed, + blockNumber: events[i].blockNumber + }; + + proposals.push(proposalInfo); + } catch (error) { + console.log(`[Blockchain] Ошибка при чтении предложения ${i}:`, error.message); + + // Если это ошибка декодирования, возможно предложение еще не полностью записано + if (error.message.includes('could not decode result data')) { + console.log(`[Blockchain] Предложение ${i} еще не полностью синхронизировано, пропускаем`); + continue; + } + + // Продолжаем с следующим предложением + } + } + + // Сортируем по ID предложения (новые сверху) + proposals.sort((a, b) => b.id - a.id); + + console.log(`[Blockchain] Найдено предложений: ${proposals.length}`); + + res.json({ + success: true, + data: { + proposals: proposals, + totalCount: proposals.length + } + }); + + } catch (error) { + console.error('[Blockchain] Ошибка при получении списка предложений:', error); + res.status(500).json({ + success: false, + error: 'Ошибка при получении списка предложений: ' + error.message + }); + } +}); + +// Получение информации о предложении +router.post('/get-proposal-info', async (req, res) => { + try { + const { dleAddress, proposalId } = req.body; + + if (!dleAddress || proposalId === undefined) { + return res.status(400).json({ + success: false, + error: 'Все поля обязательны: dleAddress, proposalId' + }); + } + + console.log(`[Blockchain] Получение информации о предложении ${proposalId} в DLE: ${dleAddress}`); + + // Получаем RPC URL для Sepolia + const rpcUrl = await rpcProviderService.getRpcUrlByChainId(11155111); + if (!rpcUrl) { + return res.status(500).json({ + success: false, + error: 'RPC URL для Sepolia не найден' + }); + } + + const provider = new ethers.JsonRpcProvider(rpcUrl); + + // ABI для чтения информации о предложении + const dleAbi = [ + "function proposals(uint256) external view returns (tuple(string description, uint256 duration, bytes operation, uint256 governanceChainId, uint256 startTime, bool executed, uint256 forVotes, uint256 againstVotes))", + "function checkProposalResult(uint256 _proposalId) external view returns (bool)" + ]; + + const dle = new ethers.Contract(dleAddress, dleAbi, provider); + + // Читаем информацию о предложении + const proposal = await dle.proposals(proposalId); + const isPassed = await dle.checkProposalResult(proposalId); + + // governanceChainId не сохраняется в предложении, используем текущую цепочку + const governanceChainId = 11155111; // Sepolia chain ID + + const proposalInfo = { + description: proposal.description, + duration: Number(proposal.duration), + operation: proposal.operation, + governanceChainId: Number(proposal.governanceChainId), + startTime: Number(proposal.startTime), + executed: proposal.executed, + forVotes: Number(proposal.forVotes), + againstVotes: Number(proposal.againstVotes), + isPassed: isPassed + }; + + console.log(`[Blockchain] Информация о предложении получена:`, proposalInfo); + + res.json({ + success: true, + data: proposalInfo + }); + + } catch (error) { + console.error('[Blockchain] Ошибка при получении информации о предложении:', error); + res.status(500).json({ + success: false, + error: 'Ошибка при получении информации о предложении: ' + error.message + }); + } +}); + + + + + +// Импортируем WebSocket функции из wsHub +const { broadcastProposalCreated, broadcastProposalVoted, broadcastProposalExecuted } = require('../wsHub'); + +// Экспортируем router как основной экспорт module.exports = router; \ No newline at end of file diff --git a/backend/routes/dleV2.js b/backend/routes/dleV2.js index 5819322..d1aa4f1 100644 --- a/backend/routes/dleV2.js +++ b/backend/routes/dleV2.js @@ -71,9 +71,9 @@ router.post('/', auth.requireAuth, auth.requireAdmin, async (req, res, next) => /** * @route GET /api/dle-v2 * @desc Получить список всех DLE v2 - * @access Private (только для авторизованных пользователей) + * @access Public (доступно всем пользователям) */ -router.get('/', auth.requireAuth, async (req, res, next) => { +router.get('/', async (req, res, next) => { try { const dles = dleV2Service.getAllDLEs(); diff --git a/backend/server.js b/backend/server.js index e2c1491..41f52d7 100644 --- a/backend/server.js +++ b/backend/server.js @@ -62,6 +62,8 @@ async function initServices() { const server = http.createServer(app); initWSS(server); +// WebSocket уже инициализирован в wsHub.js + async function startServer() { await initDbPool(); // Дождаться пересоздания пула! await seedAIAssistantSettings(); // Инициализация ассистента после загрузки модели Ollama diff --git a/backend/services/auth-service.js b/backend/services/auth-service.js index 97ba204..d7f4d47 100644 --- a/backend/services/auth-service.js +++ b/backend/services/auth-service.js @@ -890,6 +890,7 @@ class AuthService { tokenName: token.name, symbol: token.symbol || '', balance, + minBalance: token.min_balance, }); } return results; diff --git a/backend/services/authTokenService.js b/backend/services/authTokenService.js index ea36881..1688832 100644 --- a/backend/services/authTokenService.js +++ b/backend/services/authTokenService.js @@ -62,7 +62,14 @@ async function upsertAuthToken(token) { } async function deleteAuthToken(address, network) { - await encryptedDb.deleteData('auth_tokens', { address, network }); + console.log(`[AuthTokenService] deleteAuthToken: address=${address}, network=${network}`); + try { + await encryptedDb.deleteData('auth_tokens', { address, network }); + console.log(`[AuthTokenService] Токен успешно удален`); + } catch (error) { + console.error(`[AuthTokenService] Ошибка при удалении токена:`, error); + throw error; + } } module.exports = { getAllAuthTokens, saveAllAuthTokens, upsertAuthToken, deleteAuthToken }; \ No newline at end of file diff --git a/backend/services/encryptedDatabaseService.js b/backend/services/encryptedDatabaseService.js index ba9c8b5..d6fd113 100644 --- a/backend/services/encryptedDatabaseService.js +++ b/backend/services/encryptedDatabaseService.js @@ -323,48 +323,73 @@ class EncryptedDataService { */ async deleteData(tableName, conditions) { try { + console.log(`[EncryptedDataService] deleteData: tableName=${tableName}, conditions=`, conditions); + + // Проверяем, включено ли шифрование + if (!this.isEncryptionEnabled) { + return await this.executeUnencryptedQuery(tableName, conditions); + } + + // Получаем информацию о колонках + const { rows: columns } = await db.getQuery()(` + SELECT column_name, data_type + FROM information_schema.columns + WHERE table_name = $1 + AND table_schema = 'public' + ORDER BY ordinal_position + `, [tableName]); + + console.log(`[EncryptedDataService] Columns for ${tableName}:`, columns.map(c => c.column_name)); + // Функция для заключения зарезервированных слов в кавычки const quoteReservedWord = (word) => { const reservedWords = ['order', 'group', 'user', 'index', 'table', 'column', 'key', 'foreign', 'primary', 'unique', 'check', 'constraint', 'default', 'null', 'not', 'and', 'or', 'as', 'on', 'in', 'is', 'like', 'between', 'exists', 'all', 'any', 'some', 'distinct', 'case', 'when', 'then', 'else', 'end', 'limit', 'offset', 'having', 'union', 'intersect', 'except', 'with', 'recursive']; return reservedWords.includes(word.toLowerCase()) ? `"${word}"` : word; }; - // Проверяем, включено ли шифрование - if (!this.isEncryptionEnabled) { - let query = `DELETE FROM ${tableName}`; - const params = []; - let paramIndex = 1; - - if (Object.keys(conditions).length > 0) { - const whereClause = Object.keys(conditions) - .map(key => `${quoteReservedWord(key)} = $${paramIndex++}`) - .join(' AND '); - query += ` WHERE ${whereClause}`; - params.push(...Object.values(conditions)); - } - - const { rows } = await db.getQuery()(query, params); - return rows; - } - - // Для зашифрованных таблиц - пока используем обычный DELETE - // TODO: Добавить логику для зашифрованных условий WHERE let query = `DELETE FROM ${tableName}`; const params = []; let paramIndex = 1; if (Object.keys(conditions).length > 0) { const whereClause = Object.keys(conditions) - .map(key => `${quoteReservedWord(key)} = $${paramIndex++}`) + .map((key, index) => { + const value = conditions[key]; + + // Проверяем, есть ли зашифрованная версия колонки + const encryptedColumn = columns.find(col => col.column_name === `${key}_encrypted`); + + if (encryptedColumn) { + // Для зашифрованных колонок используем прямое сравнение с зашифрованным значением + // Ключ шифрования всегда первый параметр ($1), затем значения + return `${key}_encrypted = encrypt_text($${index + 2}, $1)`; + } else { + // Для незашифрованных колонок используем обычное сравнение + const columnName = quoteReservedWord(key); + return `${columnName} = $${index + 1}`; + } + }) .join(' AND '); query += ` WHERE ${whereClause}`; - params.push(...Object.values(conditions)); + + // Добавляем параметры + const paramsToAdd = Object.values(conditions); + params.push(...paramsToAdd); } + // Добавляем ключ шифрования в начало, если есть зашифрованные поля + const hasEncryptedFields = columns.some(col => col.column_name.endsWith('_encrypted')); + if (hasEncryptedFields) { + params.unshift(this.encryptionKey); + } + + console.log(`[EncryptedDataService] DELETE query: ${query}`); + console.log(`[EncryptedDataService] DELETE params:`, params); + const result = await db.getQuery()(query, params); return result.rows; } catch (error) { - // console.error(`❌ Ошибка удаления данных из ${tableName}:`, error); + console.error(`[EncryptedDataService] ❌ Ошибка удаления данных из ${tableName}:`, error); throw error; } } diff --git a/backend/wsHub.js b/backend/wsHub.js index 46e3725..b79f3b1 100644 --- a/backend/wsHub.js +++ b/backend/wsHub.js @@ -313,6 +313,58 @@ function broadcastAIStatus(status) { } } +// Blockchain уведомления +function broadcastProposalCreated(dleAddress, proposalId, txHash) { + const message = JSON.stringify({ + type: 'proposal_created', + data: { + dleAddress: dleAddress, + proposalId: proposalId, + txHash: txHash + }, + timestamp: Date.now() + }); + + broadcastToAllClients(message); +} + +function broadcastProposalVoted(dleAddress, proposalId, support, txHash) { + const message = JSON.stringify({ + type: 'proposal_voted', + data: { + dleAddress: dleAddress, + proposalId: proposalId, + support: support, + txHash: txHash + }, + timestamp: Date.now() + }); + + broadcastToAllClients(message); +} + +function broadcastProposalExecuted(dleAddress, proposalId, txHash) { + const message = JSON.stringify({ + type: 'proposal_executed', + data: { + dleAddress: dleAddress, + proposalId: proposalId, + txHash: txHash + }, + timestamp: Date.now() + }); + + broadcastToAllClients(message); +} + +function broadcastToAllClients(message) { + wss.clients.forEach((client) => { + if (client.readyState === WebSocket.OPEN) { + client.send(message); + } + }); +} + module.exports = { initWSS, broadcastContactsUpdate, @@ -323,6 +375,9 @@ module.exports = { broadcastTableRelationsUpdate, broadcastTagsUpdate, broadcastAIStatus, + broadcastProposalCreated, + broadcastProposalVoted, + broadcastProposalExecuted, getConnectedUsers, getStats }; \ No newline at end of file diff --git a/frontend/nginx-tunnel.conf b/frontend/nginx-tunnel.conf index f7013bf..1df15ca 100644 --- a/frontend/nginx-tunnel.conf +++ b/frontend/nginx-tunnel.conf @@ -12,10 +12,24 @@ server { return 403; } - # Блокировка очень старых браузеров - if ($http_user_agent ~* "MSIE [1-8]\.") { + # Блокировка старых браузеров и подозрительных User-Agent + if ($http_user_agent ~* "Chrome/[1-7][0-9]\.") { return 403; } + + if ($http_user_agent ~* "Safari/[1-5][0-9][0-9]\.") { + return 403; + } + + # Блокировка подозрительных поддоменов + if ($host !~* "^(hb3-accelerator\.com|www\.hb3-accelerator\.com|localhost|127\.0\.0\.1)$") { + return 404; + } + + # Блокировка сканирования резервных копий и архивов + if ($request_uri ~* "(backup|backups|bak|old|restore|www\.tar|website\.tar|\.tar\.gz|\.gz|\.sql\.tar|public_html\.tar|sftp-config\.json)") { + return 404; + } # Блокировка опасных файлов (НЕ блокируем .js, .css) if ($request_uri ~* "\.(php|asp|aspx|jsp|cgi|pl|py|sh|bash|exe|bat|cmd|com|pif|scr|vbs|vbe|jar|war|ear|dll|so|dylib|bin|sys|ini|log|bak|old|tmp|temp|swp|swo|~)$") { @@ -37,6 +51,16 @@ server { return 403; } + # Блокировка HEAD запросов к подозрительным файлам + if ($request_method = "HEAD" && $request_uri ~* "(backup|backups|bak|old|restore|\.tar|\.gz|\.sql|config\.js|sftp-config\.json)") { + return 404; + } + + # Блокировка всех запросов к конфигурационным файлам + if ($request_uri ~* "(config\.js|sftp-config\.json|\.config\.|\.conf\.|\.ini\.|\.env\.|\.json\.)") { + return 404; + } + # Основной location location / { try_files $uri $uri/ /index.html =404; diff --git a/frontend/src/components/BaseLayout.vue b/frontend/src/components/BaseLayout.vue index 019c78e..3a85cad 100644 --- a/frontend/src/components/BaseLayout.vue +++ b/frontend/src/components/BaseLayout.vue @@ -49,7 +49,7 @@ - - \ No newline at end of file diff --git a/frontend/src/views/smartcontracts/DleProposalsView.vue b/frontend/src/views/smartcontracts/DleProposalsView.vue index 5ab1d9d..535a01d 100644 --- a/frontend/src/views/smartcontracts/DleProposalsView.vue +++ b/frontend/src/views/smartcontracts/DleProposalsView.vue @@ -33,17 +33,160 @@ DLE не выбран - + + - -
-
-

📝 Новое предложение

- + +
+
+

📋 Список предложений

+
+ + +
+
+ +
+

Предложений пока нет

+ +
+
+ +
+
{{ proposal.description || 'Без описания' }}
+ + {{ getProposalStatusText(proposal.status) }} + +
+ +
+
+ ID: #{{ proposal.id }} +
+
+ Создатель: {{ shortenAddress(proposal.initiator) }} +
+
+ Создано: {{ formatDate(proposal.blockNumber ? proposal.blockNumber * 1000 : Date.now()) }} +
+
+ Цепочка: {{ getChainName(proposal.governanceChainId) || 'Неизвестная сеть' }} +
+
+ Дедлайн: {{ formatDate(proposal.deadline) }} +
+
+ Голоса: +
+
+ За: {{ formatVotes(proposal.forVotes) }} + Против: {{ formatVotes(proposal.againstVotes) }} +
+
+ Кворум: {{ getQuorumPercentage(proposal) }}% из {{ getRequiredQuorum() }}% +
+
+
+
+
+
+
+
+
+ Операция: + {{ decodeOperation(proposal.operation) }} +
+
+ Детали операции: + {{ getOperationDetails(proposal.operation, proposal) }} +
+
+ +
+ + + + + +
+ + + Для участия в голосовании необходимо авторизоваться + +
+
+ + + Для участия в голосовании необходимы права администратора + +
+ +
+
+
+
+ + +
+
+

📝 Создание нового предложения

+ +
+ + +
+
+ + Для создания предложений необходимо авторизоваться в приложении +

Подключите кошелек в сайдбаре для создания новых предложений

+
+
+ + +
@@ -125,8 +268,10 @@ id="transferTo" v-model="newProposal.operationParams.to" class="form-control" - placeholder="0x..." + placeholder="0x1234567890abcdef1234567890abcdef12345678" + :class="{ 'is-invalid': newProposal.operationParams.to && !validateAddress(newProposal.operationParams.to) }" > + Введите корректный Ethereum адрес (42 символа, начинается с 0x)
@@ -137,7 +282,9 @@ class="form-control" min="1" placeholder="100" + :class="{ 'is-invalid': newProposal.operationParams.amount <= 0 }" > + Введите количество токенов для передачи
@@ -243,113 +390,21 @@ - -
-
-
- - -
-
-

📋 Список предложений

-
- -
-
- -
-

Предложений пока нет

-
- -
-
-
-
{{ proposal.description }}
- - {{ getProposalStatusText(proposal.status) }} - -
- -
-
- ID: #{{ proposal.id }} -
-
- Создатель: {{ shortenAddress(proposal.initiator) }} -
-
- Цепочка: {{ getChainName(proposal.governanceChainId) }} -
-
- Дедлайн: {{ formatDate(proposal.deadline) }} -
-
- Голоса: - - За: {{ proposal.forVotes }} - Против: {{ proposal.againstVotes }} - -
-
- -
- - - - -
+
@@ -750,7 +1149,8 @@ onMounted(() => { background: #f8f9fa; border-radius: 8px; padding: 1.5rem; - margin-bottom: 2rem; + margin-top: 2rem; + border-top: 2px solid #e9ecef; } .form-header { @@ -866,6 +1266,12 @@ onMounted(() => { margin-bottom: 1rem; } +.list-filters { + display: flex; + align-items: center; + gap: 1rem; +} + .proposals-grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(300px, 1fr)); @@ -891,6 +1297,22 @@ onMounted(() => { border-color: #dc3545; } +.auth-notice { + text-align: center; + padding: 1rem; + background-color: #f8f9fa; + border-radius: 4px; + margin-top: 1rem; +} + +.auth-notice-form { + margin-bottom: 1rem; +} + +.auth-notice-form .alert { + margin-bottom: 0; +} + .proposal-header { display: flex; justify-content: space-between; @@ -934,19 +1356,59 @@ onMounted(() => { font-size: 0.9rem; } -.votes { +.votes-container { + display: flex; + flex-direction: column; + gap: 0.5rem; +} + +.votes-info { display: flex; gap: 1rem; + align-items: center; } -.votes .for { +.votes-info .for { color: #28a745; + font-weight: 500; } -.votes .against { +.votes-info .against { color: #dc3545; + font-weight: 500; } +.quorum-info { + display: flex; + gap: 0.5rem; + align-items: center; +} + +.quorum-info .quorum-percentage { + color: #666; + font-size: 0.9em; +} + +.quorum-progress { + width: 100%; +} + +.progress-bar { + width: 100%; + height: 8px; + background-color: #e9ecef; + border-radius: 4px; + overflow: hidden; +} + +.progress-fill { + height: 100%; + background: linear-gradient(90deg, #28a745, #20c997); + transition: width 0.3s ease; +} + + + .proposal-actions { display: flex; gap: 0.5rem; @@ -964,4 +1426,36 @@ onMounted(() => { color: #666; margin-bottom: 1rem; } + +/* Стили для валидации */ +.form-control.is-invalid { + border-color: #dc3545; + box-shadow: 0 0 0 0.2rem rgba(220, 53, 69, 0.25); +} + +.form-text { + font-size: 0.875rem; + color: #6c757d; + margin-top: 0.25rem; +} + +.form-control { + width: 100%; + padding: 0.75rem; + border: 1px solid #ddd; + border-radius: 4px; + font-size: 1rem; +} + +.operation { + font-family: monospace; + font-size: 0.9em; + color: #666; +} + +.operation-details { + font-size: 0.9em; + color: #28a745; + font-weight: 500; +} \ No newline at end of file diff --git a/frontend/src/views/smartcontracts/HistoryView.vue b/frontend/src/views/smartcontracts/HistoryView.vue index 365317f..d7de7f0 100644 --- a/frontend/src/views/smartcontracts/HistoryView.vue +++ b/frontend/src/views/smartcontracts/HistoryView.vue @@ -293,85 +293,8 @@ const filters = ref({ status: '' }); -// История операций (временные данные) -const history = ref([ - { - id: 1, - type: 'proposal', - title: 'Создание предложения', - description: 'Создано предложение #15: Перевод 100 токенов партнеру', - timestamp: Date.now() - 3600000, - status: 'success', - transactionHash: '0x1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef', - blockNumber: 18456789, - data: { - 'ID предложения': 15, - 'Инициатор': '0x1234...5678', - 'Количество токенов': '100 MDLE', - 'Получатель': '0xabcd...efgh' - } - }, - { - id: 2, - type: 'vote', - title: 'Голосование', - description: 'Подписано предложение #15', - timestamp: Date.now() - 7200000, - status: 'success', - transactionHash: '0xabcdef1234567890abcdef1234567890abcdef1234567890abcdef1234567890', - blockNumber: 18456788, - data: { - 'ID предложения': 15, - 'Голосующий': '0x5678...9012', - 'Вес голоса': '500 токенов' - } - }, - { - id: 3, - type: 'transfer', - title: 'Трансфер токенов', - description: 'Перевод 50 токенов между участниками', - timestamp: Date.now() - 10800000, - status: 'success', - transactionHash: '0x567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef1234', - blockNumber: 18456787, - data: { - 'От': '0x9012...3456', - 'Кому': '0x3456...7890', - 'Количество': '50 MDLE' - } - }, - { - id: 4, - type: 'treasury', - title: 'Операция с казной', - description: 'Пополнение казны на 1000 USDC', - timestamp: Date.now() - 14400000, - status: 'pending', - transactionHash: '0x901234567890abcdef1234567890abcdef1234567890abcdef1234567890abcd', - blockNumber: 18456786, - data: { - 'Тип операции': 'Депозит', - 'Актив': 'USDC', - 'Количество': '1000' - } - }, - { - id: 5, - type: 'module', - title: 'Установка модуля', - description: 'Установлен модуль "Казначейство"', - timestamp: Date.now() - 18000000, - status: 'success', - transactionHash: '0x345678901234567890abcdef1234567890abcdef1234567890abcdef123456789', - blockNumber: 18456785, - data: { - 'Название модуля': 'Казначейство', - 'Версия': '1.0.0', - 'Адрес контракта': '0xabcd...efgh' - } - } -]); +// История операций (загружается из блокчейна) +const history = ref([]); // Вычисляемые свойства const filteredHistory = computed(() => { diff --git a/frontend/src/views/smartcontracts/ModulesView.vue b/frontend/src/views/smartcontracts/ModulesView.vue deleted file mode 100644 index 4ff0771..0000000 --- a/frontend/src/views/smartcontracts/ModulesView.vue +++ /dev/null @@ -1,766 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/frontend/src/views/smartcontracts/ProposalsView.vue b/frontend/src/views/smartcontracts/ProposalsView.vue deleted file mode 100644 index 5cce163..0000000 --- a/frontend/src/views/smartcontracts/ProposalsView.vue +++ /dev/null @@ -1,611 +0,0 @@ - - - - - - - \ No newline at end of file diff --git a/frontend/src/views/smartcontracts/QuorumView.vue b/frontend/src/views/smartcontracts/QuorumView.vue index bca3053..f65d4a9 100644 --- a/frontend/src/views/smartcontracts/QuorumView.vue +++ b/frontend/src/views/smartcontracts/QuorumView.vue @@ -216,27 +216,8 @@ const newSettings = ref({ reason: '' }); -// История изменений (временные данные) -const settingsHistory = ref([ - { - id: 1, - timestamp: Date.now() - 86400000, // 1 день назад - reason: 'Оптимизация параметров голосования для повышения эффективности', - quorumChange: { from: 60, to: 51 }, - votingDelayChange: { from: 2, to: 1 }, - author: '0x1234567890123456789012345678901234567890' - }, - { - id: 2, - timestamp: Date.now() - 604800000, // 1 неделя назад - reason: 'Первоначальная настройка параметров DLE', - quorumChange: { from: 0, to: 60 }, - votingDelayChange: { from: 0, to: 2 }, - votingPeriodChange: { from: 0, to: 45818 }, - proposalThresholdChange: { from: 0, to: 100 }, - author: '0x2345678901234567890123456789012345678901' - } -]); +// История изменений (загружается из блокчейна) +const settingsHistory = ref([]); // Методы const updateSettings = async () => { diff --git a/frontend/src/views/smartcontracts/SettingsView.vue b/frontend/src/views/smartcontracts/SettingsView.vue index 5c1d29a..72d4be6 100644 --- a/frontend/src/views/smartcontracts/SettingsView.vue +++ b/frontend/src/views/smartcontracts/SettingsView.vue @@ -351,14 +351,8 @@ const networkSettings = ref({ rpcEndpoint: 'https://mainnet.infura.io/v3/YOUR_PROJECT_ID' }); -// Доступные сети -const availableNetworks = ref([ - { id: 1, name: 'Ethereum Mainnet', chainId: 1 }, - { id: 137, name: 'Polygon', chainId: 137 }, - { id: 56, name: 'BSC', chainId: 56 }, - { id: 42161, name: 'Arbitrum', chainId: 42161 }, - { id: 10, name: 'Optimism', chainId: 10 } -]); +// Доступные сети (загружаются из конфигурации) +const availableNetworks = ref([]); // Методы const saveMainSettings = async () => { diff --git a/frontend/src/views/smartcontracts/TokensView.vue b/frontend/src/views/smartcontracts/TokensView.vue index 98accf4..5eb7b68 100644 --- a/frontend/src/views/smartcontracts/TokensView.vue +++ b/frontend/src/views/smartcontracts/TokensView.vue @@ -228,12 +228,12 @@ const isLoadingDle = ref(false); const isTransferring = ref(false); const isDistributing = ref(false); -// Данные токенов (реактивные) -const tokenSymbol = computed(() => selectedDle.value?.symbol || 'MDLE'); -const totalSupply = computed(() => selectedDle.value?.initialAmounts?.[0] || 10000); -const userBalance = computed(() => Math.floor(totalSupply.value * 0.1)); // 10% для демо -const quorumPercentage = computed(() => selectedDle.value?.governanceSettings?.quorumPercentage || 51); -const tokenPrice = ref(1.25); +// Данные токенов (загружаются из блокчейна) +const tokenSymbol = computed(() => selectedDle.value?.symbol || ''); +const totalSupply = computed(() => selectedDle.value?.totalSupply || 0); +const userBalance = computed(() => selectedDle.value?.deployerBalance || 0); +const quorumPercentage = computed(() => selectedDle.value?.quorumPercentage || 0); +const tokenPrice = ref(0); // Данные трансфера const transferData = ref({ @@ -250,14 +250,8 @@ const distributionData = ref({ ] }); -// Держатели токенов (временные данные) -const tokenHolders = ref([ - { address: '0x1234567890123456789012345678901234567890', balance: 2500 }, - { address: '0x2345678901234567890123456789012345678901', balance: 1800 }, - { address: '0x3456789012345678901234567890123456789012', balance: 1200 }, - { address: '0x4567890123456789012345678901234567890123', balance: 800 }, - { address: '0x5678901234567890123456789012345678901234', balance: 600 } -]); +// Держатели токенов (загружаются из блокчейна) +const tokenHolders = ref([]); // Функции async function loadDleData() { @@ -268,27 +262,38 @@ async function loadDleData() { isLoadingDle.value = true; try { - // Загружаем данные DLE из backend - const response = await axios.get(`/dle-v2`); - const dles = response.data.data; // Используем response.data.data + // Читаем актуальные данные из блокчейна + const blockchainResponse = await axios.post('/blockchain/read-dle-info', { + dleAddress: dleAddress.value + }); - // Находим нужный DLE по адресу - const dle = dles.find(d => d.dleAddress === dleAddress.value); - - if (dle) { - selectedDle.value = dle; - console.log('Загружен DLE:', dle); - console.log('Данные токенов будут обновлены автоматически'); + if (blockchainResponse.data.success) { + const blockchainData = blockchainResponse.data.data; + selectedDle.value = blockchainData; + console.log('Загружены данные DLE из блокчейна:', blockchainData); + + // Загружаем держателей токенов (если есть API) + await loadTokenHolders(); } else { - console.warn('DLE не найден:', dleAddress.value); + console.warn('Не удалось прочитать данные из блокчейна для', dleAddress.value); } } catch (error) { - console.error('Ошибка загрузки DLE:', error); + console.error('Ошибка загрузки данных DLE из блокчейна:', error); } finally { isLoadingDle.value = false; } } +async function loadTokenHolders() { + try { + // Здесь можно добавить загрузку держателей токенов из блокчейна + // Пока оставляем пустым + tokenHolders.value = []; + } catch (error) { + console.error('Ошибка загрузки держателей токенов:', error); + } +} + function shortenAddress(address) { if (!address) return ''; return `${address.slice(0, 6)}...${address.slice(-4)}`; diff --git a/frontend/src/views/smartcontracts/TreasuryView.vue b/frontend/src/views/smartcontracts/TreasuryView.vue index f6f5276..1a9d55a 100644 --- a/frontend/src/views/smartcontracts/TreasuryView.vue +++ b/frontend/src/views/smartcontracts/TreasuryView.vue @@ -270,49 +270,8 @@ const dailyChange = ref(25000); const assetsCount = ref(5); const yieldPercentage = ref(8.5); -// Активы (временные данные) -const assets = ref([ - { - id: 'eth', - name: 'Ethereum', - symbol: 'ETH', - balance: 125.5, - value: 450000, - change: 2.5 - }, - { - id: 'usdc', - name: 'USD Coin', - symbol: 'USDC', - balance: 500000, - value: 500000, - change: 0.1 - }, - { - id: 'btc', - name: 'Bitcoin', - symbol: 'BTC', - balance: 2.5, - value: 150000, - change: -1.2 - }, - { - id: 'matic', - name: 'Polygon', - symbol: 'MATIC', - balance: 50000, - value: 75000, - change: 5.8 - }, - { - id: 'link', - name: 'Chainlink', - symbol: 'LINK', - balance: 2500, - value: 75000, - change: 3.2 - } -]); +// Активы (загружаются из блокчейна) +const assets = ref([]); // Вкладки операций const operationTabs = ref([ @@ -335,32 +294,8 @@ const withdrawData = ref({ reason: '' }); -// История операций (временные данные) -const operationsHistory = ref([ - { - id: 1, - type: 'deposit', - asset: 'Ethereum', - symbol: 'ETH', - amount: 10.5, - value: 37500, - reason: 'Пополнение казны от доходов', - timestamp: Date.now() - 3600000, - status: 'completed' - }, - { - id: 2, - type: 'withdraw', - asset: 'USD Coin', - symbol: 'USDC', - amount: 25000, - value: 25000, - reason: 'Выплата партнерам', - recipient: '0x1234567890123456789012345678901234567890', - timestamp: Date.now() - 7200000, - status: 'completed' - } -]); +// История операций (загружается из блокчейна) +const operationsHistory = ref([]); // Методы const depositAsset = (asset) => { diff --git a/security-monitor.sh b/security-monitor.sh index 4f77735..15e4ae4 100755 --- a/security-monitor.sh +++ b/security-monitor.sh @@ -37,6 +37,12 @@ block_ip() { local ip=$1 local reason=$2 + # Исключаем внутренние Docker IP адреса + if [[ "$ip" =~ ^172\.(1[6-9]|2[0-9]|3[0-1])\. ]] || [[ "$ip" =~ ^10\. ]] || [[ "$ip" =~ ^192\.168\. ]]; then + echo "🔒 Пропускаем внутренний IP: $ip (причина: $reason)" + return + fi + # Проверяем, не заблокирован ли уже IP if grep -q "^$ip$" "$BLOCKED_IPS_FILE"; then return @@ -91,11 +97,25 @@ analyze_logs() { block_ip "$ip" "Попытка доступа к чувствительным файлам" fi + # Проверяем на сканирование резервных копий и архивов + if echo "$line" | grep -q "backup\|backups\|bak\|old\|restore\|\.tar\|\.gz\|sftp-config"; then + block_ip "$ip" "Сканирование резервных копий и конфигурационных файлов" + fi + + # Проверяем на подозрительные поддомены + if echo "$line" | grep -q "bestcupcakerecipes\|usmc1\|test\|admin\|dev\|staging"; then + block_ip "$ip" "Попытка доступа к несуществующим поддоменам" + fi + # Проверяем на старые User-Agent if echo "$line" | grep -q "Chrome/[1-7][0-9]\."; then block_ip "$ip" "Подозрительный User-Agent (старый Chrome)" fi + if echo "$line" | grep -q "Safari/[1-5][0-9][0-9]\."; then + block_ip "$ip" "Подозрительный User-Agent (старый Safari)" + fi + # Проверяем на известные сканеры if echo "$line" | grep -qi "bot\|crawler\|spider\|scanner\|nmap\|sqlmap"; then block_ip "$ip" "Известный сканер/бот"