ваше сообщение коммита
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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',
|
||||
|
||||
319
frontend/src/services/analyticsService.js
Normal file
319
frontend/src/services/analyticsService.js
Normal 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;
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
31
frontend/src/services/index.js
Normal file
31
frontend/src/services/index.js
Normal 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';
|
||||
297
frontend/src/services/modulesService.js
Normal file
297
frontend/src/services/modulesService.js
Normal 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;
|
||||
}
|
||||
};
|
||||
352
frontend/src/services/multichainService.js
Normal file
352
frontend/src/services/multichainService.js
Normal 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;
|
||||
}
|
||||
};
|
||||
263
frontend/src/services/proposalsService.js
Normal file
263
frontend/src/services/proposalsService.js
Normal 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;
|
||||
}
|
||||
};
|
||||
@@ -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;
|
||||
}
|
||||
};
|
||||
|
||||
71
frontend/src/services/tokensService.js
Normal file
71
frontend/src/services/tokensService.js
Normal 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;
|
||||
}
|
||||
};
|
||||
@@ -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 контракта
|
||||
|
||||
@@ -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
@@ -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 'Нет операции';
|
||||
|
||||
|
||||
@@ -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>
|
||||
|
||||
|
||||
773
frontend/src/views/smartcontracts/ModulesView.vue
Normal file
773
frontend/src/views/smartcontracts/ModulesView.vue
Normal 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>
|
||||
@@ -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({
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -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>
|
||||
Reference in New Issue
Block a user