ваше сообщение коммита
This commit is contained in:
@@ -146,12 +146,17 @@ const props = defineProps({
|
||||
dleData: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
etherscanApiKey: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ''
|
||||
}
|
||||
},
|
||||
logoURI: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: '/uploads/logos/default-token.svg'
|
||||
},
|
||||
etherscanApiKey: {
|
||||
type: String,
|
||||
required: false,
|
||||
default: ''
|
||||
}
|
||||
});
|
||||
|
||||
// Events
|
||||
@@ -260,6 +265,7 @@ const startDeployment = async () => {
|
||||
initialAmounts: props.dleData.partners.map(p => p.amount).filter(amount => amount > 0),
|
||||
supportedChainIds: props.selectedNetworks.filter(id => id !== null && id !== undefined),
|
||||
currentChainId: props.selectedNetworks[0] || 1,
|
||||
logoURI: props.logoURI || '/uploads/logos/default-token.svg',
|
||||
privateKey: props.privateKey,
|
||||
etherscanApiKey: props.etherscanApiKey || '',
|
||||
autoVerifyAfterDeploy: false
|
||||
|
||||
@@ -113,39 +113,28 @@
|
||||
<div class="detail-item">
|
||||
<strong>Юрисдикция:</strong> {{ dle.jurisdiction }}
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<strong>Коды ОКВЭД:</strong> {{ dle.okvedCodes?.join(', ') || 'Не указаны' }}
|
||||
<div class="detail-item" v-if="dle.quorumPercentage">
|
||||
<strong>Кворум:</strong>
|
||||
<span class="quorum-info">{{ dle.quorumPercentage }}%</span>
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<strong>Партнеры:</strong>
|
||||
<span v-if="dle.partnerBalances && dle.partnerBalances.length > 0">
|
||||
{{ dle.participantCount || dle.partnerBalances.length }} участников
|
||||
<div class="partners-details">
|
||||
<div v-for="(partner, index) in dle.partnerBalances.slice(0, 3)" :key="index" class="partner-info">
|
||||
<span class="partner-address">{{ shortenAddress(partner.address) }}</span>
|
||||
<span class="partner-balance">{{ partner.balance }} токенов ({{ partner.percentage.toFixed(1) }}%)</span>
|
||||
</div>
|
||||
<div v-if="dle.partnerBalances.length > 3" class="more-partners">
|
||||
+{{ dle.partnerBalances.length - 3 }} еще
|
||||
</div>
|
||||
</div>
|
||||
</span>
|
||||
<span v-else>
|
||||
{{ dle.participantCount || 0 }} участников
|
||||
</span>
|
||||
<strong>Коды ОКВЭД:</strong> {{ dle.okvedCodes?.join(', ') || 'Не указаны' }}
|
||||
</div>
|
||||
<div class="detail-item">
|
||||
<strong>Статус:</strong>
|
||||
<span class="status active">Активен</span>
|
||||
</div>
|
||||
<div class="detail-item" v-if="verificationStatuses[dle.dleAddress]">
|
||||
<strong>Верификация:</strong>
|
||||
<ul class="verify-list">
|
||||
<li v-for="(info, chainId) in verificationStatuses[dle.dleAddress].chains" :key="chainId">
|
||||
Chain {{ chainId }}: {{ info.status || '—' }}<span v-if="info.guid"> (guid: {{ info.guid.slice(0,8) }}…)</span>
|
||||
</li>
|
||||
</ul>
|
||||
<button class="details-btn btn-sm" @click.stop="refreshVerification(dle.dleAddress)">Обновить статус</button>
|
||||
<div class="detail-item" v-if="dle.totalSupply">
|
||||
<strong>Общий объем токенов:</strong>
|
||||
<span class="token-supply">{{ parseFloat(dle.totalSupply).toLocaleString() }} {{ dle.symbol }}</span>
|
||||
</div>
|
||||
<div class="detail-item" v-if="dle.logoURI">
|
||||
<strong>Логотип:</strong>
|
||||
<span class="logo-info">Установлен</span>
|
||||
</div>
|
||||
<div class="detail-item" v-if="dle.creationTimestamp">
|
||||
<strong>Дата создания:</strong>
|
||||
<span class="creation-date">{{ formatTimestamp(dle.creationTimestamp) }}</span>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
@@ -187,8 +176,6 @@ const router = useRouter();
|
||||
// Состояние для DLE
|
||||
const deployedDles = ref([]);
|
||||
const isLoadingDles = ref(false);
|
||||
const verificationStatuses = ref({}); // { [address]: { address, chains: { [chainId]: { guid, status } } } }
|
||||
let verifyPollTimer = null;
|
||||
|
||||
|
||||
|
||||
@@ -308,18 +295,6 @@ async function loadDeployedDles() {
|
||||
|
||||
deployedDles.value = dlesWithBlockchainData;
|
||||
console.log('[ManagementView] Итоговый список DLE:', deployedDles.value);
|
||||
|
||||
// Подгружаем статусы верификации для всех адресов
|
||||
for (const dle of deployedDles.value) {
|
||||
try {
|
||||
const st = await api.get(`/dle-v2/verify/status/${dle.dleAddress}`);
|
||||
if (st.data?.success && st.data.data) {
|
||||
verificationStatuses.value[dle.dleAddress] = st.data.data;
|
||||
}
|
||||
} catch (e) {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
} else {
|
||||
console.error('[ManagementView] Ошибка при загрузке DLE:', response.data.message);
|
||||
deployedDles.value = [];
|
||||
@@ -367,6 +342,19 @@ function getExplorerUrl(chainId, address) {
|
||||
return `${baseUrl}/address/${address}`;
|
||||
}
|
||||
|
||||
|
||||
function formatTimestamp(timestamp) {
|
||||
if (!timestamp) return '';
|
||||
const date = new Date(timestamp * 1000); // Конвертируем из Unix timestamp
|
||||
return date.toLocaleDateString('ru-RU', {
|
||||
year: 'numeric',
|
||||
month: 'long',
|
||||
day: 'numeric',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit'
|
||||
});
|
||||
}
|
||||
|
||||
function openDleOnEtherscan(address) {
|
||||
window.open(`https://sepolia.etherscan.io/address/${address}`, '_blank');
|
||||
}
|
||||
@@ -377,35 +365,6 @@ function openDleManagement(dleAddress) {
|
||||
}
|
||||
|
||||
|
||||
async function refreshVerification(address) {
|
||||
try {
|
||||
const resp = await api.post(`/dle-v2/verify/refresh/${address}`, {});
|
||||
if (resp.data?.success && resp.data.data) {
|
||||
verificationStatuses.value[address] = resp.data.data;
|
||||
}
|
||||
} catch (e) {
|
||||
// no-op
|
||||
}
|
||||
}
|
||||
|
||||
function isTerminalStatus(status) {
|
||||
if (!status) return false;
|
||||
const s = String(status).toLowerCase();
|
||||
return s.includes('pass') || s.includes('verified') || s.startsWith('error');
|
||||
}
|
||||
|
||||
async function pollVerifications() {
|
||||
try {
|
||||
const addresses = Object.keys(verificationStatuses.value || {});
|
||||
for (const addr of addresses) {
|
||||
const chains = verificationStatuses.value[addr]?.chains || {};
|
||||
const hasPending = Object.values(chains).some((c) => !isTerminalStatus(c.status));
|
||||
if (hasPending) {
|
||||
await refreshVerification(addr);
|
||||
}
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
// function openMultisig() {
|
||||
// router.push('/management/multisig');
|
||||
@@ -416,14 +375,6 @@ async function pollVerifications() {
|
||||
|
||||
onMounted(() => {
|
||||
loadDeployedDles();
|
||||
verifyPollTimer = setInterval(pollVerifications, 15000);
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (verifyPollTimer) {
|
||||
clearInterval(verifyPollTimer);
|
||||
verifyPollTimer = null;
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
@@ -813,55 +764,31 @@ onBeforeUnmount(() => {
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
/* Стили для отображения партнеров */
|
||||
.partners-details {
|
||||
margin-top: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
background: #f8f9fa;
|
||||
border-radius: 6px;
|
||||
border-left: 3px solid var(--color-primary);
|
||||
}
|
||||
|
||||
.partner-info {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
padding: 0.25rem 0;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
|
||||
.partner-info:not(:last-child) {
|
||||
border-bottom: 1px solid #e9ecef;
|
||||
}
|
||||
|
||||
.partner-address {
|
||||
font-family: 'Courier New', monospace;
|
||||
color: #495057;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.partner-balance {
|
||||
/* Стили для новых элементов */
|
||||
.token-supply {
|
||||
color: var(--color-primary);
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.more-partners {
|
||||
text-align: center;
|
||||
color: #6c757d;
|
||||
font-style: italic;
|
||||
font-size: 0.8rem;
|
||||
padding: 0.25rem 0;
|
||||
.logo-info {
|
||||
color: #28a745;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.quorum-info {
|
||||
color: #fd7e14;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.creation-date {
|
||||
color: #6c757d;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
|
||||
/* Адаптивность */
|
||||
@media (max-width: 768px) {
|
||||
|
||||
.partner-info {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
gap: 0.25rem;
|
||||
}
|
||||
|
||||
.dle-title-section {
|
||||
flex-direction: column;
|
||||
align-items: flex-start;
|
||||
|
||||
@@ -693,9 +693,17 @@
|
||||
</div>
|
||||
|
||||
<!-- Основная информация DLE -->
|
||||
<div v-if="dleSettings.name || dleSettings.tokenSymbol" class="preview-section">
|
||||
<div v-if="dleSettings.name || dleSettings.tokenSymbol || logoPreviewUrl" class="preview-section">
|
||||
<h4>Основная информация DLE</h4>
|
||||
|
||||
<div v-if="logoPreviewUrl" class="preview-item">
|
||||
<strong>🎨 Логотип:</strong>
|
||||
<div style="display: flex; align-items: center; gap: 10px; margin-top: 5px;">
|
||||
<img :src="logoPreviewUrl" alt="Logo preview" style="width: 48px; height: 48px; border-radius: 6px; object-fit: contain; border: 1px solid #e9ecef;" />
|
||||
<span style="color: #666; font-size: 0.9em;">{{ logoFile?.name || 'ENS аватар' || 'Дефолтный логотип' }}</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div v-if="dleSettings.name" class="preview-item">
|
||||
<strong>📋 Название:</strong> {{ dleSettings.name }}
|
||||
</div>
|
||||
@@ -929,6 +937,7 @@
|
||||
:private-key="unifiedPrivateKey"
|
||||
:selected-networks="selectedNetworks"
|
||||
:dle-data="dleSettings"
|
||||
:logo-uri="getLogoURI()"
|
||||
:etherscan-api-key="etherscanApiKey"
|
||||
@deployment-completed="handleDeploymentCompleted"
|
||||
/>
|
||||
@@ -2751,6 +2760,19 @@ async function resolveEnsAvatar() {
|
||||
}
|
||||
}
|
||||
|
||||
// Функция для получения URI логотипа
|
||||
function getLogoURI() {
|
||||
if (logoFile.value) {
|
||||
// Если выбран файл, возвращаем временный URL для предпросмотра
|
||||
// В реальности файл будет загружен на сервер и получен настоящий URL
|
||||
return logoPreviewUrl.value || '/uploads/logos/default-token.svg';
|
||||
} else if (ensResolvedUrl.value) {
|
||||
return ensResolvedUrl.value;
|
||||
} else {
|
||||
return '/uploads/logos/default-token.svg';
|
||||
}
|
||||
}
|
||||
|
||||
async function submitDeploy() {
|
||||
try {
|
||||
// Подготовка данных формы
|
||||
|
||||
@@ -289,7 +289,10 @@
|
||||
<span>{{ chain.name }} ({{ chain.chainId }})</span>
|
||||
</label>
|
||||
</div>
|
||||
<small class="text-muted">Для offchain‑действий целевые сети не требуются.</small>
|
||||
<small class="text-muted">Выберите хотя бы одну целевую сеть для исполнения операции.</small>
|
||||
<div v-if="showTargetChains && newProposal.targetChains.length === 0" class="form-error">
|
||||
<small class="text-danger">⚠️ Необходимо выбрать хотя бы одну целевую сеть</small>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
@@ -958,7 +961,8 @@ const isFormValid = computed(() => {
|
||||
newProposal.value.governanceChainId &&
|
||||
newProposal.value.operationType &&
|
||||
newProposal.value.timelockHours >= 0 &&
|
||||
validateOperationParams()
|
||||
validateOperationParams() &&
|
||||
validateTargetChains()
|
||||
);
|
||||
});
|
||||
|
||||
@@ -1069,6 +1073,14 @@ function validateOperationParams() {
|
||||
}
|
||||
}
|
||||
|
||||
function validateTargetChains() {
|
||||
// Если показываем целевые сети, то должна быть выбрана хотя бы одна
|
||||
if (showTargetChains.value) {
|
||||
return newProposal.value.targetChains.length > 0;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
function validateAddress(address) {
|
||||
if (!address) return false;
|
||||
// Проверяем формат Ethereum адреса
|
||||
@@ -1582,22 +1594,22 @@ function encodeBurnOperation(from, amount) {
|
||||
}
|
||||
|
||||
function encodeUpdateDLEInfoOperation(name, symbol, location, coordinates, jurisdiction, oktmo, kpp) {
|
||||
// Селектор для updateDLEInfo(string,string,string,string,uint256,uint256,string[],uint256)
|
||||
const selector = '0x' + ethers.keccak256(ethers.toUtf8Bytes('updateDLEInfo(string,string,string,string,uint256,uint256,string[],uint256)')).slice(0, 10);
|
||||
// Селектор для _updateDLEInfo(string,string,string,string,uint256,string[],uint256)
|
||||
const selector = '0x' + ethers.keccak256(ethers.toUtf8Bytes('_updateDLEInfo(string,string,string,string,uint256,string[],uint256)')).slice(0, 10);
|
||||
|
||||
// Кодируем параметры
|
||||
const abiCoder = new ethers.AbiCoder();
|
||||
const encodedData = abiCoder.encode(
|
||||
['string', 'string', 'string', 'string', 'uint256', 'uint256', 'string[]', 'uint256'],
|
||||
[name, symbol, location, coordinates, jurisdiction, oktmo, [], kpp] // okvedCodes пока пустой массив
|
||||
['string', 'string', 'string', 'string', 'uint256', 'string[]', 'uint256'],
|
||||
[name, symbol, location, coordinates, jurisdiction, [], kpp] // okvedCodes пока пустой массив
|
||||
);
|
||||
|
||||
return selector + encodedData.slice(2);
|
||||
}
|
||||
|
||||
function encodeUpdateQuorumOperation(quorumPercentage) {
|
||||
// Селектор для updateQuorumPercentage(uint256)
|
||||
const selector = '0x' + ethers.keccak256(ethers.toUtf8Bytes('updateQuorumPercentage(uint256)')).slice(0, 10);
|
||||
// Селектор для _updateQuorumPercentage(uint256)
|
||||
const selector = '0x' + ethers.keccak256(ethers.toUtf8Bytes('_updateQuorumPercentage(uint256)')).slice(0, 10);
|
||||
|
||||
// Кодируем параметр
|
||||
const abiCoder = new ethers.AbiCoder();
|
||||
@@ -1607,8 +1619,8 @@ function encodeUpdateQuorumOperation(quorumPercentage) {
|
||||
}
|
||||
|
||||
function encodeUpdateChainOperation(chainId) {
|
||||
// Селектор для updateCurrentChainId(uint256)
|
||||
const selector = '0x' + ethers.keccak256(ethers.toUtf8Bytes('updateCurrentChainId(uint256)')).slice(0, 10);
|
||||
// Селектор для _updateCurrentChainId(uint256)
|
||||
const selector = '0x' + ethers.keccak256(ethers.toUtf8Bytes('_updateCurrentChainId(uint256)')).slice(0, 10);
|
||||
|
||||
// Кодируем параметр
|
||||
const abiCoder = new ethers.AbiCoder();
|
||||
@@ -2656,4 +2668,43 @@ onUnmounted(() => {
|
||||
color: #28a745;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
/* Стили для ошибок валидации */
|
||||
.form-error {
|
||||
margin-top: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
background-color: #f8d7da;
|
||||
border: 1px solid #f5c6cb;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.text-danger {
|
||||
color: #dc3545 !important;
|
||||
}
|
||||
|
||||
.targets-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
||||
gap: 0.5rem;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
.target-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
padding: 0.5rem;
|
||||
border: 1px solid #e9ecef;
|
||||
border-radius: 4px;
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
}
|
||||
|
||||
.target-item:hover {
|
||||
background-color: #f8f9fa;
|
||||
}
|
||||
|
||||
.target-item input[type="checkbox"] {
|
||||
margin: 0;
|
||||
}
|
||||
</style>
|
||||
@@ -840,14 +840,14 @@ async function loadModules() {
|
||||
active: m.isActive,
|
||||
id: m.moduleId
|
||||
})),
|
||||
modulesInitialized: modulesResponse.data.modulesInitialized,
|
||||
requiresGovernance: modulesResponse.data.requiresGovernance,
|
||||
totalModules: modulesResponse.data.totalModules,
|
||||
activeModules: modulesResponse.data.activeModules
|
||||
});
|
||||
|
||||
// Обновляем счетчики
|
||||
if (modulesResponse.data.modulesInitialized === false) {
|
||||
console.log('[ModulesView] Модули для DLE не инициализированы');
|
||||
if (modulesResponse.data.requiresGovernance === true) {
|
||||
console.log('[ModulesView] Модули требуют инициализации через governance');
|
||||
}
|
||||
|
||||
// Обновляем время последнего обновления
|
||||
|
||||
Reference in New Issue
Block a user