ваше сообщение коммита

This commit is contained in:
2025-08-16 02:29:42 +03:00
parent 3765c65a18
commit 9134e83b8f
33 changed files with 8680 additions and 1435 deletions

View File

@@ -120,10 +120,7 @@
Баланс не доступен (tokenBalances: {{ tokenBalances }}, length: {{ tokenBalances?.length }})
</div>
<div v-else>
<div class="token-balance-header">
<small class="last-update">Обновлено: {{ formattedLastUpdate }}</small>
<small class="debug-info">Debug: {{ tokenBalances.length }} токенов</small>
</div>
<div v-for="(token, index) in tokenBalances" :key="token.tokenAddress ? token.tokenAddress : 'token-' + index" class="token-balance-row">
<span class="token-name">{{ token.tokenName }}</span>
<span class="token-network">{{ token.network }}</span>
@@ -140,16 +137,15 @@
<strong>Тарабанов Александр Викторович</strong><br>
2024-2025. Все права защищены.
</p>
<p class="copyright-status">DLE - Проприетарное ПО</p>
<div class="copyright-links">
<a href="mailto:info@hb3-accelerator.com" class="copyright-link" title="Связаться с автором">
📧 Контакты
Контакты
</a>
<a href="https://hb3-accelerator.com" target="_blank" class="copyright-link" title="Официальный сайт">
🌐 Сайт
Сайт
</a>
<a href="https://github.com/HB3-ACCELERATOR" target="_blank" class="copyright-link" title="GitHub">
📦 GitHub
GitHub
</a>
</div>
</div>

View File

@@ -205,12 +205,12 @@ const routes = [
{
path: '/management/dle',
name: 'management-dle',
component: () => import('../views/smartcontracts/DleModulesView.vue')
component: () => import('../views/smartcontracts/DleManagementView.vue')
},
{
path: '/management/dle-management',
name: 'management-dle-management',
component: () => import('../views/smartcontracts/DleModulesView.vue')
component: () => import('../views/smartcontracts/DleManagementView.vue')
},
{
path: '/management/proposals',
@@ -230,7 +230,7 @@ const routes = [
{
path: '/management/modules',
name: 'management-modules',
component: () => import('../views/smartcontracts/DleModulesView.vue')
component: () => import('../views/smartcontracts/ModulesView.vue')
},
// {
// path: '/management/multisig',
@@ -238,11 +238,7 @@ const routes = [
// component: () => import('../views/smartcontracts/DleMultisigView.vue'),
// meta: { requiresAuth: true }
// },
{
path: '/management/treasury',
name: 'management-treasury',
component: () => import('../views/smartcontracts/TreasuryView.vue')
},
{
path: '/management/analytics',
name: 'management-analytics',

View File

@@ -0,0 +1,319 @@
/**
* 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 axios from 'axios';
/**
* Получает общую статистику DLE
* @param {string} dleAddress - Адрес DLE
* @returns {Promise<Object>} - Общая статистика
*/
export const getDLEStats = async (dleAddress) => {
try {
const response = await axios.post('/dle-analytics/get-dle-stats', {
dleAddress
});
return response.data;
} catch (error) {
console.error('Ошибка при получении статистики DLE:', error);
throw error;
}
};
/**
* Получает статистику предложений
* @param {string} dleAddress - Адрес DLE
* @returns {Promise<Object>} - Статистика предложений
*/
export const getProposalsStats = async (dleAddress) => {
try {
const response = await axios.post('/blockchain/get-proposals-stats', {
dleAddress
});
return response.data;
} catch (error) {
console.error('Ошибка при получении статистики предложений:', error);
throw error;
}
};
/**
* Получает статистику токенов
* @param {string} dleAddress - Адрес DLE
* @returns {Promise<Object>} - Статистика токенов
*/
export const getTokenStats = async (dleAddress) => {
try {
const response = await axios.post('/blockchain/get-token-stats', {
dleAddress
});
return response.data;
} catch (error) {
console.error('Ошибка при получении статистики токенов:', error);
throw error;
}
};
/**
* Получает статистику модулей
* @param {string} dleAddress - Адрес DLE
* @returns {Promise<Object>} - Статистика модулей
*/
export const getModulesStats = async (dleAddress) => {
try {
const response = await axios.post('/blockchain/get-modules-stats', {
dleAddress
});
return response.data;
} catch (error) {
console.error('Ошибка при получении статистики модулей:', error);
throw error;
}
};
/**
* Получает статистику голосования
* @param {string} dleAddress - Адрес DLE
* @returns {Promise<Object>} - Статистика голосования
*/
export const getVotingStats = async (dleAddress) => {
try {
const response = await axios.post('/blockchain/get-voting-stats', {
dleAddress
});
return response.data;
} catch (error) {
console.error('Ошибка при получении статистики голосования:', error);
throw error;
}
};
/**
* Получает активность DLE по времени
* @param {string} dleAddress - Адрес DLE
* @param {string} period - Период (day, week, month, year)
* @returns {Promise<Object>} - Активность по времени
*/
export const getDLEActivity = async (dleAddress, period = 'month') => {
try {
const response = await axios.post('/dle-analytics/get-dle-activity', {
dleAddress,
period
});
return response.data;
} catch (error) {
console.error('Ошибка при получении активности DLE:', error);
throw error;
}
};
/**
* Получает топ держателей токенов
* @param {string} dleAddress - Адрес DLE
* @param {number} limit - Количество записей
* @returns {Promise<Object>} - Топ держателей
*/
export const getTopTokenHolders = async (dleAddress, limit = 10) => {
try {
const response = await axios.post('/blockchain/get-top-token-holders', {
dleAddress,
limit
});
return response.data;
} catch (error) {
console.error('Ошибка при получении топ держателей токенов:', error);
throw error;
}
};
/**
* Получает распределение токенов
* @param {string} dleAddress - Адрес DLE
* @returns {Promise<Object>} - Распределение токенов
*/
export const getTokenDistribution = async (dleAddress) => {
try {
const response = await axios.post('/blockchain/get-token-distribution', {
dleAddress
});
return response.data;
} catch (error) {
console.error('Ошибка при получении распределения токенов:', error);
throw error;
}
};
/**
* Получает историю событий
* @param {string} dleAddress - Адрес DLE
* @param {string} eventType - Тип события
* @param {number} fromBlock - Начальный блок
* @param {number} toBlock - Конечный блок
* @returns {Promise<Object>} - История событий
*/
export const getEventHistory = async (dleAddress, eventType, fromBlock, toBlock) => {
try {
const response = await axios.post('/dle-analytics/get-event-history', {
dleAddress,
eventType,
fromBlock,
toBlock
});
return response.data;
} catch (error) {
console.error('Ошибка при получении истории событий:', error);
throw error;
}
};
/**
* Получает метрики производительности
* @param {string} dleAddress - Адрес DLE
* @returns {Promise<Object>} - Метрики производительности
*/
export const getPerformanceMetrics = async (dleAddress) => {
try {
const response = await axios.post('/blockchain/get-performance-metrics', {
dleAddress
});
return response.data;
} catch (error) {
console.error('Ошибка при получении метрик производительности:', error);
throw error;
}
};
/**
* Получает аналитику по сетям
* @param {string} dleAddress - Адрес DLE
* @returns {Promise<Object>} - Аналитика по сетям
*/
export const getNetworkAnalytics = async (dleAddress) => {
try {
const response = await axios.post('/blockchain/get-network-analytics', {
dleAddress
});
return response.data;
} catch (error) {
console.error('Ошибка при получении аналитики по сетям:', error);
throw error;
}
};
/**
* Получает отчет о деятельности
* @param {string} dleAddress - Адрес DLE
* @param {string} reportType - Тип отчета
* @param {Object} filters - Фильтры
* @returns {Promise<Object>} - Отчет о деятельности
*/
export const getActivityReport = async (dleAddress, reportType, filters = {}) => {
try {
const response = await axios.post('/blockchain/get-activity-report', {
dleAddress,
reportType,
...filters
});
return response.data;
} catch (error) {
console.error('Ошибка при получении отчета о деятельности:', error);
throw error;
}
};
/**
* Получает сравнительную аналитику
* @param {string} dleAddress - Адрес DLE
* @param {string} comparisonType - Тип сравнения
* @returns {Promise<Object>} - Сравнительная аналитика
*/
export const getComparativeAnalytics = async (dleAddress, comparisonType) => {
try {
const response = await axios.post('/blockchain/get-comparative-analytics', {
dleAddress,
comparisonType
});
return response.data;
} catch (error) {
console.error('Ошибка при получении сравнительной аналитики:', error);
throw error;
}
};
/**
* Получает прогнозы и тренды
* @param {string} dleAddress - Адрес DLE
* @returns {Promise<Object>} - Прогнозы и тренды
*/
export const getTrendsAndForecasts = async (dleAddress) => {
try {
const response = await axios.post('/blockchain/get-trends-forecasts', {
dleAddress
});
return response.data;
} catch (error) {
console.error('Ошибка при получении прогнозов и трендов:', error);
throw error;
}
};
/**
* Получает аналитику рисков
* @param {string} dleAddress - Адрес DLE
* @returns {Promise<Object>} - Аналитика рисков
*/
export const getRiskAnalytics = async (dleAddress) => {
try {
const response = await axios.post('/blockchain/get-risk-analytics', {
dleAddress
});
return response.data;
} catch (error) {
console.error('Ошибка при получении аналитики рисков:', error);
throw error;
}
};
/**
* Получает ключевые показатели эффективности
* @param {string} dleAddress - Адрес DLE
* @returns {Promise<Object>} - Ключевые показатели эффективности
*/
export const getKPIs = async (dleAddress) => {
try {
const response = await axios.post('/blockchain/get-kpis', {
dleAddress
});
return response.data;
} catch (error) {
console.error('Ошибка при получении ключевых показателей эффективности:', error);
throw error;
}
};
/**
* Получает дашборд аналитики
* @param {string} dleAddress - Адрес DLE
* @returns {Promise<Object>} - Дашборд аналитики
*/
export const getAnalyticsDashboard = async (dleAddress) => {
try {
const response = await axios.post('/blockchain/get-analytics-dashboard', {
dleAddress
});
return response.data;
} catch (error) {
console.error('Ошибка при получении дашборда аналитики:', error);
throw error;
}
};

View File

@@ -10,9 +10,11 @@
* GitHub: https://github.com/HB3-ACCELERATOR
*/
// Сервис для работы с DLE v2
// Сервис для работы с DLE v2 - основные функции
import axios from 'axios';
// ===== ОСНОВНЫЕ ФУНКЦИИ DLE =====
/**
* Создает новое DLE v2
* @param {Object} dleParams - Параметры DLE
@@ -69,4 +71,150 @@ export const getDefaultParams = async () => {
console.error('Ошибка при получении параметров по умолчанию:', error);
throw error;
}
};
/**
* Читает данные DLE из блокчейна
* @param {string} dleAddress - Адрес DLE
* @returns {Promise<Object>} - Данные из блокчейна
*/
export const readDLEFromBlockchain = async (dleAddress) => {
try {
const response = await axios.post('/dle-core/read-dle-info', { dleAddress });
return response.data;
} catch (error) {
console.error('Ошибка при чтении DLE из блокчейна:', error);
throw error;
}
};
/**
* Получает параметры управления DLE
* @param {string} dleAddress - Адрес DLE
* @returns {Promise<Object>} - Параметры управления
*/
export const getGovernanceParams = async (dleAddress) => {
try {
const response = await axios.post('/dle-core/get-governance-params', { dleAddress });
return response.data;
} catch (error) {
console.error('Ошибка при получении параметров управления:', error);
throw error;
}
};
// ===== МУЛЬТИ-ЧЕЙН ФУНКЦИОНАЛЬНОСТЬ =====
/**
* Получает список поддерживаемых сетей
* @param {string} dleAddress - Адрес DLE
* @returns {Promise<Object>} - Список сетей
*/
export const getSupportedChains = async (dleAddress) => {
try {
const response = await axios.post('/dle-multichain/get-supported-chains', {
dleAddress
});
return response.data;
} catch (error) {
console.error('Ошибка при получении поддерживаемых сетей:', error);
throw error;
}
};
/**
* Проверяет поддержку сети
* @param {string} dleAddress - Адрес DLE
* @param {number} chainId - ID сети
* @returns {Promise<Object>} - Статус поддержки
*/
export const isChainSupported = async (dleAddress, chainId) => {
try {
const response = await axios.post('/dle-multichain/is-chain-supported', {
dleAddress,
chainId
});
return response.data;
} catch (error) {
console.error('Ошибка при проверке поддержки сети:', error);
throw error;
}
};
/**
* Получает текущую сеть
* @param {string} dleAddress - Адрес DLE
* @returns {Promise<Object>} - Текущая сеть
*/
export const getCurrentChainId = async (dleAddress) => {
try {
const response = await axios.post('/blockchain/get-current-chain-id', {
dleAddress
});
return response.data;
} catch (error) {
console.error('Ошибка при получении текущей сети:', error);
throw error;
}
};
/**
* Исполняет предложение по подписям
* @param {string} dleAddress - Адрес DLE
* @param {Object} executionData - Данные исполнения
* @returns {Promise<Object>} - Результат исполнения
*/
export const executeProposalBySignatures = async (dleAddress, executionData) => {
try {
const response = await axios.post('/dle-multichain/execute-proposal-by-signatures', {
dleAddress,
...executionData
});
return response.data;
} catch (error) {
console.error('Ошибка при исполнении предложения по подписям:', error);
throw error;
}
};
// ===== ИСТОРИЯ И СОБЫТИЯ =====
/**
* Получает историю событий
* @param {string} dleAddress - Адрес DLE
* @param {string} eventType - Тип события
* @param {number} fromBlock - Начальный блок
* @param {number} toBlock - Конечный блок
* @returns {Promise<Object>} - История событий
*/
export const getEventHistory = async (dleAddress, eventType, fromBlock, toBlock) => {
try {
const response = await axios.post('/blockchain/get-event-history', {
dleAddress,
eventType,
fromBlock,
toBlock
});
return response.data;
} catch (error) {
console.error('Ошибка при получении истории событий:', error);
throw error;
}
};
/**
* Получает статистику DLE
* @param {string} dleAddress - Адрес DLE
* @returns {Promise<Object>} - Статистика
*/
export const getDLEStats = async (dleAddress) => {
try {
const response = await axios.post('/blockchain/get-dle-stats', {
dleAddress
});
return response.data;
} catch (error) {
console.error('Ошибка при получении статистики DLE:', error);
throw error;
}
};

View File

@@ -0,0 +1,31 @@
/**
* 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
// Основные функции DLE
export * from './dleV2Service.js';
// Модули
export * from './modulesService.js';
// Предложения
export * from './proposalsService.js';
// Токены
export * from './tokensService.js';
// Аналитика
export * from './analyticsService.js';
// Мультичейн
export * from './multichainService.js';

View File

@@ -0,0 +1,297 @@
/**
* 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 axios from 'axios';
/**
* Создает предложение о добавлении модуля
* @param {string} dleAddress - Адрес DLE
* @param {Object} moduleData - Данные модуля
* @returns {Promise<Object>} - Результат создания
*/
export const createAddModuleProposal = async (dleAddress, moduleData) => {
try {
const response = await axios.post('/dle-modules/create-add-module-proposal', {
dleAddress,
...moduleData
});
return response.data;
} catch (error) {
console.error('Ошибка при создании предложения добавления модуля:', error);
throw error;
}
};
/**
* Создает предложение об удалении модуля
* @param {string} dleAddress - Адрес DLE
* @param {Object} moduleData - Данные модуля
* @returns {Promise<Object>} - Результат создания
*/
export const createRemoveModuleProposal = async (dleAddress, moduleData) => {
try {
const response = await axios.post('/dle-modules/create-remove-module-proposal', {
dleAddress,
...moduleData
});
return response.data;
} catch (error) {
console.error('Ошибка при создании предложения удаления модуля:', error);
throw error;
}
};
/**
* Проверяет активность модуля
* @param {string} dleAddress - Адрес DLE
* @param {string} moduleId - ID модуля
* @returns {Promise<Object>} - Статус активности
*/
export const isModuleActive = async (dleAddress, moduleId) => {
try {
const response = await axios.post('/dle-modules/is-module-active', {
dleAddress,
moduleId
});
return response.data;
} catch (error) {
console.error('Ошибка при проверке активности модуля:', error);
throw error;
}
};
/**
* Получает адрес модуля
* @param {string} dleAddress - Адрес DLE
* @param {string} moduleId - ID модуля
* @returns {Promise<Object>} - Адрес модуля
*/
export const getModuleAddress = async (dleAddress, moduleId) => {
try {
const response = await axios.post('/dle-modules/get-module-address', {
dleAddress,
moduleId
});
return response.data;
} catch (error) {
console.error('Ошибка при получении адреса модуля:', error);
throw error;
}
};
/**
* Получает список всех модулей
* @param {string} dleAddress - Адрес DLE
* @returns {Promise<Object>} - Список модулей
*/
export const getAllModules = async (dleAddress) => {
try {
const response = await axios.post('/dle-modules/get-all-modules', {
dleAddress
});
return response.data;
} catch (error) {
console.error('Ошибка при получении списка модулей:', error);
throw error;
}
};
/**
* Получает информацию о модуле
* @param {string} dleAddress - Адрес DLE
* @param {string} moduleId - ID модуля
* @returns {Promise<Object>} - Информация о модуле
*/
export const getModuleInfo = async (dleAddress, moduleId) => {
try {
const response = await axios.post('/blockchain/get-module-info', {
dleAddress,
moduleId
});
return response.data;
} catch (error) {
console.error('Ошибка при получении информации о модуле:', error);
throw error;
}
};
/**
* Получает статистику модулей
* @param {string} dleAddress - Адрес DLE
* @returns {Promise<Object>} - Статистика модулей
*/
export const getModulesStats = async (dleAddress) => {
try {
const response = await axios.post('/blockchain/get-modules-stats', {
dleAddress
});
return response.data;
} catch (error) {
console.error('Ошибка при получении статистики модулей:', error);
throw error;
}
};
/**
* Получает историю модулей
* @param {string} dleAddress - Адрес DLE
* @param {Object} filters - Фильтры
* @returns {Promise<Object>} - История модулей
*/
export const getModulesHistory = async (dleAddress, filters = {}) => {
try {
const response = await axios.post('/blockchain/get-modules-history', {
dleAddress,
...filters
});
return response.data;
} catch (error) {
console.error('Ошибка при получении истории модулей:', error);
throw error;
}
};
/**
* Получает активные модули
* @param {string} dleAddress - Адрес DLE
* @returns {Promise<Object>} - Активные модули
*/
export const getActiveModules = async (dleAddress) => {
try {
const response = await axios.post('/blockchain/get-active-modules', {
dleAddress
});
return response.data;
} catch (error) {
console.error('Ошибка при получении активных модулей:', error);
throw error;
}
};
/**
* Получает неактивные модули
* @param {string} dleAddress - Адрес DLE
* @returns {Promise<Object>} - Неактивные модули
*/
export const getInactiveModules = async (dleAddress) => {
try {
const response = await axios.post('/blockchain/get-inactive-modules', {
dleAddress
});
return response.data;
} catch (error) {
console.error('Ошибка при получении неактивных модулей:', error);
throw error;
}
};
/**
* Проверяет совместимость модуля
* @param {string} dleAddress - Адрес DLE
* @param {string} moduleId - ID модуля
* @param {string} moduleAddress - Адрес модуля
* @returns {Promise<Object>} - Совместимость модуля
*/
export const checkModuleCompatibility = async (dleAddress, moduleId, moduleAddress) => {
try {
const response = await axios.post('/blockchain/check-module-compatibility', {
dleAddress,
moduleId,
moduleAddress
});
return response.data;
} catch (error) {
console.error('Ошибка при проверке совместимости модуля:', error);
throw error;
}
};
/**
* Получает конфигурацию модуля
* @param {string} dleAddress - Адрес DLE
* @param {string} moduleId - ID модуля
* @returns {Promise<Object>} - Конфигурация модуля
*/
export const getModuleConfig = async (dleAddress, moduleId) => {
try {
const response = await axios.post('/blockchain/get-module-config', {
dleAddress,
moduleId
});
return response.data;
} catch (error) {
console.error('Ошибка при получении конфигурации модуля:', error);
throw error;
}
};
/**
* Обновляет конфигурацию модуля
* @param {string} dleAddress - Адрес DLE
* @param {string} moduleId - ID модуля
* @param {Object} config - Новая конфигурация
* @returns {Promise<Object>} - Результат обновления
*/
export const updateModuleConfig = async (dleAddress, moduleId, config) => {
try {
const response = await axios.post('/blockchain/update-module-config', {
dleAddress,
moduleId,
config
});
return response.data;
} catch (error) {
console.error('Ошибка при обновлении конфигурации модуля:', error);
throw error;
}
};
/**
* Получает события модуля
* @param {string} dleAddress - Адрес DLE
* @param {string} moduleId - ID модуля
* @param {Object} filters - Фильтры
* @returns {Promise<Object>} - События модуля
*/
export const getModuleEvents = async (dleAddress, moduleId, filters = {}) => {
try {
const response = await axios.post('/blockchain/get-module-events', {
dleAddress,
moduleId,
...filters
});
return response.data;
} catch (error) {
console.error('Ошибка при получении событий модуля:', error);
throw error;
}
};
/**
* Получает производительность модуля
* @param {string} dleAddress - Адрес DLE
* @param {string} moduleId - ID модуля
* @returns {Promise<Object>} - Производительность модуля
*/
export const getModulePerformance = async (dleAddress, moduleId) => {
try {
const response = await axios.post('/blockchain/get-module-performance', {
dleAddress,
moduleId
});
return response.data;
} catch (error) {
console.error('Ошибка при получении производительности модуля:', error);
throw error;
}
};

View File

@@ -0,0 +1,352 @@
/**
* 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 axios from 'axios';
/**
* Получает список поддерживаемых сетей
* @param {string} dleAddress - Адрес DLE
* @returns {Promise<Object>} - Список сетей
*/
export const getSupportedChains = async (dleAddress) => {
try {
const response = await axios.post('/dle-multichain/get-supported-chains', {
dleAddress
});
return response.data;
} catch (error) {
console.error('Ошибка при получении поддерживаемых сетей:', error);
throw error;
}
};
/**
* Проверяет поддержку сети
* @param {string} dleAddress - Адрес DLE
* @param {number} chainId - ID сети
* @returns {Promise<Object>} - Статус поддержки
*/
export const isChainSupported = async (dleAddress, chainId) => {
try {
const response = await axios.post('/dle-multichain/is-chain-supported', {
dleAddress,
chainId
});
return response.data;
} catch (error) {
console.error('Ошибка при проверке поддержки сети:', error);
throw error;
}
};
/**
* Получает текущую сеть
* @param {string} dleAddress - Адрес DLE
* @returns {Promise<Object>} - Текущая сеть
*/
export const getCurrentChainId = async (dleAddress) => {
try {
const response = await axios.post('/blockchain/get-current-chain-id', {
dleAddress
});
return response.data;
} catch (error) {
console.error('Ошибка при получении текущей сети:', error);
throw error;
}
};
/**
* Исполняет предложение по подписям
* @param {string} dleAddress - Адрес DLE
* @param {Object} executionData - Данные исполнения
* @returns {Promise<Object>} - Результат исполнения
*/
export const executeProposalBySignatures = async (dleAddress, executionData) => {
try {
const response = await axios.post('/dle-multichain/execute-proposal-by-signatures', {
dleAddress,
...executionData
});
return response.data;
} catch (error) {
console.error('Ошибка при исполнении предложения по подписям:', error);
throw error;
}
};
/**
* Проверяет готовность синхронизации
* @param {string} dleAddress - Адрес DLE
* @param {number} proposalId - ID предложения
* @returns {Promise<Object>} - Готовность синхронизации
*/
export const checkSyncReadiness = async (dleAddress, proposalId) => {
try {
const response = await axios.post('/dle-multichain/check-sync-readiness', {
dleAddress,
proposalId
});
return response.data;
} catch (error) {
console.error('Ошибка при проверке готовности синхронизации:', error);
throw error;
}
};
/**
* Синхронизирует предложение во все сети
* @param {string} dleAddress - Адрес DLE
* @param {number} proposalId - ID предложения
* @returns {Promise<Object>} - Результат синхронизации
*/
export const syncToAllChains = async (dleAddress, proposalId) => {
try {
const response = await axios.post('/dle-multichain/sync-to-all-chains', {
dleAddress,
proposalId
});
return response.data;
} catch (error) {
console.error('Ошибка при синхронизации во все сети:', error);
throw error;
}
};
/**
* Получает статус синхронизации
* @param {string} dleAddress - Адрес DLE
* @param {number} proposalId - ID предложения
* @returns {Promise<Object>} - Статус синхронизации
*/
export const getSyncStatus = async (dleAddress, proposalId) => {
try {
const response = await axios.post('/blockchain/get-sync-status', {
dleAddress,
proposalId
});
return response.data;
} catch (error) {
console.error('Ошибка при получении статуса синхронизации:', error);
throw error;
}
};
/**
* Получает информацию о сети
* @param {number} chainId - ID сети
* @returns {Promise<Object>} - Информация о сети
*/
export const getChainInfo = async (chainId) => {
try {
const response = await axios.post('/blockchain/get-chain-info', {
chainId
});
return response.data;
} catch (error) {
console.error('Ошибка при получении информации о сети:', error);
throw error;
}
};
/**
* Получает RPC URL для сети
* @param {number} chainId - ID сети
* @returns {Promise<Object>} - RPC URL
*/
export const getRpcUrl = async (chainId) => {
try {
const response = await axios.post('/blockchain/get-rpc-url', {
chainId
});
return response.data;
} catch (error) {
console.error('Ошибка при получении RPC URL:', error);
throw error;
}
};
/**
* Проверяет подключение к сети
* @param {string} dleAddress - Адрес DLE
* @param {number} chainId - ID сети
* @returns {Promise<Object>} - Статус подключения
*/
export const checkChainConnection = async (dleAddress, chainId) => {
try {
const response = await axios.post('/dle-multichain/check-chain-connection', {
dleAddress,
chainId
});
return response.data;
} catch (error) {
console.error('Ошибка при проверке подключения к сети:', error);
throw error;
}
};
/**
* Получает баланс в сети
* @param {string} dleAddress - Адрес DLE
* @param {string} userAddress - Адрес пользователя
* @param {number} chainId - ID сети
* @returns {Promise<Object>} - Баланс в сети
*/
export const getChainBalance = async (dleAddress, userAddress, chainId) => {
try {
const response = await axios.post('/blockchain/get-chain-balance', {
dleAddress,
userAddress,
chainId
});
return response.data;
} catch (error) {
console.error('Ошибка при получении баланса в сети:', error);
throw error;
}
};
/**
* Получает предложения в сети
* @param {string} dleAddress - Адрес DLE
* @param {number} chainId - ID сети
* @returns {Promise<Object>} - Предложения в сети
*/
export const getChainProposals = async (dleAddress, chainId) => {
try {
const response = await axios.post('/blockchain/get-chain-proposals', {
dleAddress,
chainId
});
return response.data;
} catch (error) {
console.error('Ошибка при получении предложений в сети:', error);
throw error;
}
};
/**
* Получает модули в сети
* @param {string} dleAddress - Адрес DLE
* @param {number} chainId - ID сети
* @returns {Promise<Object>} - Модули в сети
*/
export const getChainModules = async (dleAddress, chainId) => {
try {
const response = await axios.post('/blockchain/get-chain-modules', {
dleAddress,
chainId
});
return response.data;
} catch (error) {
console.error('Ошибка при получении модулей в сети:', error);
throw error;
}
};
/**
* Получает статистику по сетям
* @param {string} dleAddress - Адрес DLE
* @returns {Promise<Object>} - Статистика по сетям
*/
export const getChainsStats = async (dleAddress) => {
try {
const response = await axios.post('/blockchain/get-chains-stats', {
dleAddress
});
return response.data;
} catch (error) {
console.error('Ошибка при получении статистики по сетям:', error);
throw error;
}
};
/**
* Получает события синхронизации
* @param {string} dleAddress - Адрес DLE
* @param {Object} filters - Фильтры
* @returns {Promise<Object>} - События синхронизации
*/
export const getSyncEvents = async (dleAddress, filters = {}) => {
try {
const response = await axios.post('/blockchain/get-sync-events', {
dleAddress,
...filters
});
return response.data;
} catch (error) {
console.error('Ошибка при получении событий синхронизации:', error);
throw error;
}
};
/**
* Получает подписи для исполнения
* @param {string} dleAddress - Адрес DLE
* @param {number} proposalId - ID предложения
* @param {number} chainId - ID сети
* @returns {Promise<Object>} - Подписи для исполнения
*/
export const getExecutionSignatures = async (dleAddress, proposalId, chainId) => {
try {
const response = await axios.post('/blockchain/get-execution-signatures', {
dleAddress,
proposalId,
chainId
});
return response.data;
} catch (error) {
console.error('Ошибка при получении подписей для исполнения:', error);
throw error;
}
};
/**
* Создает подпись для исполнения
* @param {string} dleAddress - Адрес DLE
* @param {number} proposalId - ID предложения
* @param {number} chainId - ID сети
* @param {string} userAddress - Адрес пользователя
* @returns {Promise<Object>} - Результат создания подписи
*/
export const createExecutionSignature = async (dleAddress, proposalId, chainId, userAddress) => {
try {
const response = await axios.post('/blockchain/create-execution-signature', {
dleAddress,
proposalId,
chainId,
userAddress
});
return response.data;
} catch (error) {
console.error('Ошибка при создании подписи для исполнения:', error);
throw error;
}
};
/**
* Получает аналитику по сетям
* @param {string} dleAddress - Адрес DLE
* @returns {Promise<Object>} - Аналитика по сетям
*/
export const getChainsAnalytics = async (dleAddress) => {
try {
const response = await axios.post('/blockchain/get-chains-analytics', {
dleAddress
});
return response.data;
} catch (error) {
console.error('Ошибка при получении аналитики по сетям:', error);
throw error;
}
};

View File

@@ -0,0 +1,263 @@
/**
* 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 axios from 'axios';
/**
* Получает список всех предложений
* @param {string} dleAddress - Адрес DLE
* @returns {Promise<Object>} - Список предложений
*/
export const getProposals = async (dleAddress) => {
try {
const response = await axios.post('/dle-proposals/get-proposals', { dleAddress });
return response.data;
} catch (error) {
console.error('Ошибка при получении предложений:', error);
throw error;
}
};
/**
* Получает информацию о конкретном предложении
* @param {string} dleAddress - Адрес DLE
* @param {number} proposalId - ID предложения
* @returns {Promise<Object>} - Информация о предложении
*/
export const getProposalInfo = async (dleAddress, proposalId) => {
try {
const response = await axios.post('/dle-proposals/get-proposal-info', {
dleAddress,
proposalId
});
return response.data;
} catch (error) {
console.error('Ошибка при получении информации о предложении:', error);
throw error;
}
};
/**
* Создает новое предложение
* @param {string} dleAddress - Адрес DLE
* @param {Object} proposalData - Данные предложения
* @returns {Promise<Object>} - Результат создания
*/
export const createProposal = async (dleAddress, proposalData) => {
try {
const response = await axios.post('/dle-proposals/create-proposal', {
dleAddress,
...proposalData
});
return response.data;
} catch (error) {
console.error('Ошибка при создании предложения:', error);
throw error;
}
};
/**
* Голосует за предложение
* @param {string} dleAddress - Адрес DLE
* @param {number} proposalId - ID предложения
* @param {boolean} support - Поддержка предложения
* @returns {Promise<Object>} - Результат голосования
*/
export const voteOnProposal = async (dleAddress, proposalId, support) => {
try {
const response = await axios.post('/dle-proposals/vote-proposal', {
dleAddress,
proposalId,
support
});
return response.data;
} catch (error) {
console.error('Ошибка при голосовании:', error);
throw error;
}
};
/**
* Исполняет предложение
* @param {string} dleAddress - Адрес DLE
* @param {number} proposalId - ID предложения
* @returns {Promise<Object>} - Результат исполнения
*/
export const executeProposal = async (dleAddress, proposalId) => {
try {
const response = await axios.post('/dle-proposals/execute-proposal', {
dleAddress,
proposalId
});
return response.data;
} catch (error) {
console.error('Ошибка при исполнении предложения:', error);
throw error;
}
};
/**
* Отменяет предложение
* @param {string} dleAddress - Адрес DLE
* @param {number} proposalId - ID предложения
* @param {string} reason - Причина отмены
* @returns {Promise<Object>} - Результат отмены
*/
export const cancelProposal = async (dleAddress, proposalId, reason) => {
try {
const response = await axios.post('/dle-proposals/cancel-proposal', {
dleAddress,
proposalId,
reason
});
return response.data;
} catch (error) {
console.error('Ошибка при отмене предложения:', error);
throw error;
}
};
/**
* Получает состояние предложения
* @param {string} dleAddress - Адрес DLE
* @param {number} proposalId - ID предложения
* @returns {Promise<Object>} - Состояние предложения
*/
export const getProposalState = async (dleAddress, proposalId) => {
try {
const response = await axios.post('/dle-proposals/get-proposal-state', {
dleAddress,
proposalId
});
return response.data;
} catch (error) {
console.error('Ошибка при получении состояния предложения:', error);
throw error;
}
};
/**
* Получает голоса по предложению
* @param {string} dleAddress - Адрес DLE
* @param {number} proposalId - ID предложения
* @returns {Promise<Object>} - Голоса по предложению
*/
export const getProposalVotes = async (dleAddress, proposalId) => {
try {
const response = await axios.post('/dle-proposals/get-proposal-votes', {
dleAddress,
proposalId
});
return response.data;
} catch (error) {
console.error('Ошибка при получении голосов:', error);
throw error;
}
};
/**
* Проверяет результат предложения
* @param {string} dleAddress - Адрес DLE
* @param {number} proposalId - ID предложения
* @returns {Promise<Object>} - Результат проверки
*/
export const checkProposalResult = async (dleAddress, proposalId) => {
try {
const response = await axios.post('/dle-proposals/check-proposal-result', {
dleAddress,
proposalId
});
return response.data;
} catch (error) {
console.error('Ошибка при проверке результата предложения:', error);
throw error;
}
};
/**
* Получает количество предложений
* @param {string} dleAddress - Адрес DLE
* @returns {Promise<Object>} - Количество предложений
*/
export const getProposalsCount = async (dleAddress) => {
try {
const response = await axios.post('/dle-proposals/get-proposals-count', {
dleAddress
});
return response.data;
} catch (error) {
console.error('Ошибка при получении количества предложений:', error);
throw error;
}
};
/**
* Получает список предложений с пагинацией
* @param {string} dleAddress - Адрес DLE
* @param {number} offset - Смещение
* @param {number} limit - Лимит
* @returns {Promise<Object>} - Список предложений
*/
export const listProposals = async (dleAddress, offset = 0, limit = 10) => {
try {
const response = await axios.post('/dle-proposals/list-proposals', {
dleAddress,
offset,
limit
});
return response.data;
} catch (error) {
console.error('Ошибка при получении списка предложений:', error);
throw error;
}
};
/**
* Получает голосующую силу на момент времени
* @param {string} dleAddress - Адрес DLE
* @param {string} voter - Адрес голосующего
* @param {number} timepoint - Временная точка
* @returns {Promise<Object>} - Голосующая сила
*/
export const getVotingPowerAt = async (dleAddress, voter, timepoint) => {
try {
const response = await axios.post('/dle-proposals/get-voting-power-at', {
dleAddress,
voter,
timepoint
});
return response.data;
} catch (error) {
console.error('Ошибка при получении голосующей силы:', error);
throw error;
}
};
/**
* Получает требуемый кворум на момент времени
* @param {string} dleAddress - Адрес DLE
* @param {number} timepoint - Временная точка
* @returns {Promise<Object>} - Требуемый кворум
*/
export const getQuorumAt = async (dleAddress, timepoint) => {
try {
const response = await axios.post('/dle-proposals/get-quorum-at', {
dleAddress,
timepoint
});
return response.data;
} catch (error) {
console.error('Ошибка при получении требуемого кворума:', error);
throw error;
}
};

View File

@@ -10,28 +10,174 @@
* GitHub: https://github.com/HB3-ACCELERATOR
*/
import api from '../api/axios';
// Сервис для работы с токенами DLE
import axios from 'axios';
// Получение балансов токенов
export const fetchTokenBalances = async (address = null) => {
/**
* Получает балансы токенов для пользователя
* @param {string} userAddress - Адрес пользователя
* @returns {Promise<Object>} - Балансы токенов
*/
export const getTokenBalances = async (userAddress) => {
try {
let url = '/tokens/balances';
if (address) {
url += `?address=${encodeURIComponent(address)}`;
// console.log(`Fetching token balances for specific address: ${address}`);
} else {
// console.log('Fetching token balances for session user');
}
const response = await api.get(url);
const response = await axios.get(`/tokens/balances/${userAddress}`);
return response.data;
} catch (error) {
// console.error('Error fetching token balances:', error);
return {
eth: '0',
bsc: '0',
arbitrum: '0',
polygon: '0',
sepolia: '0',
};
console.error('Ошибка при получении балансов токенов:', error);
throw error;
}
};
/**
* Получает баланс токенов конкретного DLE
* @param {string} dleAddress - Адрес DLE
* @param {string} userAddress - Адрес пользователя
* @returns {Promise<Object>} - Баланс токенов
*/
export const getDLEBalance = async (dleAddress, userAddress) => {
try {
const response = await axios.post('/blockchain/get-token-balance', {
dleAddress,
account: userAddress
});
return response.data;
} catch (error) {
console.error('Ошибка при получении баланса DLE:', error);
throw error;
}
};
/**
* Получает общее предложение токенов DLE
* @param {string} dleAddress - Адрес DLE
* @returns {Promise<Object>} - Общее предложение
*/
export const getDLETotalSupply = async (dleAddress) => {
try {
const response = await axios.post('/blockchain/get-total-supply', {
dleAddress
});
return response.data;
} catch (error) {
console.error('Ошибка при получении общего предложения DLE:', error);
throw error;
}
};
/**
* Получает список держателей токенов DLE
* @param {string} dleAddress - Адрес DLE
* @param {number} offset - Смещение
* @param {number} limit - Лимит
* @returns {Promise<Object>} - Список держателей
*/
export const getDLETokenHolders = async (dleAddress, offset = 0, limit = 10) => {
try {
const response = await axios.post('/blockchain/get-token-holders', {
dleAddress,
offset,
limit
});
return response.data;
} catch (error) {
console.error('Ошибка при получении держателей токенов DLE:', error);
throw error;
}
};
/**
* Получает голосующую силу пользователя на момент времени
* @param {string} dleAddress - Адрес DLE
* @param {string} userAddress - Адрес пользователя
* @param {number} timepoint - Временная точка
* @returns {Promise<Object>} - Голосующая сила
*/
export const getVotingPower = async (dleAddress, userAddress, timepoint) => {
try {
const response = await axios.post('/blockchain/get-voting-power-at', {
dleAddress,
voter: userAddress,
timepoint
});
return response.data;
} catch (error) {
console.error('Ошибка при получении голосующей силы:', error);
throw error;
}
};
/**
* Получает требуемый кворум на момент времени
* @param {string} dleAddress - Адрес DLE
* @param {number} timepoint - Временная точка
* @returns {Promise<Object>} - Требуемый кворум
*/
export const getQuorumRequirement = async (dleAddress, timepoint) => {
try {
const response = await axios.post('/blockchain/get-quorum-at', {
dleAddress,
timepoint
});
return response.data;
} catch (error) {
console.error('Ошибка при получении требуемого кворума:', error);
throw error;
}
};
/**
* Получает статистику токенов DLE
* @param {string} dleAddress - Адрес DLE
* @returns {Promise<Object>} - Статистика токенов
*/
export const getTokenStats = async (dleAddress) => {
try {
const response = await axios.post('/blockchain/get-token-stats', {
dleAddress
});
return response.data;
} catch (error) {
console.error('Ошибка при получении статистики токенов:', error);
throw error;
}
};
/**
* Получает историю транзакций токенов
* @param {string} dleAddress - Адрес DLE
* @param {string} userAddress - Адрес пользователя (опционально)
* @param {number} fromBlock - Начальный блок
* @param {number} toBlock - Конечный блок
* @returns {Promise<Object>} - История транзакций
*/
export const getTokenTransactionHistory = async (dleAddress, userAddress = null, fromBlock = null, toBlock = null) => {
try {
const response = await axios.post('/blockchain/get-token-transactions', {
dleAddress,
userAddress,
fromBlock,
toBlock
});
return response.data;
} catch (error) {
console.error('Ошибка при получении истории транзакций токенов:', error);
throw error;
}
};
/**
* Получает распределение токенов
* @param {string} dleAddress - Адрес DLE
* @returns {Promise<Object>} - Распределение токенов
*/
export const getTokenDistribution = async (dleAddress) => {
try {
const response = await axios.post('/blockchain/get-token-distribution', {
dleAddress
});
return response.data;
} catch (error) {
console.error('Ошибка при получении распределения токенов:', error);
throw error;
}
};

View File

@@ -0,0 +1,71 @@
/**
* 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 axios from 'axios';
/**
* Получает баланс токенов
* @param {string} dleAddress - Адрес DLE
* @param {string} account - Адрес аккаунта
* @returns {Promise<Object>} - Баланс токенов
*/
export const getTokenBalance = async (dleAddress, account) => {
try {
const response = await axios.post('/dle-tokens/get-token-balance', {
dleAddress,
account
});
return response.data;
} catch (error) {
console.error('Ошибка при получении баланса токенов:', error);
throw error;
}
};
/**
* Получает общее предложение токенов
* @param {string} dleAddress - Адрес DLE
* @returns {Promise<Object>} - Общее предложение
*/
export const getTotalSupply = async (dleAddress) => {
try {
const response = await axios.post('/dle-tokens/get-total-supply', {
dleAddress
});
return response.data;
} catch (error) {
console.error('Ошибка при получении общего предложения:', error);
throw error;
}
};
/**
* Получает список держателей токенов
* @param {string} dleAddress - Адрес DLE
* @param {number} offset - Смещение
* @param {number} limit - Лимит
* @returns {Promise<Object>} - Список держателей
*/
export const getTokenHolders = async (dleAddress, offset = 0, limit = 10) => {
try {
const response = await axios.post('/dle-tokens/get-token-holders', {
dleAddress,
offset,
limit
});
return response.data;
} catch (error) {
console.error('Ошибка при получении держателей токенов:', error);
throw error;
}
};

View File

@@ -306,6 +306,30 @@ export async function isModuleActive(dleAddress, moduleId) {
}
}
/**
* Получить адрес модуля
* @param {string} dleAddress - Адрес DLE контракта
* @param {string} moduleId - ID модуля
* @returns {Promise<string>} - Адрес модуля
*/
export async function getModuleAddress(dleAddress, moduleId) {
try {
const response = await axios.post('/dle-modules/get-module-address', {
dleAddress: dleAddress,
moduleId: moduleId
});
if (response.data.success) {
return response.data.data.moduleAddress;
} else {
throw new Error(response.data.message || 'Не удалось получить адрес модуля');
}
} catch (error) {
console.error('Ошибка получения адреса модуля:', error);
return '';
}
}
/**
* Проверить, поддерживается ли цепочка
* @param {string} dleAddress - Адрес DLE контракта

View File

@@ -150,11 +150,7 @@
<button class="details-btn" @click="openModulesWithDle">Подробнее</button>
</div>
<div class="management-block">
<h3>Казна</h3>
<p>Управление средствами</p>
<button class="details-btn" @click="openTreasuryWithDle">Подробнее</button>
</div>
<div class="management-block">
<h3>Аналитика</h3>
@@ -177,11 +173,7 @@
<button class="details-btn" @click="openSettingsWithDle">Подробнее</button>
</div>
<div class="management-block">
<h3>Мультиподпись</h3>
<p>Управление мультиподписью</p>
<!-- Мультиподпись удалена - используется только голосование -->
</div>
</div>
</div>
@@ -241,9 +233,7 @@ const openDle = () => {
router.push('/management/dle-management');
};
const openTreasury = () => {
router.push('/management/treasury');
};
const openAnalytics = () => {
router.push('/management/analytics');
@@ -430,11 +420,7 @@ function openModulesWithDle() {
}
}
function openTreasuryWithDle() {
if (selectedDle.value) {
router.push(`/management/treasury?address=${selectedDle.value.dleAddress}`);
}
}
function openAnalyticsWithDle() {
if (selectedDle.value) {

File diff suppressed because it is too large Load Diff

View File

@@ -530,9 +530,11 @@
<script setup>
import { ref, computed, onMounted, onUnmounted, watch, defineProps, defineEmits, inject } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import { useAuthContext } from '@/composables/useAuth';
import { useAuthContext } from '../../composables/useAuth';
import BaseLayout from '../../components/BaseLayout.vue';
import { getDLEInfo, loadProposals, createProposal as createProposalAPI, voteForProposal as voteForProposalAPI, executeProposal as executeProposalAPI, getSupportedChains } from '../../utils/dle-contract.js';
import { getDLEInfo, getSupportedChains } from '../../services/dleV2Service.js';
import { getProposals, createProposal as createProposalAPI, voteOnProposal as voteForProposalAPI, executeProposal as executeProposalAPI } from '../../services/proposalsService.js';
import api from '../../api/axios';
const showTargetChains = computed(() => {
// Для offchain-действий не требуется ончейн исполнение (здесь типы пока ончейн)
// Можно расширить логику при появлении offchain типа
@@ -648,13 +650,24 @@ async function loadDleData() {
isLoadingDle.value = true;
try {
// Загружаем данные DLE из блокчейна
const dleData = await getDLEInfo(dleAddress.value);
selectedDle.value = dleData;
console.log('Загружены данные DLE из блокчейна:', dleData);
const response = await api.post('/dle-core/read-dle-info', {
dleAddress: dleAddress.value
});
if (response.data.success) {
selectedDle.value = response.data.data;
console.log('Загружены данные DLE из блокчейна:', selectedDle.value);
} else {
console.error('Ошибка загрузки DLE:', response.data.error);
}
// Загружаем предложения
const proposalsData = await loadProposals(dleAddress.value);
console.log('[Frontend] Загруженные предложения из API:', proposalsData);
const proposalsResponse = await getProposals(dleAddress.value);
console.log('[Frontend] Загруженные предложения из API:', proposalsResponse);
// Извлекаем массив предложений из ответа API
const proposalsData = proposalsResponse.data?.proposals || [];
console.log('[Frontend] Массив предложений:', proposalsData);
// Преобразуем данные из API в формат для frontend
proposals.value = proposalsData.map(proposal => {
@@ -670,8 +683,8 @@ async function loadDleData() {
console.log('[Frontend] Итоговый список предложений:', proposals.value);
// Загружаем поддерживаемые цепочки
const chainsData = await getSupportedChains(dleAddress.value);
availableChains.value = chainsData;
const chainsResponse = await getSupportedChains(dleAddress.value);
availableChains.value = chainsResponse.data?.chains || [];
} catch (error) {
@@ -712,8 +725,10 @@ function validateAddress(address) {
function getChainName(chainId) {
// Сначала ищем в availableChains
const chain = availableChains.value.find(c => c.chainId === chainId);
if (chain) return chain.name;
if (Array.isArray(availableChains.value)) {
const chain = availableChains.value.find(c => c.chainId === chainId);
if (chain) return chain.name;
}
// Если не найдено, используем известные chain ID
const knownChains = {
@@ -787,42 +802,30 @@ function getProposalStatus(proposal) {
return 'executed';
}
// Используем isPassed из API, если доступно
if (proposal.isPassed !== undefined) {
if (proposal.isPassed) {
// Проверяем дедлайн
if (deadline > 0 && now >= deadline) {
// Если дедлайн истек, определяем результат по голосам
const forVotes = Number(proposal.forVotes) || 0;
const againstVotes = Number(proposal.againstVotes) || 0;
if (forVotes > againstVotes) {
return 'succeeded';
} else if (deadline > 0 && now >= deadline) {
return 'defeated';
} else {
return 'active';
return 'defeated';
}
}
// Fallback логика для старых данных
const quorumPercentage = getQuorumPercentage(proposal);
const requiredQuorum = getRequiredQuorum();
const hasReachedQuorum = quorumPercentage >= requiredQuorum;
// Если дедлайн не истек, но есть голоса, определяем текущий статус
const forVotes = Number(proposal.forVotes) || 0;
const againstVotes = Number(proposal.againstVotes) || 0;
// Добавляем отладочную информацию
console.log('[getProposalStatus] Проверка предложения:', {
proposalId: proposal.id,
now,
deadline,
deadlinePassed: deadline > 0 && now >= deadline,
quorumPercentage,
requiredQuorum,
hasReachedQuorum,
isPassed: proposal.isPassed
});
// Если кворум достигнут, предложение можно выполнить
if (hasReachedQuorum) {
return 'succeeded';
}
// Если дедлайн истек, но кворум не достигнут
if (deadline > 0 && now >= deadline) {
return 'defeated';
// Если есть голоса, определяем результат
if (forVotes > 0 || againstVotes > 0) {
if (forVotes > againstVotes) {
return 'succeeded';
} else if (againstVotes > forVotes) {
return 'defeated';
}
}
return 'active';
@@ -840,6 +843,18 @@ function getProposalStatusText(status) {
return statusMap[status] || status;
}
function getProposalStatusClass(status) {
const classMap = {
'pending': 'status-pending',
'active': 'status-active',
'succeeded': 'status-success',
'defeated': 'status-defeated',
'executed': 'status-executed',
'canceled': 'status-canceled'
};
return classMap[status] || 'status-default';
}
function decodeOperation(operation) {
if (!operation || operation === '0x') return 'Нет операции';

View File

@@ -22,8 +22,10 @@
<!-- Заголовок -->
<div class="page-header">
<div class="header-content">
<h1>История</h1>
<p>Лог операций, события и транзакции DLE</p>
<h1>История DLE</h1>
<p v-if="selectedDle">{{ selectedDle.name }} ({{ selectedDle.symbol }}) - {{ selectedDle.dleAddress }}</p>
<p v-else-if="isLoadingDle">Загрузка...</p>
<p v-else>Лог операций, события и транзакции DLE</p>
</div>
<button class="close-btn" @click="router.push('/management')">×</button>
</div>
@@ -37,12 +39,18 @@
<label for="eventType">Тип события:</label>
<select id="eventType" v-model="filters.eventType">
<option value="">Все события</option>
<option value="proposal">Предложения</option>
<option value="vote">Голосования</option>
<option value="transfer">Трансферы</option>
<option value="treasury">Казна</option>
<option value="module">Модули</option>
<option value="settings">Настройки</option>
<option value="dle_created">Создание DLE</option>
<option value="proposal_created">Создание предложений</option>
<option value="proposal_executed">Исполнение предложений</option>
<option value="proposal_cancelled">Отмена предложений</option>
<option value="module_added">Добавление модулей</option>
<option value="module_removed">Удаление модулей</option>
<option value="chain_added">Добавление сетей</option>
<option value="chain_removed">Удаление сетей</option>
<option value="chain_updated">Изменение текущей сети</option>
<option value="quorum_updated">Изменение кворума</option>
<option value="dle_info_updated">Обновление информации DLE</option>
<option value="proposal_execution_approved">Одобрение исполнения</option>
</select>
</div>
@@ -64,15 +72,7 @@
>
</div>
<div class="filter-group">
<label for="status">Статус:</label>
<select id="status" v-model="filters.status">
<option value="">Все статусы</option>
<option value="success">Успешно</option>
<option value="pending">В обработке</option>
<option value="failed">Ошибка</option>
</select>
</div>
<!-- Убираем фильтр по статусу, так как все события успешны -->
</div>
<div class="filters-actions">
@@ -87,33 +87,33 @@
<h2>Статистика</h2>
<div class="stats-grid">
<div class="stat-card">
<h3>Всего операций</h3>
<h3>Всего событий</h3>
<p class="stat-value">{{ totalOperations }}</p>
</div>
<div class="stat-card">
<h3>Успешных</h3>
<p class="stat-value success">{{ successfulOperations }}</p>
<h3>Предложения</h3>
<p class="stat-value">{{ history.filter(e => e.type.includes('proposal')).length }}</p>
</div>
<div class="stat-card">
<h3>Ошибок</h3>
<p class="stat-value error">{{ failedOperations }}</p>
<h3>Модули</h3>
<p class="stat-value">{{ history.filter(e => e.type.includes('module')).length }}</p>
</div>
<div class="stat-card">
<h3>В обработке</h3>
<p class="stat-value pending">{{ pendingOperations }}</p>
<h3>Сети</h3>
<p class="stat-value">{{ history.filter(e => e.type.includes('chain')).length }}</p>
</div>
</div>
</div>
<!-- История операций -->
<!-- История событий -->
<div class="history-section">
<h2>История операций</h2>
<h2>История событий</h2>
<div class="history-controls">
<div class="search-box">
<input
v-model="searchQuery"
type="text"
placeholder="Поиск по описанию или адресу..."
placeholder="Поиск по названию или описанию события..."
@input="filterHistory"
>
</div>
@@ -121,7 +121,7 @@
<select v-model="sortBy" @change="sortHistory">
<option value="timestamp">По дате</option>
<option value="type">По типу</option>
<option value="status">По статусу</option>
<option value="title">По названию</option>
</select>
<button @click="toggleSortOrder" class="sort-btn">
{{ sortOrder === 'desc' ? '↓' : '↑' }}
@@ -130,14 +130,14 @@
</div>
<div v-if="filteredHistory.length === 0" class="empty-state">
<p>Нет операций, соответствующих фильтрам</p>
<p>Нет событий, соответствующих фильтрам</p>
</div>
<div v-else class="history-list">
<div
v-for="event in filteredHistory"
:key="event.id"
class="history-item"
:class="event.status"
:class="event.type"
>
<div class="event-icon">
<span class="icon">{{ getEventIcon(event.type) }}</span>
@@ -146,8 +146,8 @@
<div class="event-content">
<div class="event-header">
<h3>{{ getEventTitle(event) }}</h3>
<span class="event-status" :class="event.status">
{{ getStatusText(event.status) }}
<span class="event-status success">
Успешно
</span>
</div>
@@ -158,8 +158,8 @@
<span class="event-hash">Tx: {{ formatHash(event.transactionHash) }}</span>
<span v-if="event.blockNumber" class="event-block">Block: {{ event.blockNumber }}</span>
</div>
<div v-if="event.data" class="event-data">
<div v-for="(value, key) in event.data" :key="key" class="data-item">
<div v-if="event.details" class="event-data">
<div v-for="(value, key) in event.details" :key="key" class="data-item">
<span class="data-label">{{ key }}:</span>
<span class="data-value">{{ formatDataValue(value) }}</span>
</div>
@@ -217,8 +217,8 @@
</div>
<div class="detail-row">
<span class="detail-label">Статус:</span>
<span class="detail-value" :class="selectedEvent.status">
{{ getStatusText(selectedEvent.status) }}
<span class="detail-value success">
Успешно
</span>
</div>
<div class="detail-row">
@@ -237,11 +237,11 @@
<span class="detail-label">Описание:</span>
<span class="detail-value">{{ selectedEvent.description }}</span>
</div>
<div v-if="selectedEvent.data" class="detail-section">
<h4>Данные операции:</h4>
<div v-if="selectedEvent.details" class="detail-section">
<h4>Детали события:</h4>
<div class="data-grid">
<div
v-for="(value, key) in selectedEvent.data"
v-for="(value, key) in selectedEvent.details"
:key="key"
class="data-item-full"
>
@@ -259,9 +259,10 @@
</template>
<script setup>
import { ref, computed, defineProps, defineEmits } from 'vue';
import { useRouter } from 'vue-router';
import { ref, computed, defineProps, defineEmits, onMounted } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import BaseLayout from '../../components/BaseLayout.vue';
import api from '../../api/axios';
// Определяем props
const props = defineProps({
@@ -275,8 +276,14 @@ const props = defineProps({
const emit = defineEmits(['auth-action-completed']);
const router = useRouter();
const route = useRoute();
// Получаем адрес DLE из URL параметров
const dleAddress = ref(route.query.address || '');
// Состояние
const selectedDle = ref(null);
const isLoadingDle = ref(false);
const showDetailsModal = ref(false);
const selectedEvent = ref(null);
const searchQuery = ref('');
@@ -296,6 +303,71 @@ const filters = ref({
// История операций (загружается из блокчейна)
const history = ref([]);
// Загрузка данных DLE
async function loadDleData() {
try {
isLoadingDle.value = true;
if (!dleAddress.value) {
console.error('Адрес DLE не указан');
return;
}
console.log('[HistoryView] Загрузка данных DLE:', dleAddress.value);
// Читаем данные из блокчейна
const response = await api.post('/dle-core/read-dle-info', {
dleAddress: dleAddress.value
});
if (response.data.success) {
selectedDle.value = response.data.data;
console.log('[HistoryView] Данные DLE загружены:', selectedDle.value);
// Загружаем историю событий
await loadEventHistory();
} else {
console.error('[HistoryView] Ошибка загрузки DLE:', response.data.error);
}
} catch (error) {
console.error('[HistoryView] Ошибка загрузки DLE:', error);
} finally {
isLoadingDle.value = false;
}
}
// Загрузка истории событий
async function loadEventHistory() {
try {
console.log('[HistoryView] Загрузка расширенной истории событий для DLE:', dleAddress.value);
// Загружаем расширенную историю из блокчейна
const response = await api.post('/dle-history/get-extended-history', {
dleAddress: dleAddress.value
});
if (response.data.success) {
const historyData = response.data.data;
history.value = historyData.history || [];
console.log('[HistoryView] Расширенная история событий загружена:', history.value);
} else {
console.error('[HistoryView] Ошибка загрузки истории:', response.data.error);
history.value = [];
}
} catch (error) {
console.error('[HistoryView] Ошибка загрузки истории событий:', error);
history.value = [];
}
}
// Загружаем данные при монтировании компонента
onMounted(() => {
if (dleAddress.value) {
loadDleData();
}
});
// Вычисляемые свойства
const filteredHistory = computed(() => {
let filtered = history.value;
@@ -305,10 +377,10 @@ const filteredHistory = computed(() => {
filtered = filtered.filter(event => event.type === filters.value.eventType);
}
// Фильтр по статусу
if (filters.value.status) {
filtered = filtered.filter(event => event.status === filters.value.status);
}
// Фильтр по статусу - убираем, так как все события успешны
// if (filters.value.status) {
// filtered = filtered.filter(event => event.status === filters.value.status);
// }
// Фильтр по датам
if (filters.value.dateFrom) {
@@ -325,8 +397,9 @@ const filteredHistory = computed(() => {
if (searchQuery.value) {
const query = searchQuery.value.toLowerCase();
filtered = filtered.filter(event =>
event.title.toLowerCase().includes(query) ||
event.description.toLowerCase().includes(query) ||
event.transactionHash.toLowerCase().includes(query)
(event.transactionHash && event.transactionHash.toLowerCase().includes(query))
);
}
@@ -351,9 +424,9 @@ const filteredHistory = computed(() => {
});
const totalOperations = computed(() => history.value.length);
const successfulOperations = computed(() => history.value.filter(e => e.status === 'success').length);
const failedOperations = computed(() => history.value.filter(e => e.status === 'failed').length);
const pendingOperations = computed(() => history.value.filter(e => e.status === 'pending').length);
const successfulOperations = computed(() => history.value.length); // Все события из блокчейна успешны
const failedOperations = computed(() => 0); // Нет неуспешных событий в блокчейне
const pendingOperations = computed(() => 0); // Нет ожидающих событий в блокчейне
const totalPages = computed(() => Math.ceil(filteredHistory.value.length / itemsPerPage.value));
@@ -394,12 +467,18 @@ const changePage = (page) => {
const getEventIcon = (type) => {
const icons = {
proposal: '📋',
vote: '🗳️',
transfer: '💸',
treasury: '🏦',
module: '🔧',
settings: '⚙️'
dle_created: '🏢',
proposal_created: '📋',
proposal_executed: '',
proposal_cancelled: '',
module_added: '🔧',
module_removed: '🔧',
chain_added: '🌐',
chain_removed: '🌐',
chain_updated: '🔄',
quorum_updated: '📊',
dle_info_updated: '📝',
proposal_execution_approved: '👍'
};
return icons[type] || '📄';
};
@@ -409,12 +488,8 @@ const getEventTitle = (event) => {
};
const getStatusText = (status) => {
const statusMap = {
success: 'Успешно',
pending: 'В обработке',
failed: 'Ошибка'
};
return statusMap[status] || status;
// Все события из блокчейна считаются успешными, так как они уже произошли
return 'Успешно';
};
const formatDate = (timestamp) => {
@@ -430,6 +505,13 @@ const formatDataValue = (value) => {
if (typeof value === 'object') {
return JSON.stringify(value);
}
if (typeof value === 'string' && value.startsWith('0x') && value.length === 42) {
// Это адрес - форматируем его
return value.substring(0, 6) + '...' + value.substring(value.length - 4);
}
if (typeof value === 'number') {
return value.toLocaleString();
}
return value;
};
@@ -439,8 +521,10 @@ const viewDetails = (event) => {
};
const viewOnExplorer = (event) => {
// Здесь будет логика открытия в блокчейн эксплорере
window.open(`https://etherscan.io/tx/${event.transactionHash}`, '_blank');
// Открываем в Sepolia Etherscan
if (event.transactionHash && event.transactionHash !== '0x0000000000000000000000000000000000000000000000000000000000000000') {
window.open(`https://sepolia.etherscan.io/tx/${event.transactionHash}`, '_blank');
}
};
</script>

View File

@@ -0,0 +1,773 @@
<!--
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="modules-management">
<!-- Заголовок -->
<div class="page-header">
<div class="header-content">
<h1>Модули DLE</h1>
<p v-if="selectedDle">{{ selectedDle.name }} ({{ selectedDle.symbol }}) - {{ selectedDle.dleAddress }}</p>
<p v-else-if="isLoadingDle">Загрузка...</p>
<p v-else>DLE не выбран</p>
</div>
<button class="close-btn" @click="router.push('/management')">×</button>
</div>
<!-- Информация о модулях -->
<div class="modules-info">
<div class="info-card">
<h3>📊 Информация о модулях</h3>
<div class="info-grid">
<div class="info-item">
<strong>Всего модулей:</strong> {{ modulesCount }}
</div>
<div class="info-item">
<strong>Активных модулей:</strong> {{ activeModulesCount }}
</div>
<div class="info-item">
<strong>Неактивных модулей:</strong> {{ inactiveModulesCount }}
</div>
</div>
</div>
</div>
<!-- Форма добавления модуля -->
<div class="add-module-form">
<div class="form-header">
<h3> Добавить модуль</h3>
<p>Создать предложение для добавления нового модуля</p>
</div>
<div class="form-content">
<div class="form-row">
<div class="form-group">
<label for="moduleId">ID модуля:</label>
<input
type="text"
id="moduleId"
v-model="newModule.moduleId"
class="form-control"
placeholder="0x..."
>
<small class="form-help">Уникальный идентификатор модуля (bytes32)</small>
</div>
<div class="form-group">
<label for="moduleAddress">Адрес модуля:</label>
<input
type="text"
id="moduleAddress"
v-model="newModule.moduleAddress"
class="form-control"
placeholder="0x..."
>
<small class="form-help">Адрес контракта модуля</small>
</div>
</div>
<div class="form-group">
<label for="moduleDescription">Описание предложения:</label>
<textarea
id="moduleDescription"
v-model="newModule.description"
class="form-control"
rows="3"
placeholder="Описание предложения для добавления модуля..."
></textarea>
</div>
<div class="form-row">
<div class="form-group">
<label for="moduleDuration">Продолжительность голосования (сек):</label>
<input
type="number"
id="moduleDuration"
v-model="newModule.duration"
class="form-control"
placeholder="86400"
>
<small class="form-help">Время голосования в секундах (86400 = 1 день)</small>
</div>
<div class="form-group">
<label for="moduleChainId">ID сети:</label>
<input
type="number"
id="moduleChainId"
v-model="newModule.chainId"
class="form-control"
placeholder="11155111"
>
<small class="form-help">ID сети (11155111 = Sepolia)</small>
</div>
</div>
<div class="form-actions">
<button
class="btn btn-primary"
@click="handleCreateAddModuleProposal"
:disabled="!isFormValid || isCreating"
>
<i class="fas fa-plus"></i>
{{ isCreating ? 'Создание предложения...' : 'Создать предложение' }}
</button>
</div>
</div>
</div>
<!-- Список модулей -->
<div class="modules-list">
<div class="list-header">
<h3>📋 Модули DLE</h3>
<button class="btn btn-sm btn-outline-secondary" @click="loadModules" :disabled="isLoadingModules">
<i class="fas fa-sync-alt" :class="{ 'fa-spin': isLoadingModules }"></i> Обновить
</button>
</div>
<div v-if="isLoadingModules" class="loading-modules">
<p>Загрузка модулей...</p>
</div>
<div v-else-if="modules.length === 0" class="no-modules">
<p>Модулей пока нет</p>
<p>Используйте форму выше для добавления первого модуля</p>
</div>
<div v-else class="modules-grid">
<div
v-for="module in modules"
:key="module.moduleId"
class="module-card"
:class="{ 'active': module.isActive, 'inactive': !module.isActive }"
>
<div class="module-header">
<h5>{{ module.moduleId }}</h5>
<span class="module-status" :class="{ 'active': module.isActive, 'inactive': !module.isActive }">
{{ module.isActive ? 'Активен' : 'Неактивен' }}
</span>
</div>
<div class="module-details">
<div class="detail-item">
<strong>Адрес:</strong>
<a
:href="`https://sepolia.etherscan.io/address/${module.moduleAddress}`"
target="_blank"
class="address-link"
>
{{ shortenAddress(module.moduleAddress) }}
<i class="fas fa-external-link-alt"></i>
</a>
</div>
</div>
<div class="module-actions">
<button
v-if="module.isActive"
class="btn btn-sm btn-danger"
@click="handleCreateRemoveModuleProposal(module.moduleId)"
:disabled="isRemoving === module.moduleId"
>
<i class="fas fa-trash"></i>
{{ isRemoving === module.moduleId ? 'Создание предложения...' : 'Удалить' }}
</button>
<button
v-else
class="btn btn-sm btn-success"
@click="activateModule(module.moduleId)"
:disabled="isActivating === module.moduleId"
>
<i class="fas fa-check"></i>
{{ isActivating === module.moduleId ? 'Активация...' : 'Активировать' }}
</button>
</div>
</div>
</div>
</div>
</div>
</BaseLayout>
</template>
<script setup>
import { defineProps, defineEmits, ref, onMounted, computed } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import BaseLayout from '../../components/BaseLayout.vue';
import {
createAddModuleProposal,
createRemoveModuleProposal,
isModuleActive,
getModuleAddress,
getAllModules
} from '../../services/modulesService.js';
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']);
const router = useRouter();
const route = useRoute();
// Состояние
const selectedDle = ref(null);
const isLoadingDle = ref(false);
const modules = ref([]);
const isLoadingModules = ref(false);
const isCreating = ref(false);
const isRemoving = ref(null);
const isActivating = ref(null);
// Форма нового модуля
const newModule = ref({
moduleId: '',
moduleAddress: '',
description: '',
duration: 86400,
chainId: 11155111
});
// Вычисляемые свойства
const isFormValid = computed(() => {
return newModule.value.moduleId &&
newModule.value.moduleAddress &&
newModule.value.description &&
newModule.value.duration > 0 &&
newModule.value.chainId > 0;
});
const modulesCount = computed(() => modules.value.length);
const activeModulesCount = computed(() => modules.value.filter(m => m.isActive).length);
const inactiveModulesCount = computed(() => modules.value.filter(m => !m.isActive).length);
// Загрузка данных DLE
async function loadDleData() {
try {
isLoadingDle.value = true;
const dleAddress = route.query.address;
if (!dleAddress) {
console.error('Адрес DLE не указан');
return;
}
console.log('[ModulesView] Загрузка данных DLE:', dleAddress);
// Читаем данные из блокчейна
const response = await api.post('/blockchain/read-dle-info', {
dleAddress: dleAddress
});
if (response.data.success) {
selectedDle.value = response.data.data;
console.log('[ModulesView] Данные DLE загружены:', selectedDle.value);
} else {
console.error('[ModulesView] Ошибка загрузки DLE:', response.data.error);
}
} catch (error) {
console.error('[ModulesView] Ошибка загрузки DLE:', error);
} finally {
isLoadingDle.value = false;
}
}
// Загрузка модулей
async function loadModules() {
try {
isLoadingModules.value = true;
const dleAddress = route.query.address;
if (!dleAddress) {
console.error('Адрес DLE не указан');
return;
}
console.log('[ModulesView] Загрузка модулей для DLE:', dleAddress);
// Загружаем модули через modulesService
const modulesResponse = await getAllModules(dleAddress);
if (modulesResponse.success) {
modules.value = modulesResponse.data.modules || [];
console.log('[ModulesView] Модули загружены:', modules.value);
} else {
console.error('[ModulesView] Ошибка загрузки модулей:', modulesResponse.error);
modules.value = [];
}
} catch (error) {
console.error('[ModulesView] Ошибка загрузки модулей:', error);
modules.value = [];
} finally {
isLoadingModules.value = false;
}
}
// Создание предложения добавления модуля
async function handleCreateAddModuleProposal() {
try {
isCreating.value = true;
const dleAddress = route.query.address;
if (!dleAddress) {
alert('Адрес DLE не указан');
return;
}
console.log('[ModulesView] Создание предложения добавления модуля:', newModule.value);
// Создаем предложение через modulesService
const result = await createAddModuleProposal(dleAddress, {
description: newModule.value.description,
duration: newModule.value.duration,
moduleId: newModule.value.moduleId,
moduleAddress: newModule.value.moduleAddress,
chainId: newModule.value.chainId
});
if (result.success) {
console.log('[ModulesView] Предложение создано:', result);
alert('✅ Предложение для добавления модуля создано!');
// Очищаем форму
newModule.value = {
moduleId: '',
moduleAddress: '',
description: '',
duration: 86400,
chainId: 11155111
};
// Перезагружаем модули
await loadModules();
} else {
alert('❌ Ошибка создания предложения: ' + result.error);
}
} catch (error) {
console.error('[ModulesView] Ошибка создания предложения:', error);
alert('❌ Ошибка создания предложения: ' + error.message);
} finally {
isCreating.value = false;
}
}
// Создание предложения удаления модуля
async function handleCreateRemoveModuleProposal(moduleId) {
try {
isRemoving.value = moduleId;
const dleAddress = route.query.address;
if (!dleAddress) {
alert('Адрес DLE не указан');
return;
}
console.log('[ModulesView] Создание предложения удаления модуля:', moduleId);
// Создаем предложение через modulesService
const result = await createRemoveModuleProposal(dleAddress, {
description: `Удаление модуля ${moduleId}`,
duration: 86400, // 1 день
moduleId: moduleId,
chainId: 11155111 // Sepolia
});
if (result.success) {
console.log('[ModulesView] Предложение удаления создано:', result);
alert('✅ Предложение для удаления модуля создано!');
// Перезагружаем модули
await loadModules();
} else {
alert('❌ Ошибка создания предложения: ' + result.error);
}
} catch (error) {
console.error('[ModulesView] Ошибка создания предложения удаления:', error);
alert('❌ Ошибка создания предложения: ' + error.message);
} finally {
isRemoving.value = null;
}
}
// Активация модуля (заглушка)
async function activateModule(moduleId) {
try {
isActivating.value = moduleId;
console.log('[ModulesView] Активация модуля:', moduleId);
// Здесь нужно будет реализовать активацию модуля
alert('Функция активации модуля будет реализована позже');
} catch (error) {
console.error('[ModulesView] Ошибка активации модуля:', error);
alert('❌ Ошибка активации модуля: ' + error.message);
} finally {
isActivating.value = null;
}
}
// Утилиты
function shortenAddress(address) {
if (!address) return '';
return `${address.slice(0, 6)}...${address.slice(-4)}`;
}
// Инициализация
onMounted(() => {
loadDleData();
loadModules();
});
</script>
<style scoped>
.modules-management {
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;
}
.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;
}
/* Информация о модулях */
.modules-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(200px, 1fr));
gap: 15px;
}
.info-item {
padding: 10px;
background: white;
border-radius: var(--radius-sm);
border: 1px solid #dee2e6;
}
/* Форма добавления модуля */
.add-module-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;
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 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;
}
.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;
}
.form-actions {
margin-top: 20px;
}
/* Список модулей */
.modules-list {
background: white;
border-radius: var(--radius-md);
padding: 20px;
border: 1px solid #e9ecef;
}
.list-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
}
.list-header h3 {
margin: 0;
color: var(--color-primary);
}
.loading-modules,
.no-modules {
text-align: center;
padding: 40px;
color: #666;
}
.modules-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(300px, 1fr));
gap: 20px;
}
.module-card {
border: 1px solid #e9ecef;
border-radius: var(--radius-md);
padding: 15px;
transition: all 0.2s;
}
.module-card:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.module-card.active {
border-color: #28a745;
background: #f8fff9;
}
.module-card.inactive {
border-color: #dc3545;
background: #fff8f8;
}
.module-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 10px;
}
.module-header h5 {
margin: 0;
font-size: 14px;
font-family: monospace;
word-break: break-all;
}
.module-status {
padding: 4px 8px;
border-radius: var(--radius-sm);
font-size: 12px;
font-weight: 500;
}
.module-status.active {
background: #d4edda;
color: #155724;
}
.module-status.inactive {
background: #f8d7da;
color: #721c24;
}
.module-details {
margin-bottom: 15px;
}
.detail-item {
margin-bottom: 5px;
font-size: 14px;
}
.detail-item strong {
color: #333;
}
.address-link {
color: var(--color-primary);
text-decoration: none;
font-family: monospace;
}
.address-link:hover {
text-decoration: underline;
}
.module-actions {
display: flex;
gap: 10px;
}
/* Кнопки */
.btn {
padding: 8px 16px;
border: none;
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 14px;
transition: all 0.2s;
display: inline-flex;
align-items: center;
gap: 5px;
}
.btn:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.btn-primary {
background: var(--color-primary);
color: white;
}
.btn-primary:hover:not(:disabled) {
background: var(--color-primary-dark);
}
.btn-success {
background: #28a745;
color: white;
}
.btn-success:hover:not(:disabled) {
background: #218838;
}
.btn-danger {
background: #dc3545;
color: white;
}
.btn-danger:hover:not(:disabled) {
background: #c82333;
}
.btn-outline-secondary {
background: transparent;
color: #6c757d;
border: 1px solid #6c757d;
}
.btn-outline-secondary:hover:not(:disabled) {
background: #6c757d;
color: white;
}
.btn-sm {
padding: 4px 8px;
font-size: 12px;
}
/* Адаптивность */
@media (max-width: 768px) {
.form-row {
grid-template-columns: 1fr;
}
.modules-grid {
grid-template-columns: 1fr;
}
.info-grid {
grid-template-columns: 1fr;
}
}
</style>

View File

@@ -184,6 +184,8 @@
import { ref, defineProps, defineEmits } from 'vue';
import { useRouter } from 'vue-router';
import BaseLayout from '../../components/BaseLayout.vue';
import { getGovernanceParams } from '../../services/dleV2Service.js';
import { getQuorumAt, getVotingPowerAt } from '../../services/proposalsService.js';
// Определяем props
const props = defineProps({

View File

@@ -61,9 +61,10 @@
<script setup>
import { ref, defineProps, defineEmits, onMounted } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import { useAuthContext } from '@/composables/useAuth';
import { useAuthContext } from '../../composables/useAuth';
import BaseLayout from '../../components/BaseLayout.vue';
import { getDLEInfo, deactivateDLE } from '../../utils/dle-contract.js';
import { deactivateDLE } from '../../utils/dle-contract.js';
import api from '../../api/axios';
// Определяем props
const props = defineProps({
@@ -103,14 +104,23 @@ const loadDLEInfo = async () => {
console.log('Загружаем информацию о DLE:', address);
// Загружаем данные DLE из блокчейна через API
const dleData = await getDLEInfo(address);
console.log('Загружены данные DLE из блокчейна:', dleData);
const response = await api.post('/dle-core/read-dle-info', {
dleAddress: address
});
dleInfo.value = {
name: dleData.name, // Название DLE из блокчейна
symbol: dleData.symbol, // Символ DLE из блокчейна
address: dleData.dleAddress || address // Адрес из API или из URL
};
if (response.data.success) {
const dleData = response.data.data;
console.log('Загружены данные DLE из блокчейна:', dleData);
dleInfo.value = {
name: dleData.name, // Название DLE из блокчейна
symbol: dleData.symbol, // Символ DLE из блокчейна
address: dleData.dleAddress || address // Адрес из API или из URL
};
} else {
console.error('Ошибка загрузки DLE:', response.data.error);
throw new Error(response.data.error || 'Не удалось загрузить данные DLE');
}
} catch (error) {
console.error('Ошибка при загрузке информации о DLE:', error);

View File

@@ -197,7 +197,8 @@
import { ref, computed, onMounted, watch, defineProps, defineEmits } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import BaseLayout from '../../components/BaseLayout.vue';
import axios from 'axios';
import { getTokenBalance, getTotalSupply, getTokenHolders } from '../../services/tokensService.js';
import api from '../../api/axios';
// Определяем props
const props = defineProps({
@@ -263,12 +264,12 @@ async function loadDleData() {
isLoadingDle.value = true;
try {
// Читаем актуальные данные из блокчейна
const blockchainResponse = await axios.post('/blockchain/read-dle-info', {
const response = await api.post('/dle-core/read-dle-info', {
dleAddress: dleAddress.value
});
if (blockchainResponse.data.success) {
const blockchainData = blockchainResponse.data.data;
if (response.data.success) {
const blockchainData = response.data.data;
selectedDle.value = blockchainData;
console.log('Загружены данные DLE из блокчейна:', blockchainData);

View File

@@ -1,870 +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-container">
<!-- Заголовок -->
<div class="page-header">
<div class="header-content">
<h1>Казна</h1>
<p>Управление средствами и активами DLE</p>
</div>
<button class="close-btn" @click="router.push('/management')">×</button>
</div>
<!-- Обзор казны -->
<div class="treasury-overview">
<h2>Обзор казны</h2>
<div class="overview-grid">
<div class="overview-card">
<h3>Общий баланс</h3>
<p class="balance-amount">${{ totalBalance.toLocaleString() }}</p>
<p class="balance-change positive">+${{ dailyChange.toLocaleString() }} (24ч)</p>
</div>
<div class="overview-card">
<h3>Активы</h3>
<p class="balance-amount">{{ assetsCount }}</p>
<p class="balance-description">различных активов</p>
</div>
<div class="overview-card">
<h3>Доходность</h3>
<p class="balance-amount">{{ yieldPercentage }}%</p>
<p class="balance-description">годовая доходность</p>
</div>
<div class="overview-card">
<h3>Риск</h3>
<p class="balance-amount risk-low">Низкий</p>
<p class="balance-description">уровень риска</p>
</div>
</div>
</div>
<!-- Активы -->
<div class="assets-section">
<h2>Активы</h2>
<div class="assets-list">
<div
v-for="asset in assets"
:key="asset.id"
class="asset-card"
>
<div class="asset-info">
<div class="asset-header">
<h3>{{ asset.name }}</h3>
<span class="asset-symbol">{{ asset.symbol }}</span>
</div>
<div class="asset-balance">
<p class="asset-amount">{{ asset.balance }} {{ asset.symbol }}</p>
<p class="asset-value">${{ asset.value.toLocaleString() }}</p>
</div>
<div class="asset-change" :class="asset.change >= 0 ? 'positive' : 'negative'">
{{ asset.change >= 0 ? '+' : '' }}{{ asset.change }}% (24ч)
</div>
</div>
<div class="asset-actions">
<button @click="depositAsset(asset)" class="btn-secondary">
Пополнить
</button>
<button @click="withdrawAsset(asset)" class="btn-secondary">
Вывести
</button>
</div>
</div>
</div>
</div>
<!-- Операции -->
<div class="operations-section">
<h2>Операции</h2>
<div class="operations-tabs">
<button
v-for="tab in operationTabs"
:key="tab.id"
@click="activeTab = tab.id"
class="tab-button"
:class="{ active: activeTab === tab.id }"
>
{{ tab.name }}
</button>
</div>
<!-- Форма депозита -->
<div v-if="activeTab === 'deposit'" class="operation-form">
<form @submit.prevent="performDeposit" class="deposit-form">
<div class="form-row">
<div class="form-group">
<label for="depositAsset">Актив:</label>
<select id="depositAsset" v-model="depositData.asset" required>
<option value="">Выберите актив</option>
<option v-for="asset in assets" :key="asset.id" :value="asset.id">
{{ asset.name }} ({{ asset.symbol }})
</option>
</select>
</div>
<div class="form-group">
<label for="depositAmount">Количество:</label>
<input
id="depositAmount"
v-model="depositData.amount"
type="number"
min="0.000001"
step="0.000001"
placeholder="0.00"
required
>
</div>
</div>
<div class="form-group">
<label for="depositReason">Причина депозита:</label>
<textarea
id="depositReason"
v-model="depositData.reason"
placeholder="Опишите причину депозита..."
rows="3"
></textarea>
</div>
<button type="submit" class="btn-primary" :disabled="isProcessing">
{{ isProcessing ? 'Обработка...' : 'Пополнить казну' }}
</button>
</form>
</div>
<!-- Форма вывода -->
<div v-if="activeTab === 'withdraw'" class="operation-form">
<form @submit.prevent="performWithdraw" class="withdraw-form">
<div class="form-row">
<div class="form-group">
<label for="withdrawAsset">Актив:</label>
<select id="withdrawAsset" v-model="withdrawData.asset" required>
<option value="">Выберите актив</option>
<option v-for="asset in assets" :key="asset.id" :value="asset.id">
{{ asset.name }} ({{ asset.symbol }})
</option>
</select>
</div>
<div class="form-group">
<label for="withdrawAmount">Количество:</label>
<input
id="withdrawAmount"
v-model="withdrawData.amount"
type="number"
min="0.000001"
step="0.000001"
placeholder="0.00"
required
>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="withdrawRecipient">Получатель:</label>
<input
id="withdrawRecipient"
v-model="withdrawData.recipient"
type="text"
placeholder="0x..."
required
>
</div>
<div class="form-group">
<label for="withdrawReason">Причина вывода:</label>
<input
id="withdrawReason"
v-model="withdrawData.reason"
type="text"
placeholder="Опишите причину вывода..."
required
>
</div>
</div>
<button type="submit" class="btn-primary" :disabled="isProcessing">
{{ isProcessing ? 'Обработка...' : 'Вывести из казны' }}
</button>
</form>
</div>
</div>
<!-- История операций -->
<div class="history-section">
<h2>История операций</h2>
<div v-if="operationsHistory.length === 0" class="empty-state">
<p>Нет операций в истории</p>
</div>
<div v-else class="history-list">
<div
v-for="operation in operationsHistory"
:key="operation.id"
class="history-item"
>
<div class="operation-info">
<div class="operation-header">
<h3>{{ operation.type === 'deposit' ? 'Депозит' : 'Вывод' }}</h3>
<span class="operation-date">{{ formatDate(operation.timestamp) }}</span>
</div>
<div class="operation-details">
<p><strong>Актив:</strong> {{ operation.asset }}</p>
<p><strong>Количество:</strong> {{ operation.amount }} {{ operation.symbol }}</p>
<p><strong>Стоимость:</strong> ${{ operation.value.toLocaleString() }}</p>
<p v-if="operation.reason"><strong>Причина:</strong> {{ operation.reason }}</p>
<p v-if="operation.recipient"><strong>Получатель:</strong> {{ formatAddress(operation.recipient) }}</p>
</div>
</div>
<div class="operation-status" :class="operation.status">
{{ getStatusText(operation.status) }}
</div>
</div>
</div>
</div>
</div>
</BaseLayout>
</template>
<script setup>
import { ref, defineProps, defineEmits } from 'vue';
import { useRouter } from 'vue-router';
import BaseLayout from '../../components/BaseLayout.vue';
// Определяем props
const props = defineProps({
isAuthenticated: Boolean,
identities: Array,
tokenBalances: Object,
isLoadingTokens: Boolean
});
// Определяем emits
const emit = defineEmits(['auth-action-completed']);
const router = useRouter();
// Состояние
const isProcessing = ref(false);
const activeTab = ref('deposit');
// Данные казны
const totalBalance = ref(1250000);
const dailyChange = ref(25000);
const assetsCount = ref(5);
const yieldPercentage = ref(8.5);
// Активы (загружаются из блокчейна)
const assets = ref([]);
// Вкладки операций
const operationTabs = ref([
{ id: 'deposit', name: 'Депозит' },
{ id: 'withdraw', name: 'Вывод' }
]);
// Данные депозита
const depositData = ref({
asset: '',
amount: '',
reason: ''
});
// Данные вывода
const withdrawData = ref({
asset: '',
amount: '',
recipient: '',
reason: ''
});
// История операций (загружается из блокчейна)
const operationsHistory = ref([]);
// Методы
const depositAsset = (asset) => {
depositData.value.asset = asset.id;
activeTab.value = 'deposit';
};
const withdrawAsset = (asset) => {
withdrawData.value.asset = asset.id;
activeTab.value = 'withdraw';
};
const performDeposit = async () => {
if (isProcessing.value) return;
try {
isProcessing.value = true;
// Здесь будет логика депозита
// console.log('Депозит:', depositData.value);
// Временная логика
const asset = assets.value.find(a => a.id === depositData.value.asset);
if (asset) {
const amount = parseFloat(depositData.value.amount);
asset.balance += amount;
asset.value = asset.balance * (asset.value / (asset.balance - amount));
totalBalance.value += amount * (asset.value / asset.balance);
}
// Добавляем в историю
operationsHistory.value.unshift({
id: operationsHistory.value.length + 1,
type: 'deposit',
asset: asset.name,
symbol: asset.symbol,
amount: parseFloat(depositData.value.amount),
value: parseFloat(depositData.value.amount) * (asset.value / asset.balance),
reason: depositData.value.reason,
timestamp: Date.now(),
status: 'completed'
});
// Сброс формы
depositData.value = {
asset: '',
amount: '',
reason: ''
};
alert('Депозит успешно выполнен!');
} catch (error) {
// console.error('Ошибка депозита:', error);
alert('Ошибка при выполнении депозита');
} finally {
isProcessing.value = false;
}
};
const performWithdraw = async () => {
if (isProcessing.value) return;
try {
isProcessing.value = true;
// Здесь будет логика вывода
// console.log('Вывод:', withdrawData.value);
// Временная логика
const asset = assets.value.find(a => a.id === withdrawData.value.asset);
if (asset) {
const amount = parseFloat(withdrawData.value.amount);
if (amount > asset.balance) {
alert('Недостаточно средств для вывода');
return;
}
asset.balance -= amount;
asset.value = asset.balance * (asset.value / (asset.balance + amount));
totalBalance.value -= amount * (asset.value / asset.balance);
}
// Добавляем в историю
operationsHistory.value.unshift({
id: operationsHistory.value.length + 1,
type: 'withdraw',
asset: asset.name,
symbol: asset.symbol,
amount: parseFloat(withdrawData.value.amount),
value: parseFloat(withdrawData.value.amount) * (asset.value / asset.balance),
reason: withdrawData.value.reason,
recipient: withdrawData.value.recipient,
timestamp: Date.now(),
status: 'completed'
});
// Сброс формы
withdrawData.value = {
asset: '',
amount: '',
recipient: '',
reason: ''
};
alert('Вывод успешно выполнен!');
} catch (error) {
// console.error('Ошибка вывода:', error);
alert('Ошибка при выполнении вывода');
} finally {
isProcessing.value = false;
}
};
const getStatusText = (status) => {
const statusMap = {
'completed': 'Завершено',
'pending': 'В обработке',
'failed': 'Ошибка'
};
return statusMap[status] || status;
};
const formatAddress = (address) => {
if (!address) return '';
return address.substring(0, 6) + '...' + address.substring(address.length - 4);
};
const formatDate = (timestamp) => {
return new Date(timestamp).toLocaleString('ru-RU');
};
</script>
<style scoped>
.treasury-container {
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: flex-start;
margin-bottom: 40px;
padding-bottom: 20px;
border-bottom: 2px solid #f0f0f0;
}
.header-content {
flex-grow: 1;
}
.page-header h1 {
color: var(--color-primary);
font-size: 2.5rem;
margin: 0 0 10px 0;
}
.page-header p {
color: var(--color-grey-dark);
font-size: 1.1rem;
margin: 0;
}
.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;
flex-shrink: 0;
}
.close-btn:hover {
background: #f0f0f0;
color: #333;
}
/* Секции */
.treasury-overview,
.assets-section,
.operations-section,
.history-section {
margin-bottom: 40px;
}
.treasury-overview h2,
.assets-section h2,
.operations-section h2,
.history-section h2 {
color: var(--color-primary);
margin-bottom: 20px;
font-size: 1.8rem;
}
/* Обзор казны */
.overview-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));
gap: 20px;
}
.overview-card {
background: #f8f9fa;
padding: 25px;
border-radius: var(--radius-lg);
border-left: 4px solid var(--color-primary);
text-align: center;
}
.overview-card h3 {
color: var(--color-primary);
margin-bottom: 15px;
font-size: 1rem;
text-transform: uppercase;
font-weight: 600;
}
.balance-amount {
font-size: 2rem;
font-weight: 700;
margin: 0 0 10px 0;
color: var(--color-primary);
}
.balance-change {
font-size: 1rem;
font-weight: 600;
margin: 0;
}
.balance-change.positive {
color: #28a745;
}
.balance-change.negative {
color: #dc3545;
}
.balance-description {
font-size: 0.9rem;
color: var(--color-grey-dark);
margin: 0;
}
.risk-low {
color: #28a745;
}
/* Активы */
.assets-list {
display: grid;
gap: 20px;
}
.asset-card {
display: flex;
justify-content: space-between;
align-items: center;
padding: 25px;
background: white;
border-radius: var(--radius-lg);
border: 1px solid #e9ecef;
transition: all 0.3s ease;
}
.asset-card:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.asset-info {
flex-grow: 1;
}
.asset-header {
display: flex;
align-items: center;
gap: 15px;
margin-bottom: 10px;
}
.asset-header h3 {
margin: 0;
color: var(--color-primary);
font-size: 1.2rem;
}
.asset-symbol {
background: var(--color-primary);
color: white;
padding: 4px 8px;
border-radius: 12px;
font-size: 0.8rem;
font-weight: 600;
}
.asset-balance {
margin-bottom: 10px;
}
.asset-amount {
font-size: 1.1rem;
font-weight: 600;
margin: 0 0 5px 0;
color: var(--color-primary);
}
.asset-value {
font-size: 0.9rem;
color: var(--color-grey-dark);
margin: 0;
}
.asset-change {
font-size: 0.9rem;
font-weight: 600;
}
.asset-change.positive {
color: #28a745;
}
.asset-change.negative {
color: #dc3545;
}
.asset-actions {
display: flex;
gap: 10px;
}
/* Операции */
.operations-tabs {
display: flex;
gap: 10px;
margin-bottom: 20px;
}
.tab-button {
background: #f8f9fa;
border: 1px solid #e9ecef;
padding: 12px 24px;
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 1rem;
font-weight: 600;
transition: all 0.3s ease;
}
.tab-button:hover {
background: #e9ecef;
}
.tab-button.active {
background: var(--color-primary);
color: white;
border-color: var(--color-primary);
}
.operation-form {
background: #f8f9fa;
padding: 25px;
border-radius: var(--radius-lg);
border: 1px solid #e9ecef;
}
.form-row {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 20px;
margin-bottom: 20px;
}
.form-group {
display: flex;
flex-direction: column;
gap: 8px;
margin-bottom: 20px;
}
.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;
}
/* История операций */
.history-list {
display: grid;
gap: 20px;
}
.history-item {
display: flex;
justify-content: space-between;
align-items: flex-start;
padding: 25px;
background: white;
border-radius: var(--radius-lg);
border: 1px solid #e9ecef;
transition: all 0.3s ease;
}
.history-item:hover {
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1);
}
.operation-info {
flex-grow: 1;
}
.operation-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 15px;
}
.operation-header h3 {
margin: 0;
color: var(--color-primary);
font-size: 1.2rem;
}
.operation-date {
font-size: 0.9rem;
color: var(--color-grey-dark);
}
.operation-details p {
margin: 5px 0;
font-size: 0.95rem;
}
.operation-status {
padding: 6px 16px;
border-radius: 20px;
font-size: 0.9rem;
font-weight: 600;
margin-left: 20px;
}
.operation-status.completed {
background: #d4edda;
color: #155724;
}
.operation-status.pending {
background: #fff3cd;
color: #856404;
}
.operation-status.failed {
background: #f8d7da;
color: #721c24;
}
/* Кнопки */
.btn-primary {
background: var(--color-primary);
color: white;
border: none;
padding: 12px 24px;
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 1rem;
font-weight: 600;
transition: all 0.3s ease;
}
.btn-primary:hover:not(:disabled) {
background: var(--color-primary-dark);
}
.btn-primary:disabled {
opacity: 0.6;
cursor: not-allowed;
}
.btn-secondary {
background: var(--color-secondary);
color: white;
border: none;
padding: 10px 20px;
border-radius: var(--radius-sm);
cursor: pointer;
font-size: 0.9rem;
font-weight: 600;
transition: all 0.3s ease;
}
.btn-secondary:hover {
background: var(--color-secondary-dark);
}
/* Состояния */
.empty-state {
text-align: center;
padding: 60px;
color: var(--color-grey-dark);
background: #f8f9fa;
border-radius: var(--radius-lg);
border: 2px dashed #dee2e6;
}
.empty-state p {
margin: 0;
font-size: 1.1rem;
}
/* Адаптивность */
@media (max-width: 768px) {
.form-row {
grid-template-columns: 1fr;
}
.asset-card {
flex-direction: column;
align-items: flex-start;
gap: 20px;
}
.asset-actions {
width: 100%;
justify-content: space-between;
}
.history-item {
flex-direction: column;
gap: 15px;
}
.operation-status {
margin-left: 0;
align-self: flex-start;
}
.overview-grid {
grid-template-columns: 1fr;
}
.operations-tabs {
flex-direction: column;
}
}
</style>