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

This commit is contained in:
2025-09-24 13:05:20 +03:00
parent de0f8aecf2
commit 76cde4b53d
45 changed files with 2167 additions and 2854 deletions

View File

@@ -27,7 +27,7 @@
<p v-else-if="isLoadingDle">Загрузка...</p>
<p v-else>Подробная аналитика и статистика DLE</p>
</div>
<button class="close-btn" @click="router.push('/management')">×</button>
<button class="close-btn" @click="goBackToBlocks">×</button>
</div>
<!-- Основная информация -->
@@ -252,6 +252,15 @@ const route = useRoute();
// Получаем адрес DLE из URL параметров
const dleAddress = ref(route.query.address || '');
// Функция возврата к блокам управления
const goBackToBlocks = () => {
if (dleAddress.value) {
router.push(`/management/dle-blocks?address=${dleAddress.value}`);
} else {
router.push('/management');
}
};
// Состояние
const selectedDle = ref(null);
const isLoadingDle = ref(false);

View File

@@ -0,0 +1,298 @@
<!--
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="dle-blocks-management">
<!-- Заголовок -->
<div class="management-header">
<div class="header-content">
<h1>Управление DLE</h1>
<p v-if="dleAddress" class="dle-address">
<strong>DLE:</strong> {{ dleAddress }}
</p>
</div>
<button class="close-btn" @click="router.push('/management')">×</button>
</div>
<!-- Блоки управления -->
<div class="management-blocks">
<!-- Первый ряд -->
<div class="blocks-row">
<div class="management-block">
<h3>Предложения</h3>
<p>Создание, подписание, выполнение</p>
<button class="details-btn" @click="openProposals">Подробнее</button>
</div>
<div class="management-block">
<h3>Токены DLE</h3>
<p>Балансы, трансферы, распределение</p>
<button class="details-btn" @click="openTokens">Подробнее</button>
</div>
<div class="management-block">
<h3>Кворум</h3>
<p>Настройки голосования</p>
<button class="details-btn" @click="openQuorum">Подробнее</button>
</div>
</div>
<!-- Второй ряд -->
<div class="blocks-row">
<div class="management-block">
<h3>Модули DLE</h3>
<p>Установка, настройка, управление</p>
<button class="details-btn" @click="openModules">Подробнее</button>
</div>
<div class="management-block">
<h3>Аналитика</h3>
<p>Графики, статистика, отчеты</p>
<button class="details-btn" @click="openAnalytics">Подробнее</button>
</div>
</div>
<!-- Третий ряд -->
<div class="blocks-row">
<div class="management-block">
<h3>История</h3>
<p>Лог операций, события, транзакции</p>
<button class="details-btn" @click="openHistory">Подробнее</button>
</div>
<div class="management-block">
<h3>Настройки</h3>
<p>Параметры DLE, конфигурация</p>
<button class="details-btn" @click="openSettings">Подробнее</button>
</div>
</div>
</div>
</div>
</BaseLayout>
</template>
<script setup>
import { ref, computed, onMounted } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import BaseLayout from '../../components/BaseLayout.vue';
// Props
const props = defineProps({
isAuthenticated: Boolean,
identities: Array,
tokenBalances: Object,
isLoadingTokens: Boolean
});
const emit = defineEmits(['auth-action-completed']);
const router = useRouter();
const route = useRoute();
// Получаем адрес DLE из query параметров
const dleAddress = computed(() => route.query.address || null);
// Функции для открытия страниц управления
const openProposals = () => {
if (dleAddress.value) {
router.push(`/management/proposals?address=${dleAddress.value}`);
} else {
router.push('/management/proposals');
}
};
const openTokens = () => {
if (dleAddress.value) {
router.push(`/management/tokens?address=${dleAddress.value}`);
} else {
router.push('/management/tokens');
}
};
const openQuorum = () => {
if (dleAddress.value) {
router.push(`/management/quorum?address=${dleAddress.value}`);
} else {
router.push('/management/quorum');
}
};
const openModules = () => {
if (dleAddress.value) {
router.push(`/management/modules?address=${dleAddress.value}`);
} else {
router.push('/management/modules');
}
};
const openAnalytics = () => {
if (dleAddress.value) {
router.push(`/management/analytics?address=${dleAddress.value}`);
} else {
router.push('/management/analytics');
}
};
const openHistory = () => {
if (dleAddress.value) {
router.push(`/management/history?address=${dleAddress.value}`);
} else {
router.push('/management/history');
}
};
const openSettings = () => {
if (dleAddress.value) {
router.push(`/management/settings?address=${dleAddress.value}`);
} else {
router.push('/management/settings');
}
};
onMounted(() => {
// Если нет адреса DLE, перенаправляем на главную страницу management
if (!dleAddress.value) {
router.push('/management');
}
});
</script>
<style scoped>
.dle-blocks-management {
padding: 20px;
background-color: var(--color-white);
border-radius: var(--radius-lg);
min-height: 100vh;
}
.management-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 2rem;
padding-bottom: 1rem;
border-bottom: 2px solid #e9ecef;
}
.header-content h1 {
margin: 0;
color: var(--color-primary);
font-size: 2rem;
font-weight: 700;
}
.dle-address {
margin: 0.5rem 0 0 0;
color: #666;
font-size: 1.1rem;
}
.close-btn {
background: none;
border: none;
font-size: 2rem;
color: #666;
cursor: pointer;
padding: 0.5rem;
border-radius: 50%;
transition: all 0.2s;
}
.close-btn:hover {
background: #f8f9fa;
color: #333;
}
.management-blocks {
display: flex;
flex-direction: column;
gap: 2rem;
}
.blocks-row {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 1.5rem;
}
.management-block {
background: white;
border-radius: 12px;
padding: 2rem;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
border: 1px solid #e9ecef;
transition: all 0.3s ease;
text-align: center;
}
.management-block:hover {
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.12);
transform: translateY(-2px);
border-color: var(--color-primary);
}
.management-block h3 {
margin: 0 0 1rem 0;
color: var(--color-primary);
font-size: 1.5rem;
font-weight: 600;
}
.management-block p {
margin: 0 0 1.5rem 0;
color: #666;
font-size: 1rem;
line-height: 1.5;
}
.details-btn {
background: var(--color-primary);
color: #fff;
border: none;
border-radius: 8px;
padding: 0.75rem 1.5rem;
cursor: pointer;
font-size: 1rem;
font-weight: 600;
transition: all 0.2s;
min-width: 120px;
}
.details-btn:hover {
background: var(--color-primary-dark);
transform: translateY(-1px);
}
/* Адаптивность */
@media (max-width: 768px) {
.blocks-row {
grid-template-columns: 1fr;
}
.management-header {
flex-direction: column;
align-items: flex-start;
gap: 1rem;
}
.header-content h1 {
font-size: 1.5rem;
}
}
</style>

View File

@@ -27,7 +27,7 @@
<p v-else-if="isLoadingDle">Загрузка...</p>
<p v-else>DLE не выбран</p>
</div>
<button class="close-btn" @click="router.push('/management')">×</button>
<button class="close-btn" @click="goBackToBlocks">×</button>
</div>
<!-- Фильтры и управление -->
@@ -900,6 +900,15 @@ const dleAddress = computed(() => {
return address;
});
// Функция возврата к блокам управления
const goBackToBlocks = () => {
if (dleAddress.value) {
router.push(`/management/dle-blocks?address=${dleAddress.value}`);
} else {
router.push('/management');
}
};
// Состояние DLE
const selectedDle = ref(null);
const isLoadingDle = ref(false);

View File

@@ -27,7 +27,7 @@
<p v-else-if="isLoadingDle">Загрузка...</p>
<p v-else>Лог операций, события и транзакции DLE</p>
</div>
<button class="close-btn" @click="router.push('/management')">×</button>
<button class="close-btn" @click="goBackToBlocks">×</button>
</div>
<!-- Фильтры -->
@@ -281,6 +281,15 @@ const route = useRoute();
// Получаем адрес DLE из URL параметров
const dleAddress = ref(route.query.address || '');
// Функция возврата к блокам управления
const goBackToBlocks = () => {
if (dleAddress.value) {
router.push(`/management/dle-blocks?address=${dleAddress.value}`);
} else {
router.push('/management');
}
};
// Состояние
const selectedDle = ref(null);
const isLoadingDle = ref(false);

View File

@@ -27,7 +27,7 @@
<p v-else-if="isLoadingDle">Загрузка...</p>
<p v-else>DLE не выбран</p>
</div>
<button class="close-btn" @click="router.push('/management')">×</button>
<button class="close-btn" @click="goBackToBlocks">×</button>
</div>
<!-- Информация о модулях -->
@@ -665,6 +665,20 @@ const emit = defineEmits(['auth-action-completed']);
const router = useRouter();
const route = useRoute();
// Получаем адрес DLE из URL
const dleAddress = computed(() => {
return route.query.address;
});
// Функция возврата к блокам управления
const goBackToBlocks = () => {
if (dleAddress.value) {
router.push(`/management/dle-blocks?address=${dleAddress.value}`);
} else {
router.push('/management');
}
};
// Состояние
const selectedDle = ref(null);
const isLoadingDle = ref(false);

View File

@@ -25,7 +25,7 @@
<h1>Кворум</h1>
<p>Настройки голосования и кворума</p>
</div>
<button class="close-btn" @click="router.push('/management')">×</button>
<button class="close-btn" @click="goBackToBlocks">×</button>
</div>
<!-- Текущие настройки -->
@@ -181,8 +181,8 @@
</template>
<script setup>
import { ref, defineProps, defineEmits } from 'vue';
import { useRouter } from 'vue-router';
import { ref, computed, defineProps, defineEmits } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import BaseLayout from '../../components/BaseLayout.vue';
import { getGovernanceParams } from '../../services/dleV2Service.js';
import { getQuorumAt, getVotingPowerAt } from '../../services/proposalsService.js';
@@ -199,6 +199,21 @@ const props = defineProps({
const emit = defineEmits(['auth-action-completed']);
const router = useRouter();
const route = useRoute();
// Получаем адрес DLE из URL
const dleAddress = computed(() => {
return route.query.address;
});
// Функция возврата к блокам управления
const goBackToBlocks = () => {
if (dleAddress.value) {
router.push(`/management/dle-blocks?address=${dleAddress.value}`);
} else {
router.push('/management');
}
};
// Состояние
const isUpdating = ref(false);

View File

@@ -27,7 +27,7 @@
<p v-else-if="address">Загрузка...</p>
<p v-else>DLE не выбран</p>
</div>
<button class="close-btn" @click="router.push('/management')">×</button>
<button class="close-btn" @click="goBackToBlocks">×</button>
</div>
<!-- Основной контент -->
@@ -39,6 +39,15 @@
</div>
<div class="danger-content">
<p>Полное удаление DLE и всех связанных данных. Это действие необратимо.</p>
<div class="warning-info">
<h4> Важно:</h4>
<ul>
<li>Для деактивации DLE необходимо иметь токены</li>
<li>Может потребоваться голосование участников</li>
<li>DLE должен быть активен</li>
<li>Только владелец токенов может инициировать деактивацию</li>
</ul>
</div>
<button @click="deleteDLE" class="btn-danger" :disabled="isLoading">
{{ isLoading ? 'Загрузка...' : 'Удалить DLE' }}
</button>
@@ -50,7 +59,7 @@
<div v-if="!address" class="no-dle-card">
<h3>DLE не выбран</h3>
<p>Для управления настройками необходимо выбрать DLE</p>
<button @click="router.push('/management')" class="btn-primary">
<button @click="goBackToBlocks" class="btn-primary">
Вернуться к списку DLE
</button>
</div>
@@ -89,6 +98,15 @@ const isLoading = ref(false);
// Получаем адрес DLE из URL параметров
const address = route.query.address || props.dleAddress;
// Функция возврата к блокам управления
const goBackToBlocks = () => {
if (address) {
router.push(`/management/dle-blocks?address=${address}`);
} else {
router.push('/management');
}
};
// Получаем адрес пользователя из контекста аутентификации
const { address: userAddress } = useAuthContext();
@@ -167,20 +185,26 @@ const deleteDLE = async () => {
alert(`✅ DLE ${dleInfo.value?.name || address} успешно деактивирован!\n\nТранзакция: ${result.txHash}`);
// Перенаправляем на главную страницу управления
router.push('/management');
// Перенаправляем на страницу блоков управления
goBackToBlocks();
} catch (error) {
console.error('Ошибка при деактивации DLE:', error);
let errorMessage = 'Ошибка при деактивации DLE';
if (error.message.includes('владелец')) {
if (error.message.includes('execution reverted')) {
errorMessage = '❌ Деактивация невозможна: не выполнены условия смарт-контракта. Возможно, требуется голосование участников или DLE уже деактивирован.';
} else if (error.message.includes('владелец')) {
errorMessage = '❌ Только владелец DLE может его деактивировать';
} else if (error.message.includes('кошелек')) {
errorMessage = '❌ Необходимо подключить кошелек';
} else if (error.message.includes('деактивирован')) {
errorMessage = '❌ DLE уже деактивирован';
} else if (error.message.includes('токены')) {
errorMessage = '❌ Для деактивации DLE необходимо иметь токены';
} else if (error.message.includes('условия смарт-контракта')) {
errorMessage = error.message; // Используем сообщение из dle-contract.js
} else {
errorMessage = `❌ Ошибка: ${error.message}`;
}
@@ -345,4 +369,31 @@ onMounted(() => {
line-height: 1.5;
font-size: 0.9rem;
}
/* Стили для блока предупреждения */
.warning-info {
background: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: 6px;
padding: 15px;
margin: 15px 0;
}
.warning-info h4 {
color: #856404;
margin: 0 0 10px 0;
font-size: 1rem;
}
.warning-info ul {
margin: 0;
padding-left: 20px;
color: #856404;
}
.warning-info li {
margin-bottom: 5px;
font-size: 0.9rem;
line-height: 1.4;
}
</style>

View File

@@ -35,7 +35,7 @@
<span>DLE не выбран</span>
</div>
</div>
<button class="close-btn" @click="router.push('/management')">×</button>
<button class="close-btn" @click="goBackToBlocks">×</button>
</div>
<!-- Информация о токенах -->
@@ -182,6 +182,15 @@ const dleAddress = computed(() => {
return address;
});
// Функция возврата к блокам управления
const goBackToBlocks = () => {
if (dleAddress.value) {
router.push(`/management/dle-blocks?address=${dleAddress.value}`);
} else {
router.push('/management');
}
};
// Состояние DLE
const selectedDle = ref(null);
const isLoadingDle = ref(false);

View File

@@ -49,11 +49,11 @@
</div>
</div>
<!-- Форма деплоя модуля во всех сетях -->
<div class="deploy-form">
<!-- Форма деплоя модуля администратором -->
<div v-if="canManageSettings" class="deploy-form">
<div class="form-header">
<h3>🌐 Деплой DLEReader во всех сетях</h3>
<p>Деплой API модуля для чтения данных во всех 4 сетях одновременно</p>
<h3>🔧 Деплой DLEReader администратором</h3>
<p>Администратор деплоит модуль, затем создает предложение для добавления в DLE</p>
</div>
<div class="form-content">
@@ -85,22 +85,39 @@
<h4> Настройки DLEReader:</h4>
<div class="settings-form">
<div class="form-group">
<label for="chainId">ID сети:</label>
<select
id="chainId"
v-model="moduleSettings.chainId"
class="form-control"
required
>
<option value="11155111">Sepolia (11155111)</option>
<option value="17000">Holesky (17000)</option>
<option value="421614">Arbitrum Sepolia (421614)</option>
<option value="84532">Base Sepolia (84532)</option>
</select>
<small class="form-help">ID сети для деплоя модуля</small>
<!-- Поля администратора -->
<div class="admin-section">
<h5>🔐 Настройки администратора:</h5>
<div class="form-row">
<div class="form-group">
<label for="adminPrivateKey">Приватный ключ администратора:</label>
<input
type="password"
id="adminPrivateKey"
v-model="moduleSettings.adminPrivateKey"
class="form-control"
placeholder="0x..."
required
>
<small class="form-help">Приватный ключ для деплоя модуля (администратор платит газ)</small>
</div>
<div class="form-group">
<label for="etherscanApiKey">Etherscan API ключ:</label>
<input
type="text"
id="etherscanApiKey"
v-model="moduleSettings.etherscanApiKey"
class="form-control"
placeholder="YourAPIKey..."
>
<small class="form-help">API ключ для автоматической верификации контрактов</small>
</div>
</div>
</div>
<div class="simple-info">
<h5>📋 Информация о DLEReader:</h5>
<div class="info-text">
@@ -122,12 +139,17 @@
<button
class="btn btn-primary btn-large deploy-module"
@click="deployDLEReader"
:disabled="isDeploying || !dleAddress"
:disabled="isDeploying || !dleAddress || !isFormValid"
>
<i class="fas fa-rocket" :class="{ 'fa-spin': isDeploying }"></i>
{{ isDeploying ? 'Деплой модуля...' : 'Деплой DLEReader' }}
</button>
<div v-if="!isFormValid && !isDeploying" class="form-validation-info">
<i class="fas fa-exclamation-triangle"></i>
<span>Заполните приватный ключ и API ключ для деплоя</span>
</div>
<div v-if="deploymentProgress" class="deployment-progress">
<div class="progress-info">
<span>{{ deploymentProgress.message }}</span>
@@ -141,14 +163,26 @@
</div>
</div>
<!-- Сообщение для пользователей без прав доступа -->
<div v-if="!canManageSettings" class="no-access-message">
<div class="message-content">
<h3>🔒 Нет прав доступа</h3>
<p>У вас нет прав для деплоя смарт-контрактов. Только пользователи с ролью Editor могут выполнять деплой.</p>
<button class="btn btn-secondary" @click="router.push('/management/modules')">
Вернуться к модулям
</button>
</div>
</div>
</div>
</BaseLayout>
</template>
<script setup>
import { ref, onMounted } from 'vue';
import { ref, computed, onMounted } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import BaseLayout from '../../../components/BaseLayout.vue';
import { usePermissions } from '@/composables/usePermissions';
// Props
const props = defineProps({
@@ -162,6 +196,7 @@ const emit = defineEmits(['auth-action-completed']);
const router = useRouter();
const route = useRoute();
const { canEdit, canManageSettings } = usePermissions();
// Состояние
const isLoading = ref(false);
@@ -171,12 +206,23 @@ const deploymentProgress = ref(null);
// Настройки модуля
const moduleSettings = ref({
// Единственный параметр - ID сети
chainId: 11155111
// Поля администратора
adminPrivateKey: '',
etherscanApiKey: ''
});
// Проверка валидности формы
const isFormValid = computed(() => {
return moduleSettings.value.adminPrivateKey && moduleSettings.value.etherscanApiKey;
});
// Функция деплоя DLEReader
async function deployDLEReader() {
if (!canManageSettings.value) {
alert('У вас нет прав для деплоя смарт-контрактов');
return;
}
try {
isDeploying.value = true;
deploymentProgress.value = {
@@ -186,8 +232,8 @@ async function deployDLEReader() {
console.log('[DLEReaderDeployView] Начинаем деплой DLEReader для DLE:', dleAddress.value);
// Вызываем API для деплоя модуля во всех сетях
const response = await fetch('/api/dle-modules/deploy-reader', {
// Вызываем API для деплоя модуля администратором
const response = await fetch('/api/dle-modules/deploy-reader-admin', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -195,9 +241,11 @@ async function deployDLEReader() {
body: JSON.stringify({
dleAddress: dleAddress.value,
moduleType: 'reader',
adminPrivateKey: moduleSettings.value.adminPrivateKey,
etherscanApiKey: moduleSettings.value.etherscanApiKey,
settings: {
// Единственный параметр - ID сети
chainId: moduleSettings.value.chainId
// Используем настройки по умолчанию
useDefaultSettings: true
}
})
});
@@ -217,12 +265,31 @@ async function deployDLEReader() {
percentage: 100
};
alert('✅ Деплой DLEReader запущен во всех сетях!');
// Показываем детальную информацию о деплое
const deployInfo = result.data || {};
const deployedAddresses = deployInfo.addresses || [];
let successMessage = '✅ DLEReader успешно задеплоен!\n\n';
successMessage += `📊 Детали деплоя:\n`;
successMessage += `• DLE: ${dleAddress.value}\n`;
successMessage += `• Тип модуля: DLEReader\n`;
successMessage += `• Адрес модуля: ${deployInfo.moduleAddress || 'Не указан'}\n`;
if (deployedAddresses.length > 0) {
successMessage += `\n🌐 Задеплоенные адреса:\n`;
deployedAddresses.forEach((addr, index) => {
successMessage += `${index + 1}. ${addr.network}: ${addr.address}\n`;
});
}
successMessage += `\n📝 Следующий шаг: Создайте предложение для добавления модуля в DLE через governance.`;
alert(successMessage);
// Перенаправляем обратно к модулям
setTimeout(() => {
router.push(`/management/modules?address=${dleAddress.value}`);
}, 2000);
}, 3000);
} else {
throw new Error(result.error || 'Неизвестная ошибка');
@@ -440,6 +507,22 @@ onMounted(() => {
margin-bottom: 0;
}
/* Секция администратора */
.admin-section {
margin-bottom: 20px;
padding: 20px;
background: #fff3cd;
border-radius: var(--radius-sm);
border: 1px solid #ffeaa7;
}
.admin-section h5 {
margin: 0 0 15px 0;
color: #856404;
font-size: 1.1rem;
font-weight: 600;
}
/* Простая информация */
.simple-info {
margin-top: 20px;
@@ -582,4 +665,43 @@ onMounted(() => {
color: #666;
font-family: 'Courier New', monospace;
}
/* Сообщение об отсутствии прав доступа */
.no-access-message {
background: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: var(--radius-md);
padding: 30px;
margin: 20px 0;
text-align: center;
}
.message-content h3 {
color: #856404;
margin-bottom: 15px;
font-size: 1.4em;
}
.message-content p {
color: #856404;
margin-bottom: 20px;
font-size: 1.1em;
line-height: 1.5;
}
.message-content .btn {
background: #6c757d;
color: white;
border: none;
border-radius: var(--radius-sm);
padding: 12px 24px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: background 0.2s;
}
.message-content .btn:hover {
background: #5a6268;
}
</style>

View File

@@ -49,11 +49,11 @@
</div>
</div>
<!-- Форма деплоя модуля во всех сетях -->
<div class="deploy-form">
<!-- Форма деплоя модуля администратором -->
<div v-if="canManageSettings" class="deploy-form">
<div class="form-header">
<h3>🌐 Деплой TimelockModule во всех сетях</h3>
<p>Деплой модуля временных задержек во всех 4 сетях одновременно</p>
<h3>🔧 Деплой TimelockModule администратором</h3>
<p>Администратор деплоит модуль, затем создает предложение для добавления в DLE</p>
</div>
<div class="form-content">
@@ -85,193 +85,49 @@
<h4> Настройки TimelockModule:</h4>
<div class="settings-form">
<div class="form-row">
<div class="form-group">
<label for="chainId">ID сети:</label>
<select
id="chainId"
v-model="moduleSettings.chainId"
class="form-control"
required
>
<option value="11155111">Sepolia (11155111)</option>
<option value="17000">Holesky (17000)</option>
<option value="421614">Arbitrum Sepolia (421614)</option>
<option value="84532">Base Sepolia (84532)</option>
</select>
<small class="form-help">ID сети для деплоя модуля</small>
</div>
<div class="form-group">
<label for="defaultDelay">Стандартная задержка (дни):</label>
<input
type="number"
id="defaultDelay"
v-model="moduleSettings.defaultDelay"
class="form-control"
min="1"
max="30"
placeholder="2"
>
<small class="form-help">Стандартная задержка для операций (1-30 дней)</small>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="emergencyDelay">Экстренная задержка (минуты):</label>
<input
type="number"
id="emergencyDelay"
v-model="moduleSettings.emergencyDelay"
class="form-control"
min="5"
max="1440"
placeholder="30"
>
<small class="form-help">Экстренная задержка для критических операций (5-1440 минут)</small>
</div>
<div class="form-group">
<label for="maxDelay">Максимальная задержка (дни):</label>
<input
type="number"
id="maxDelay"
v-model="moduleSettings.maxDelay"
class="form-control"
min="1"
max="365"
placeholder="30"
>
<small class="form-help">Максимальная задержка для операций (1-365 дней)</small>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="minDelay">Минимальная задержка (часы):</label>
<input
type="number"
id="minDelay"
v-model="moduleSettings.minDelay"
class="form-control"
min="1"
max="720"
placeholder="24"
>
<small class="form-help">Минимальная задержка для операций (1-720 часов)</small>
</div>
<div class="form-group">
<label for="maxOperations">Максимум операций в очереди:</label>
<input
type="number"
id="maxOperations"
v-model="moduleSettings.maxOperations"
class="form-control"
min="10"
max="1000"
placeholder="100"
>
<small class="form-help">Максимальное количество операций в очереди (10-1000)</small>
</div>
</div>
<!-- Дополнительные настройки таймлока -->
<div class="advanced-settings">
<h5>🔧 Дополнительные настройки таймлока:</h5>
<div class="form-group">
<label for="criticalOperations">Критические операции (JSON формат):</label>
<textarea
id="criticalOperations"
v-model="moduleSettings.criticalOperations"
class="form-control"
rows="3"
placeholder='["0x12345678", "0x87654321"]'
></textarea>
<small class="form-help">Селекторы функций, которые считаются критическими (JSON массив)</small>
</div>
<div class="form-group">
<label for="emergencyOperations">Экстренные операции (JSON формат):</label>
<textarea
id="emergencyOperations"
v-model="moduleSettings.emergencyOperations"
class="form-control"
rows="3"
placeholder='["0xabcdef12", "0x21fedcba"]'
></textarea>
<small class="form-help">Селекторы функций для экстренных операций (JSON массив)</small>
</div>
<!-- Поля администратора -->
<div class="admin-section">
<h5>🔐 Настройки администратора:</h5>
<div class="form-row">
<div class="form-group">
<label for="operationDelays">Задержки для операций (JSON формат):</label>
<textarea
id="operationDelays"
v-model="moduleSettings.operationDelays"
class="form-control"
rows="4"
placeholder='{"0x12345678": 86400, "0x87654321": 172800}'
></textarea>
<small class="form-help">Кастомные задержки для конкретных операций (селектор => секунды)</small>
<label for="adminPrivateKey">Приватный ключ администратора:</label>
<input
type="password"
id="adminPrivateKey"
v-model="moduleSettings.adminPrivateKey"
class="form-control"
placeholder="0x..."
required
>
<small class="form-help">Приватный ключ для деплоя модуля (администратор платит газ)</small>
</div>
<div class="form-group">
<label for="autoExecuteEnabled">Автоисполнение включено:</label>
<select
id="autoExecuteEnabled"
v-model="moduleSettings.autoExecuteEnabled"
<label for="etherscanApiKey">Etherscan API ключ:</label>
<input
type="text"
id="etherscanApiKey"
v-model="moduleSettings.etherscanApiKey"
class="form-control"
placeholder="YourAPIKey..."
>
<option value="true">Включено</option>
<option value="false">Отключено</option>
</select>
<small class="form-help">Автоматическое исполнение операций после истечения задержки</small>
<small class="form-help">API ключ для автоматической верификации контрактов</small>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="cancellationWindow">Окно отмены (часы):</label>
<input
type="number"
id="cancellationWindow"
v-model="moduleSettings.cancellationWindow"
class="form-control"
min="1"
max="168"
placeholder="24"
>
<small class="form-help">Время, в течение которого можно отменить операцию (1-168 часов)</small>
</div>
<div class="form-group">
<label for="executionWindow">Окно исполнения (часы):</label>
<input
type="number"
id="executionWindow"
v-model="moduleSettings.executionWindow"
class="form-control"
min="1"
max="168"
placeholder="48"
>
<small class="form-help">Время, в течение которого можно исполнить операцию (1-168 часов)</small>
</div>
</div>
<div class="form-group">
<label for="timelockDescription">Описание таймлока:</label>
<textarea
id="timelockDescription"
v-model="moduleSettings.timelockDescription"
class="form-control"
rows="2"
placeholder="Описание таймлока DLE для безопасности операций..."
></textarea>
<small class="form-help">Описание таймлока для документации</small>
</div>
<div class="simple-info">
<h5>📋 Информация о TimelockModule:</h5>
<div class="info-text">
<p><strong>TimelockModule</strong> будет задеплоен с настройками по умолчанию:</p>
<ul>
<li> Стандартная задержка: 2 дня</li>
<li> Экстренная задержка: 30 минут</li>
<li> Автоматическое исполнение операций</li>
<li> Готовые настройки безопасности</li>
</ul>
<p><em>После деплоя настройки можно будет изменить через governance.</em></p>
</div>
</div>
</div>
@@ -282,12 +138,17 @@
<button
class="btn btn-primary btn-large deploy-module"
@click="deployTimelockModule"
:disabled="isDeploying || !dleAddress"
:disabled="isDeploying || !dleAddress || !isFormValid"
>
<i class="fas fa-rocket" :class="{ 'fa-spin': isDeploying }"></i>
{{ isDeploying ? 'Деплой модуля...' : 'Деплой TimelockModule' }}
</button>
<div v-if="!isFormValid && !isDeploying" class="form-validation-info">
<i class="fas fa-exclamation-triangle"></i>
<span>Заполните приватный ключ и API ключ для деплоя</span>
</div>
<div v-if="deploymentProgress" class="deployment-progress">
<div class="progress-info">
<span>{{ deploymentProgress.message }}</span>
@@ -301,14 +162,26 @@
</div>
</div>
<!-- Сообщение для пользователей без прав доступа -->
<div v-if="!canManageSettings" class="no-access-message">
<div class="message-content">
<h3>🔒 Нет прав доступа</h3>
<p>У вас нет прав для деплоя смарт-контрактов. Только пользователи с ролью Editor могут выполнять деплой.</p>
<button class="btn btn-secondary" @click="router.push('/management/modules')">
Вернуться к модулям
</button>
</div>
</div>
</div>
</BaseLayout>
</template>
<script setup>
import { defineProps, defineEmits, ref, onMounted } from 'vue';
import { defineProps, defineEmits, ref, computed, onMounted } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import BaseLayout from '../../../components/BaseLayout.vue';
import { usePermissions } from '@/composables/usePermissions';
// Определяем props
const props = defineProps({
@@ -323,6 +196,7 @@ const emit = defineEmits(['auth-action-completed']);
const router = useRouter();
const route = useRoute();
const { canEdit, canManageSettings } = usePermissions();
// Состояние
const isLoading = ref(false);
@@ -332,26 +206,23 @@ const deploymentProgress = ref(null);
// Настройки модуля
const moduleSettings = ref({
// Основные параметры
chainId: 11155111,
defaultDelay: 2, // days
emergencyDelay: 30, // minutes
maxDelay: 30, // days
minDelay: 24, // hours
// Дополнительные настройки
maxOperations: 100,
criticalOperations: '',
emergencyOperations: '',
operationDelays: '',
autoExecuteEnabled: 'true',
cancellationWindow: 24, // hours
executionWindow: 48, // hours
timelockDescription: ''
// Поля администратора
adminPrivateKey: '',
etherscanApiKey: ''
});
// Проверка валидности формы
const isFormValid = computed(() => {
return moduleSettings.value.adminPrivateKey && moduleSettings.value.etherscanApiKey;
});
// Функция деплоя TimelockModule
async function deployTimelockModule() {
if (!canManageSettings.value) {
alert('У вас нет прав для деплоя смарт-контрактов');
return;
}
try {
isDeploying.value = true;
deploymentProgress.value = {
@@ -361,8 +232,8 @@ async function deployTimelockModule() {
console.log('[TimelockModuleDeployView] Начинаем деплой TimelockModule для DLE:', dleAddress.value);
// Вызываем API для деплоя модуля во всех сетях
const response = await fetch('/api/dle-modules/deploy-timelock', {
// Вызываем API для деплоя модуля администратором
const response = await fetch('/api/dle-modules/deploy-timelock-admin', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -370,23 +241,11 @@ async function deployTimelockModule() {
body: JSON.stringify({
dleAddress: dleAddress.value,
moduleType: 'timelock',
adminPrivateKey: moduleSettings.value.adminPrivateKey,
etherscanApiKey: moduleSettings.value.etherscanApiKey,
settings: {
// Основные параметры
chainId: moduleSettings.value.chainId,
defaultDelay: moduleSettings.value.defaultDelay * 24 * 60 * 60, // конвертируем дни в секунды
emergencyDelay: moduleSettings.value.emergencyDelay * 60, // конвертируем минуты в секунды
maxDelay: moduleSettings.value.maxDelay * 24 * 60 * 60, // конвертируем дни в секунды
minDelay: moduleSettings.value.minDelay * 60 * 60, // конвертируем часы в секунды
// Дополнительные настройки
maxOperations: parseInt(moduleSettings.value.maxOperations),
criticalOperations: moduleSettings.value.criticalOperations ? JSON.parse(moduleSettings.value.criticalOperations) : [],
emergencyOperations: moduleSettings.value.emergencyOperations ? JSON.parse(moduleSettings.value.emergencyOperations) : [],
operationDelays: moduleSettings.value.operationDelays ? JSON.parse(moduleSettings.value.operationDelays) : {},
autoExecuteEnabled: moduleSettings.value.autoExecuteEnabled === 'true',
cancellationWindow: moduleSettings.value.cancellationWindow * 60 * 60, // конвертируем часы в секунды
executionWindow: moduleSettings.value.executionWindow * 60 * 60, // конвертируем часы в секунды
timelockDescription: moduleSettings.value.timelockDescription
// Используем настройки по умолчанию
useDefaultSettings: true
}
})
});
@@ -406,12 +265,31 @@ async function deployTimelockModule() {
percentage: 100
};
alert('✅ Деплой TimelockModule запущен во всех сетях!');
// Показываем детальную информацию о деплое
const deployInfo = result.data || {};
const deployedAddresses = deployInfo.addresses || [];
let successMessage = '✅ TimelockModule успешно задеплоен!\n\n';
successMessage += `📊 Детали деплоя:\n`;
successMessage += `• DLE: ${dleAddress.value}\n`;
successMessage += `• Тип модуля: TimelockModule\n`;
successMessage += `• Адрес модуля: ${deployInfo.moduleAddress || 'Не указан'}\n`;
if (deployedAddresses.length > 0) {
successMessage += `\n🌐 Задеплоенные адреса:\n`;
deployedAddresses.forEach((addr, index) => {
successMessage += `${index + 1}. ${addr.network}: ${addr.address}\n`;
});
}
successMessage += `\n📝 Следующий шаг: Создайте предложение для добавления модуля в DLE через governance.`;
alert(successMessage);
// Перенаправляем обратно к модулям
setTimeout(() => {
router.push(`/management/modules?address=${dleAddress.value}`);
}, 2000);
}, 3000);
} else {
throw new Error(result.error || 'Неизвестная ошибка');
@@ -569,6 +447,22 @@ onMounted(() => {
line-height: 1.4;
}
/* Секция администратора */
.admin-section {
margin-bottom: 20px;
padding: 20px;
background: #fff3cd;
border-radius: var(--radius-sm);
border: 1px solid #ffeaa7;
}
.admin-section h5 {
margin: 0 0 15px 0;
color: #856404;
font-size: 1.1rem;
font-weight: 600;
}
/* Дополнительные настройки */
.advanced-settings {
margin-top: 20px;
@@ -725,6 +619,45 @@ onMounted(() => {
margin: 0;
}
/* Сообщение об отсутствии прав доступа */
.no-access-message {
background: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: var(--radius-md);
padding: 30px;
margin: 20px 0;
text-align: center;
}
.message-content h3 {
color: #856404;
margin-bottom: 15px;
font-size: 1.4em;
}
.message-content p {
color: #856404;
margin-bottom: 20px;
font-size: 1.1em;
line-height: 1.5;
}
.message-content .btn {
background: #6c757d;
color: white;
border: none;
border-radius: var(--radius-sm);
padding: 12px 24px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: background 0.2s;
}
.message-content .btn:hover {
background: #5a6268;
}
/* Адаптивность */
@media (max-width: 768px) {
.info-grid {

View File

@@ -49,11 +49,11 @@
</div>
</div>
<!-- Форма деплоя модуля во всех сетях -->
<div class="deploy-form">
<!-- Форма деплоя модуля администратором -->
<div v-if="canManageSettings" class="deploy-form">
<div class="form-header">
<h3>🌐 Деплой TreasuryModule во всех сетях</h3>
<p>Деплой модуля казначейства во всех 4 сетях одновременно</p>
<p>Администратор деплоит модуль во всех 4 сетях одновременно, затем создает предложение для добавления в DLE</p>
</div>
<div class="form-content">
@@ -85,203 +85,52 @@
<h4> Настройки TreasuryModule:</h4>
<div class="settings-form">
<div class="form-row">
<div class="form-group">
<label for="emergencyAdmin">Адрес экстренного администратора:</label>
<input
type="text"
id="emergencyAdmin"
v-model="moduleSettings.emergencyAdmin"
class="form-control"
placeholder="0x..."
required
>
<small class="form-help">Адрес экстренного администратора для управления модулем</small>
</div>
<div class="form-group">
<label for="chainId">ID сети:</label>
<select
id="chainId"
v-model="moduleSettings.chainId"
class="form-control"
required
>
<option value="11155111">Sepolia (11155111)</option>
<option value="17000">Holesky (17000)</option>
<option value="421614">Arbitrum Sepolia (421614)</option>
<option value="84532">Base Sepolia (84532)</option>
</select>
<small class="form-help">ID сети для деплоя модуля</small>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="defaultDelay">Стандартная задержка (часы):</label>
<input
type="number"
id="defaultDelay"
v-model="moduleSettings.defaultDelay"
class="form-control"
min="1"
max="720"
placeholder="24"
>
<small class="form-help">Стандартная задержка для операций (1-720 часов)</small>
</div>
<div class="form-group">
<label for="emergencyDelay">Экстренная задержка (минуты):</label>
<input
type="number"
id="emergencyDelay"
v-model="moduleSettings.emergencyDelay"
class="form-control"
min="5"
max="1440"
placeholder="30"
>
<small class="form-help">Экстренная задержка для критических операций (5-1440 минут)</small>
</div>
</div>
<div class="form-group">
<label for="supportedTokens">Поддерживаемые токены (адреса через запятую):</label>
<textarea
id="supportedTokens"
v-model="moduleSettings.supportedTokens"
class="form-control"
rows="3"
placeholder="0x1234..., 0x5678..., 0x9abc..."
></textarea>
<small class="form-help">Адреса ERC20 токенов, которые будет поддерживать казначейство (через запятую)</small>
</div>
<div class="form-group">
<label for="gasPaymentTokens">Токены для оплаты газа (адреса через запятую):</label>
<textarea
id="gasPaymentTokens"
v-model="moduleSettings.gasPaymentTokens"
class="form-control"
rows="2"
placeholder="0x1234..., 0x5678..."
></textarea>
<small class="form-help">Токены, которыми можно оплачивать газ (через запятую)</small>
</div>
<!-- Дополнительные настройки казны -->
<div class="advanced-settings">
<h5>🔧 Дополнительные настройки казны:</h5>
<div class="form-row">
<div class="form-group">
<label for="paymasterAddress">Адрес Paymaster:</label>
<input
type="text"
id="paymasterAddress"
v-model="moduleSettings.paymasterAddress"
class="form-control"
placeholder="0x..."
>
<small class="form-help">Адрес Paymaster для ERC-4337 (оплата газа любым токеном)</small>
</div>
<div class="form-group">
<label for="maxBatchTransfers">Максимум batch переводов:</label>
<input
type="number"
id="maxBatchTransfers"
v-model="moduleSettings.maxBatchTransfers"
class="form-control"
min="1"
max="100"
placeholder="50"
>
<small class="form-help">Максимальное количество переводов в batch операции (1-100)</small>
</div>
</div>
<div class="form-row">
<div class="form-group">
<label for="gasTokenRates">Курсы токенов для газа (JSON формат):</label>
<textarea
id="gasTokenRates"
v-model="moduleSettings.gasTokenRates"
class="form-control"
rows="3"
placeholder='{"0x1234...": "1000000000000000000", "0x5678...": "2000000000000000000"}'
></textarea>
<small class="form-help">Курсы обмена токенов на нативную монету (JSON формат)</small>
</div>
<div class="form-group">
<label for="emergencyThreshold">Порог экстренных операций (ETH):</label>
<input
type="number"
id="emergencyThreshold"
v-model="moduleSettings.emergencyThreshold"
class="form-control"
min="0"
step="0.001"
placeholder="1.0"
>
<small class="form-help">Порог для экстренных операций в ETH</small>
</div>
</div>
<div class="form-group">
<label for="initialTokens">Начальные токены для добавления (JSON формат):</label>
<textarea
id="initialTokens"
v-model="moduleSettings.initialTokens"
class="form-control"
rows="4"
placeholder='[{"address": "0x1234...", "symbol": "USDC", "decimals": 6}, {"address": "0x5678...", "symbol": "USDT", "decimals": 6}]'
></textarea>
<small class="form-help">Токены для автоматического добавления при деплое (JSON массив)</small>
</div>
<!-- Поля администратора -->
<div class="admin-section">
<h5>🔐 Настройки администратора:</h5>
<div class="form-row">
<div class="form-group">
<label for="autoRefreshBalances">Автообновление балансов:</label>
<select
id="autoRefreshBalances"
v-model="moduleSettings.autoRefreshBalances"
<label for="adminPrivateKey">Приватный ключ администратора:</label>
<input
type="password"
id="adminPrivateKey"
v-model="moduleSettings.adminPrivateKey"
class="form-control"
placeholder="0x..."
required
>
<option value="true">Включено</option>
<option value="false">Отключено</option>
</select>
<small class="form-help">Автоматическое обновление балансов токенов</small>
<small class="form-help">Приватный ключ для деплоя модуля (администратор платит газ)</small>
</div>
<div class="form-group">
<label for="batchTransferEnabled">Batch переводы включены:</label>
<select
id="batchTransferEnabled"
v-model="moduleSettings.batchTransferEnabled"
<label for="etherscanApiKey">Etherscan API ключ:</label>
<input
type="text"
id="etherscanApiKey"
v-model="moduleSettings.etherscanApiKey"
class="form-control"
placeholder="YourAPIKey..."
>
<option value="true">Включено</option>
<option value="false">Отключено</option>
</select>
<small class="form-help">Разрешить batch операции переводов</small>
<small class="form-help">API ключ для автоматической верификации контрактов</small>
</div>
</div>
<div class="form-group">
<label for="treasuryDescription">Описание казны:</label>
<textarea
id="treasuryDescription"
v-model="moduleSettings.treasuryDescription"
class="form-control"
rows="2"
placeholder="Описание казны DLE для управления финансами..."
></textarea>
<small class="form-help">Описание казны для документации</small>
</div>
<div class="simple-info">
<h5>📋 Информация о TreasuryModule:</h5>
<div class="info-text">
<p><strong>TreasuryModule</strong> будет задеплоен с настройками по умолчанию:</p>
<ul>
<li> Поддержка ETH и основных ERC20 токенов</li>
<li> Стандартные задержки для безопасности</li>
<li> Автоматическая настройка для всех сетей</li>
<li> Готовые настройки безопасности</li>
</ul>
<p><em>После деплоя настройки можно будет изменить через governance.</em></p>
</div>
</div>
</div>
</div>
@@ -290,12 +139,17 @@
<button
class="btn btn-primary btn-large deploy-module"
@click="deployTreasuryModule"
:disabled="isDeploying || !dleAddress"
:disabled="isDeploying || !dleAddress || !isFormValid"
>
<i class="fas fa-rocket" :class="{ 'fa-spin': isDeploying }"></i>
{{ isDeploying ? 'Деплой модуля...' : 'Деплой TreasuryModule' }}
{{ isDeploying ? 'Деплой во всех сетях...' : 'Деплой TreasuryModule во всех сетях' }}
</button>
<div v-if="!isFormValid && !isDeploying" class="form-validation-info">
<i class="fas fa-exclamation-triangle"></i>
<span>Заполните приватный ключ и API ключ для деплоя</span>
</div>
<div v-if="deploymentProgress" class="deployment-progress">
<div class="progress-info">
<span>{{ deploymentProgress.message }}</span>
@@ -309,14 +163,26 @@
</div>
</div>
<!-- Сообщение для пользователей без прав доступа -->
<div v-if="!canManageSettings" class="no-access-message">
<div class="message-content">
<h3>🔒 Нет прав доступа</h3>
<p>У вас нет прав для деплоя смарт-контрактов. Только пользователи с ролью Editor могут выполнять деплой.</p>
<button class="btn btn-secondary" @click="router.push('/management/modules')">
Вернуться к модулям
</button>
</div>
</div>
</div>
</BaseLayout>
</template>
<script setup>
import { defineProps, defineEmits, ref, onMounted } from 'vue';
import { defineProps, defineEmits, ref, computed, onMounted } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import BaseLayout from '../../../components/BaseLayout.vue';
import { usePermissions } from '@/composables/usePermissions';
// Определяем props
const props = defineProps({
@@ -331,6 +197,7 @@ const emit = defineEmits(['auth-action-completed']);
const router = useRouter();
const route = useRoute();
const { canEdit, canManageSettings } = usePermissions();
// Состояние
const isLoading = ref(false);
@@ -340,29 +207,23 @@ const deploymentProgress = ref(null);
// Настройки модуля
const moduleSettings = ref({
// Основные параметры
emergencyAdmin: '',
chainId: 11155111,
defaultDelay: 24, // hours
emergencyDelay: 30, // minutes
// Токены
supportedTokens: '',
gasPaymentTokens: '',
initialTokens: '',
// Дополнительные настройки
paymasterAddress: '',
maxBatchTransfers: 50,
gasTokenRates: '',
emergencyThreshold: 1.0,
autoRefreshBalances: 'true',
batchTransferEnabled: 'true',
treasuryDescription: ''
// Поля администратора
adminPrivateKey: '',
etherscanApiKey: ''
});
// Проверка валидности формы
const isFormValid = computed(() => {
return moduleSettings.value.adminPrivateKey && moduleSettings.value.etherscanApiKey;
});
// Функция деплоя TreasuryModule
async function deployTreasuryModule() {
if (!canManageSettings.value) {
alert('У вас нет прав для деплоя смарт-контрактов');
return;
}
try {
isDeploying.value = true;
deploymentProgress.value = {
@@ -372,8 +233,8 @@ async function deployTreasuryModule() {
console.log('[TreasuryModuleDeployView] Начинаем деплой TreasuryModule для DLE:', dleAddress.value);
// Вызываем API для деплоя модуля во всех сетях
const response = await fetch('/api/dle-modules/deploy-treasury', {
// Вызываем API для деплоя модуля администратором
const response = await fetch('/api/dle-modules/deploy-treasury-admin', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
@@ -381,26 +242,11 @@ async function deployTreasuryModule() {
body: JSON.stringify({
dleAddress: dleAddress.value,
moduleType: 'treasury',
adminPrivateKey: moduleSettings.value.adminPrivateKey,
etherscanApiKey: moduleSettings.value.etherscanApiKey,
settings: {
// Основные параметры
emergencyAdmin: moduleSettings.value.emergencyAdmin,
chainId: moduleSettings.value.chainId,
defaultDelay: moduleSettings.value.defaultDelay,
emergencyDelay: moduleSettings.value.emergencyDelay,
// Токены
supportedTokens: moduleSettings.value.supportedTokens.split(',').map(addr => addr.trim()).filter(addr => addr),
gasPaymentTokens: moduleSettings.value.gasPaymentTokens.split(',').map(addr => addr.trim()).filter(addr => addr),
initialTokens: moduleSettings.value.initialTokens ? JSON.parse(moduleSettings.value.initialTokens) : [],
// Дополнительные настройки
paymasterAddress: moduleSettings.value.paymasterAddress,
maxBatchTransfers: parseInt(moduleSettings.value.maxBatchTransfers),
gasTokenRates: moduleSettings.value.gasTokenRates ? JSON.parse(moduleSettings.value.gasTokenRates) : {},
emergencyThreshold: parseFloat(moduleSettings.value.emergencyThreshold),
autoRefreshBalances: moduleSettings.value.autoRefreshBalances === 'true',
batchTransferEnabled: moduleSettings.value.batchTransferEnabled === 'true',
treasuryDescription: moduleSettings.value.treasuryDescription
// Используем настройки по умолчанию
useDefaultSettings: true
}
})
});
@@ -420,12 +266,31 @@ async function deployTreasuryModule() {
percentage: 100
};
alert('✅ Деплой TreasuryModule запущен во всех сетях!');
// Показываем детальную информацию о деплое
const deployInfo = result.data || {};
const deployedAddresses = deployInfo.addresses || [];
let successMessage = '✅ TreasuryModule успешно задеплоен во всех сетях!\n\n';
successMessage += `📊 Детали деплоя:\n`;
successMessage += `• DLE: ${dleAddress.value}\n`;
successMessage += `• Тип модуля: TreasuryModule\n`;
successMessage += `• Сети: Sepolia, Holesky, Arbitrum Sepolia, Base Sepolia\n`;
if (deployedAddresses.length > 0) {
successMessage += `\n🌐 Задеплоенные адреса:\n`;
deployedAddresses.forEach((addr, index) => {
successMessage += `${index + 1}. ${addr.network}: ${addr.address}\n`;
});
}
successMessage += `\n📝 Следующий шаг: Создайте предложение для добавления модуля в DLE через governance.`;
alert(successMessage);
// Перенаправляем обратно к модулям
setTimeout(() => {
router.push(`/management/modules?address=${dleAddress.value}`);
}, 2000);
}, 3000);
} else {
throw new Error(result.error || 'Неизвестная ошибка');
@@ -583,6 +448,176 @@ onMounted(() => {
line-height: 1.4;
}
/* Секция администратора */
.admin-section {
margin-bottom: 20px;
padding: 20px;
background: #fff3cd;
border-radius: var(--radius-sm);
border: 1px solid #ffeaa7;
}
.admin-section h5 {
margin: 0 0 15px 0;
color: #856404;
font-size: 1.1rem;
font-weight: 600;
}
/* Простая информация */
.simple-info {
margin-top: 20px;
padding: 20px;
background: #f8f9fa;
border-radius: var(--radius-sm);
border: 1px solid #e9ecef;
}
.simple-info h5 {
margin: 0 0 15px 0;
color: var(--color-primary);
font-size: 1.1rem;
font-weight: 600;
}
.info-text {
color: #666;
line-height: 1.6;
}
.info-text ul {
margin: 10px 0;
padding-left: 20px;
}
.info-text li {
margin-bottom: 5px;
}
.token-item {
display: flex;
align-items: center;
gap: 10px;
margin-bottom: 10px;
}
.token-input-wrapper {
position: relative;
flex: 1;
}
.token-input {
width: 100%;
padding-right: 40px;
}
.token-input.is-valid {
border-color: #28a745;
box-shadow: 0 0 0 2px rgba(40, 167, 69, 0.1);
}
.token-input.is-invalid {
border-color: #dc3545;
box-shadow: 0 0 0 2px rgba(220, 53, 69, 0.1);
}
.validation-icon {
position: absolute;
right: 12px;
top: 50%;
transform: translateY(-50%);
width: 20px;
height: 20px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
font-size: 10px;
}
.validation-icon.valid {
background: #28a745;
color: white;
}
.validation-icon.invalid {
background: #dc3545;
color: white;
}
.validation-icon.loading {
background: #6c757d;
color: white;
}
.remove-token {
width: 36px;
height: 36px;
display: flex;
align-items: center;
justify-content: center;
border-radius: 50%;
background: #dc3545;
border: none;
color: white;
font-size: 12px;
transition: all 0.2s ease;
flex-shrink: 0;
}
.remove-token:hover {
background: #c82333;
transform: scale(1.05);
}
.remove-token:disabled {
background: #6c757d;
cursor: not-allowed;
transform: none;
}
.add-token {
margin-top: 15px;
width: 100%;
padding: 12px 20px;
border: 2px dashed #28a745;
background: #f8f9fa;
color: #28a745;
border-radius: var(--radius-sm);
font-weight: 500;
transition: all 0.3s ease;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
}
.add-token:hover {
background: #28a745;
color: white;
border-color: #28a745;
transform: translateY(-1px);
box-shadow: 0 4px 8px rgba(40, 167, 69, 0.2);
}
/* Сообщение валидации */
.form-validation-info {
display: flex;
align-items: center;
gap: 8px;
margin-top: 15px;
padding: 10px 15px;
background: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: var(--radius-sm);
color: #856404;
font-size: 14px;
}
.form-validation-info i {
color: #f39c12;
}
/* Дополнительные настройки */
.advanced-settings {
margin-top: 20px;
@@ -797,6 +832,45 @@ onMounted(() => {
margin: 0;
}
/* Сообщение об отсутствии прав доступа */
.no-access-message {
background: #fff3cd;
border: 1px solid #ffeaa7;
border-radius: var(--radius-md);
padding: 30px;
margin: 20px 0;
text-align: center;
}
.message-content h3 {
color: #856404;
margin-bottom: 15px;
font-size: 1.4em;
}
.message-content p {
color: #856404;
margin-bottom: 20px;
font-size: 1.1em;
line-height: 1.5;
}
.message-content .btn {
background: #6c757d;
color: white;
border: none;
border-radius: var(--radius-sm);
padding: 12px 24px;
font-size: 16px;
font-weight: 600;
cursor: pointer;
transition: background 0.2s;
}
.message-content .btn:hover {
background: #5a6268;
}
/* Адаптивность */
@media (max-width: 768px) {
.info-grid {