ваше сообщение коммита
This commit is contained in:
@@ -18,7 +18,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, watch, onMounted, computed } from 'vue';
|
||||
import { ref, watch, onMounted, computed, onUnmounted } from 'vue';
|
||||
import { RouterView } from 'vue-router';
|
||||
import { useAuth } from './composables/useAuth';
|
||||
import { fetchTokenBalances } from './services/tokens';
|
||||
@@ -142,6 +142,21 @@
|
||||
if (auth.isAuthenticated.value) {
|
||||
refreshTokenBalances();
|
||||
}
|
||||
|
||||
// Подписываемся на событие изменения настроек аутентификации
|
||||
const unsubscribe = eventBus.on('auth-settings-saved', () => {
|
||||
console.log('[App] Получено событие сохранения настроек аутентификации, обновляем балансы');
|
||||
if (auth.isAuthenticated.value) {
|
||||
refreshTokenBalances();
|
||||
}
|
||||
});
|
||||
|
||||
// Отписываемся при размонтировании компонента
|
||||
onUnmounted(() => {
|
||||
if (unsubscribe) {
|
||||
unsubscribe();
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
|
||||
|
||||
@@ -62,29 +62,16 @@
|
||||
<div v-if="isLoadingTokens" class="token-loading">
|
||||
Загрузка балансов...
|
||||
</div>
|
||||
<div v-else-if="!tokenBalances || Object.keys(tokenBalances).length === 0" class="token-no-data">
|
||||
<div v-else-if="!tokenBalances || tokenBalances.length === 0" class="token-no-data">
|
||||
Баланс не доступен
|
||||
</div>
|
||||
<div v-else>
|
||||
<div v-if="tokenBalances.eth" class="token-balance">
|
||||
<span class="token-name">ETH:</span>
|
||||
<span class="token-amount">{{ Number(tokenBalances.eth).toLocaleString() }}</span>
|
||||
<span class="token-symbol">{{ TOKEN_CONTRACTS.eth.symbol }}</span>
|
||||
<div class="token-balance-header">
|
||||
</div>
|
||||
<div v-if="tokenBalances.bsc" class="token-balance">
|
||||
<span class="token-name">BSC:</span>
|
||||
<span class="token-amount">{{ Number(tokenBalances.bsc).toLocaleString() }}</span>
|
||||
<span class="token-symbol">{{ TOKEN_CONTRACTS.bsc.symbol }}</span>
|
||||
</div>
|
||||
<div v-if="tokenBalances.arbitrum" class="token-balance">
|
||||
<span class="token-name">ARB:</span>
|
||||
<span class="token-amount">{{ Number(tokenBalances.arbitrum).toLocaleString() }}</span>
|
||||
<span class="token-symbol">{{ TOKEN_CONTRACTS.arbitrum.symbol }}</span>
|
||||
</div>
|
||||
<div v-if="tokenBalances.polygon" class="token-balance">
|
||||
<span class="token-name">POL:</span>
|
||||
<span class="token-amount">{{ Number(tokenBalances.polygon).toLocaleString() }}</span>
|
||||
<span class="token-symbol">{{ TOKEN_CONTRACTS.polygon.symbol }}</span>
|
||||
<div v-for="(token, index) in tokenBalances.data || []" :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>
|
||||
<span class="token-amount">{{ isNaN(Number(token.balance)) ? '—' : Number(token.balance).toLocaleString() }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -96,7 +83,6 @@
|
||||
|
||||
<script setup>
|
||||
import { defineProps, defineEmits, ref, onMounted, onBeforeUnmount, watch } from 'vue';
|
||||
import { TOKEN_CONTRACTS } from '../services/tokens';
|
||||
import { useRouter } from 'vue-router';
|
||||
import eventBus from '../utils/eventBus';
|
||||
|
||||
@@ -346,11 +332,6 @@ h3 {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.token-symbol {
|
||||
color: var(--color-text-light);
|
||||
margin-left: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.token-no-data,
|
||||
.user-info-empty {
|
||||
color: var(--color-text-light);
|
||||
@@ -432,4 +413,31 @@ h3 {
|
||||
padding: 0 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.token-balance-header {
|
||||
display: flex;
|
||||
font-weight: bold;
|
||||
color: var(--color-primary, #4caf50);
|
||||
gap: 10px;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.token-balance-row {
|
||||
display: flex;
|
||||
gap: 10px;
|
||||
align-items: center;
|
||||
margin-bottom: 4px;
|
||||
}
|
||||
.token-name {
|
||||
min-width: 80px;
|
||||
font-weight: 500;
|
||||
}
|
||||
.token-network {
|
||||
min-width: 70px;
|
||||
color: var(--color-dark, #333);
|
||||
}
|
||||
.token-amount {
|
||||
min-width: 80px;
|
||||
text-align: right;
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
</style>
|
||||
@@ -6,34 +6,6 @@ import axios from 'axios';
|
||||
* Предоставляет списки доступных сетей, URL RPC и функции для работы с ними
|
||||
*/
|
||||
export default function useBlockchainNetworks() {
|
||||
// Список стандартных URL для популярных сетей
|
||||
const defaultRpcUrls = {
|
||||
ethereum: 'https://mainnet.infura.io/v3/YOUR_API_KEY',
|
||||
sepolia: 'https://sepolia.infura.io/v3/YOUR_API_KEY',
|
||||
goerli: 'https://goerli.infura.io/v3/YOUR_API_KEY',
|
||||
holesky: 'https://holesky.infura.io/v3/YOUR_API_KEY',
|
||||
bsc: 'https://bsc-dataseed1.binance.org',
|
||||
'bsc-testnet': 'https://data-seed-prebsc-1-s1.binance.org:8545',
|
||||
polygon: 'https://polygon-rpc.com',
|
||||
mumbai: 'https://rpc-mumbai.maticvigil.com',
|
||||
arbitrum: 'https://arb1.arbitrum.io/rpc',
|
||||
'arbitrum-goerli': 'https://goerli-rollup.arbitrum.io/rpc',
|
||||
optimism: 'https://mainnet.optimism.io',
|
||||
'optimism-goerli': 'https://goerli.optimism.io',
|
||||
avalanche: 'https://api.avax.network/ext/bc/C/rpc',
|
||||
'avalanche-fuji': 'https://api.avax-test.network/ext/bc/C/rpc',
|
||||
gnosis: 'https://rpc.gnosischain.com',
|
||||
celo: 'https://forno.celo.org',
|
||||
fantom: 'https://rpc.ftm.tools',
|
||||
'fantom-testnet': 'https://rpc.testnet.fantom.network',
|
||||
harmony: 'https://api.harmony.one',
|
||||
metis: 'https://andromeda.metis.io/?owner=1088',
|
||||
aurora: 'https://mainnet.aurora.dev',
|
||||
cronos: 'https://evm.cronos.org',
|
||||
localhost: 'http://localhost:8545',
|
||||
ganache: 'http://localhost:7545'
|
||||
};
|
||||
|
||||
// Группы сетей для отображения в интерфейсе
|
||||
const networkGroups = [
|
||||
{
|
||||
@@ -96,21 +68,6 @@ export default function useBlockchainNetworks() {
|
||||
customChainId: null
|
||||
});
|
||||
|
||||
// Вычисляемое свойство для предложения URL на основе выбранной сети
|
||||
const defaultRpcUrlSuggestion = computed(() => {
|
||||
if (networkEntry.value.networkId && defaultRpcUrls[networkEntry.value.networkId]) {
|
||||
return defaultRpcUrls[networkEntry.value.networkId];
|
||||
}
|
||||
return '';
|
||||
});
|
||||
|
||||
// Функция для использования предложенного URL
|
||||
const useDefaultRpcUrl = () => {
|
||||
if (defaultRpcUrlSuggestion.value) {
|
||||
networkEntry.value.rpcUrl = defaultRpcUrlSuggestion.value;
|
||||
}
|
||||
};
|
||||
|
||||
// Функция для получения chainId по networkId
|
||||
const getChainIdByNetworkId = (networkId) => {
|
||||
for (const group of networkGroups) {
|
||||
@@ -215,16 +172,13 @@ export default function useBlockchainNetworks() {
|
||||
|
||||
return {
|
||||
// Данные
|
||||
defaultRpcUrls,
|
||||
networkGroups,
|
||||
networkEntry,
|
||||
defaultRpcUrlSuggestion,
|
||||
testingRpc,
|
||||
testingRpcId,
|
||||
networks, // Экспортируем плоский список сетей
|
||||
|
||||
// Методы
|
||||
useDefaultRpcUrl,
|
||||
getChainIdByNetworkId,
|
||||
validateAndPrepareNetworkConfig,
|
||||
resetNetworkEntry,
|
||||
|
||||
@@ -1,15 +1,12 @@
|
||||
import { ref, watch, onUnmounted } from 'vue';
|
||||
import { fetchTokenBalances } from '../services/tokens';
|
||||
import { useAuth } from './useAuth'; // Предполагаем, что useAuth предоставляет identities
|
||||
import eventBus from '../utils/eventBus';
|
||||
|
||||
export function useTokenBalances() {
|
||||
const auth = useAuth(); // Получаем доступ к состоянию аутентификации
|
||||
const tokenBalances = ref({
|
||||
eth: '0',
|
||||
bsc: '0',
|
||||
arbitrum: '0',
|
||||
polygon: '0',
|
||||
});
|
||||
const tokenBalances = ref([]); // теперь массив объектов
|
||||
const isLoadingTokens = ref(false);
|
||||
let balanceUpdateInterval = null;
|
||||
|
||||
const getIdentityValue = (type) => {
|
||||
@@ -23,28 +20,25 @@ export function useTokenBalances() {
|
||||
const walletAddress = auth.address?.value || getIdentityValue('wallet');
|
||||
if (walletAddress) {
|
||||
try {
|
||||
isLoadingTokens.value = true;
|
||||
console.log('[useTokenBalances] Запрос балансов для адреса:', walletAddress);
|
||||
const balances = await fetchTokenBalances(walletAddress);
|
||||
console.log('[useTokenBalances] Полученные балансы:', balances);
|
||||
tokenBalances.value = {
|
||||
eth: balances.eth || '0',
|
||||
bsc: balances.bsc || '0',
|
||||
arbitrum: balances.arbitrum || '0',
|
||||
polygon: balances.polygon || '0',
|
||||
};
|
||||
const response = await fetchTokenBalances(walletAddress);
|
||||
// Ожидаем, что response — это массив объектов
|
||||
tokenBalances.value = Array.isArray(response) ? response : (response?.data || []);
|
||||
console.log('[useTokenBalances] Обновленные балансы:', tokenBalances.value);
|
||||
} catch (error) {
|
||||
console.error('[useTokenBalances] Ошибка при обновлении балансов:', error);
|
||||
// Возможно, стоит сбросить балансы при ошибке
|
||||
tokenBalances.value = { eth: '0', bsc: '0', arbitrum: '0', polygon: '0' };
|
||||
tokenBalances.value = [];
|
||||
} finally {
|
||||
isLoadingTokens.value = false;
|
||||
}
|
||||
} else {
|
||||
console.log('[useTokenBalances] Не найден адрес кошелька для запроса балансов.');
|
||||
tokenBalances.value = { eth: '0', bsc: '0', arbitrum: '0', polygon: '0' };
|
||||
tokenBalances.value = [];
|
||||
}
|
||||
} else {
|
||||
console.log('[useTokenBalances] Пользователь не аутентифицирован, сброс балансов.');
|
||||
tokenBalances.value = { eth: '0', bsc: '0', arbitrum: '0', polygon: '0' };
|
||||
tokenBalances.value = [];
|
||||
}
|
||||
};
|
||||
|
||||
@@ -79,21 +73,31 @@ export function useTokenBalances() {
|
||||
// Если пользователь вышел, отвязал кошелек, или не аутентифицирован
|
||||
stopBalanceUpdates();
|
||||
// Сбрасываем балансы
|
||||
tokenBalances.value = { eth: '0', bsc: '0', arbitrum: '0', polygon: '0' };
|
||||
tokenBalances.value = [];
|
||||
}
|
||||
},
|
||||
{ immediate: true } // Запустить проверку сразу при инициализации
|
||||
);
|
||||
|
||||
// Остановка интервала при размонтировании
|
||||
// Подписываемся на событие для обновления баланса после сохранения настроек
|
||||
const unsubscribe = eventBus.on('auth-settings-saved', () => {
|
||||
console.log('[useTokenBalances] Получено событие сохранения настроек, обновляем балансы');
|
||||
updateBalances();
|
||||
});
|
||||
|
||||
// Остановка интервала и отписки при размонтировании
|
||||
onUnmounted(() => {
|
||||
stopBalanceUpdates();
|
||||
if (unsubscribe) {
|
||||
unsubscribe();
|
||||
}
|
||||
});
|
||||
|
||||
return {
|
||||
tokenBalances,
|
||||
isLoadingTokens,
|
||||
updateBalances,
|
||||
startBalanceUpdates, // Можно не экспортировать, если управление полностью автоматическое
|
||||
stopBalanceUpdates, // Можно не экспортировать
|
||||
startBalanceUpdates,
|
||||
stopBalanceUpdates,
|
||||
};
|
||||
}
|
||||
@@ -1,29 +1,5 @@
|
||||
import api from '../api/axios';
|
||||
|
||||
// Адреса смарт-контрактов токенов HB3A
|
||||
export const TOKEN_CONTRACTS = {
|
||||
eth: {
|
||||
address: '0xd95a45fc46a7300e6022885afec3d618d7d3f27c',
|
||||
symbol: 'HB3A',
|
||||
network: 'Ethereum',
|
||||
},
|
||||
bsc: {
|
||||
address: '0x1d47f12ffA279BFE59Ab16d56fBb10d89AECdD5D',
|
||||
symbol: 'HB3A',
|
||||
network: 'BSC',
|
||||
},
|
||||
arbitrum: {
|
||||
address: '0xdce769b847a0a697239777d0b1c7dd33b6012ba0',
|
||||
symbol: 'HB3A',
|
||||
network: 'Arbitrum',
|
||||
},
|
||||
polygon: {
|
||||
address: '0x351f59de4fedbdf7601f5592b93db3b9330c1c1d',
|
||||
symbol: 'HB3A',
|
||||
network: 'Polygon',
|
||||
},
|
||||
};
|
||||
|
||||
// Получение балансов токенов
|
||||
export const fetchTokenBalances = async (address = null) => {
|
||||
try {
|
||||
@@ -43,6 +19,7 @@ export const fetchTokenBalances = async (address = null) => {
|
||||
bsc: '0',
|
||||
arbitrum: '0',
|
||||
polygon: '0',
|
||||
sepolia: '0',
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -8,7 +8,6 @@
|
||||
<div class="setting-form">
|
||||
<p>Здесь будут настройки для конфигурации промптов</p>
|
||||
<textarea v-model="settings.prompt" placeholder="Базовый промпт для ИИ..." rows="5" class="form-control"></textarea>
|
||||
<button class="btn btn-primary" @click="saveSettings('prompt')">Сохранить</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -23,7 +22,6 @@
|
||||
Включить RAG
|
||||
</label>
|
||||
</div>
|
||||
<button class="btn btn-primary" @click="saveSettings('rag')">Сохранить</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -44,7 +42,6 @@
|
||||
Email
|
||||
</label>
|
||||
</div>
|
||||
<button class="btn btn-primary" @click="saveSettings('channels')">Сохранить</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -62,7 +59,6 @@
|
||||
<option value="gpt-4o">GPT-4o</option>
|
||||
</select>
|
||||
</div>
|
||||
<button class="btn btn-primary" @click="saveSettings('models')">Сохранить</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@@ -169,10 +165,6 @@ h3 {
|
||||
max-width: 500px; /* Ограничим ширину для select/textarea */
|
||||
}
|
||||
|
||||
.btn-primary {
|
||||
align-self: flex-start;
|
||||
}
|
||||
|
||||
@keyframes fadeIn {
|
||||
from { opacity: 0; }
|
||||
to { opacity: 1; }
|
||||
|
||||
98
frontend/src/views/settings/AuthTokensSettings.vue
Normal file
98
frontend/src/views/settings/AuthTokensSettings.vue
Normal file
@@ -0,0 +1,98 @@
|
||||
<template>
|
||||
<div class="auth-tokens-settings">
|
||||
<h4>Токены аутентификации</h4>
|
||||
<div v-if="authTokens.length > 0" class="tokens-list">
|
||||
<div v-for="(token, index) in authTokens" :key="token.address + token.network" class="token-entry">
|
||||
<span><strong>Название:</strong> {{ token.name }}</span>
|
||||
<span><strong>Адрес:</strong> {{ token.address }}</span>
|
||||
<span><strong>Сеть:</strong> {{ getNetworkLabel(token.network) }}</span>
|
||||
<span><strong>Мин. баланс:</strong> {{ token.minBalance }}</span>
|
||||
<button class="btn btn-danger btn-sm" @click="removeToken(index)">Удалить</button>
|
||||
</div>
|
||||
</div>
|
||||
<p v-else>Нет добавленных токенов аутентификации.</p>
|
||||
<div class="add-token-form">
|
||||
<h5>Добавить новый токен:</h5>
|
||||
<div class="form-group">
|
||||
<label>Название:</label>
|
||||
<input type="text" v-model="newToken.name" class="form-control" placeholder="test2">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Адрес:</label>
|
||||
<input type="text" v-model="newToken.address" class="form-control" placeholder="0x...">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Сеть:</label>
|
||||
<select v-model="newToken.network" class="form-control">
|
||||
<option value="">-- Выберите сеть --</option>
|
||||
<optgroup v-for="(group, groupIndex) in networkGroups" :key="groupIndex" :label="group.label">
|
||||
<option v-for="option in group.options" :key="option.value" :value="option.value">
|
||||
{{ option.label }}
|
||||
</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>Мин. баланс:</label>
|
||||
<input type="number" v-model.number="newToken.minBalance" class="form-control" placeholder="0">
|
||||
</div>
|
||||
<button class="btn btn-secondary" @click="addToken">Добавить токен</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive } from 'vue';
|
||||
import useBlockchainNetworks from '@/composables/useBlockchainNetworks';
|
||||
import api from '@/api/axios';
|
||||
const props = defineProps({
|
||||
authTokens: { type: Array, required: true }
|
||||
});
|
||||
const emit = defineEmits(['update']);
|
||||
const newToken = reactive({ name: '', address: '', network: '', minBalance: 0 });
|
||||
|
||||
const { networkGroups, networks } = useBlockchainNetworks();
|
||||
|
||||
async function addToken() {
|
||||
if (!newToken.name || !newToken.address || !newToken.network) {
|
||||
alert('Все поля обязательны');
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await api.post('/api/settings/auth-token', {
|
||||
...newToken,
|
||||
minBalance: Number(newToken.minBalance) || 0
|
||||
});
|
||||
emit('update');
|
||||
newToken.name = '';
|
||||
newToken.address = '';
|
||||
newToken.network = '';
|
||||
newToken.minBalance = 0;
|
||||
} catch (e) {
|
||||
alert('Ошибка при добавлении токена: ' + (e.response?.data?.error || e.message));
|
||||
}
|
||||
}
|
||||
|
||||
async function removeToken(index) {
|
||||
const token = props.authTokens[index];
|
||||
if (!token) return;
|
||||
if (!confirm(`Удалить токен ${token.name} (${token.address})?`)) return;
|
||||
try {
|
||||
await api.delete(`/api/settings/auth-token/${token.address}/${token.network}`);
|
||||
emit('update');
|
||||
} catch (e) {
|
||||
alert('Ошибка при удалении токена: ' + (e.response?.data?.error || e.message));
|
||||
}
|
||||
}
|
||||
|
||||
function getNetworkLabel(networkId) {
|
||||
const found = networks.value.find(n => n.value === networkId);
|
||||
return found ? found.label : networkId;
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.tokens-list { margin-bottom: 1rem; }
|
||||
.token-entry { display: flex; gap: 1rem; align-items: center; margin-bottom: 0.5rem; }
|
||||
.add-token-form { margin-top: 1rem; }
|
||||
</style>
|
||||
@@ -235,14 +235,6 @@
|
||||
<button class="btn btn-secondary" @click="addRpcConfig">Добавить RPC</button>
|
||||
</div>
|
||||
|
||||
<!-- Кнопка сохранения настроек RPC -->
|
||||
<div class="save-rpc-actions mt-3">
|
||||
<button class="btn btn-primary" @click="saveRpcSettingsWithFeedback" :disabled="isSavingRpc">
|
||||
<i class="fas" :class="isSavingRpc ? 'fa-spinner fa-spin' : 'fa-save'"></i>
|
||||
{{ isSavingRpc ? 'Сохранение...' : 'Сохранить RPC настройки' }}
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- 8. Выбор сети для деплоя -->
|
||||
<h4>Сеть для деплоя</h4>
|
||||
<div class="form-group">
|
||||
@@ -918,20 +910,24 @@ const toggleShowDeployerKey = () => {
|
||||
const loadRpcSettings = async () => {
|
||||
try {
|
||||
const response = await axios.get('/api/settings/rpc');
|
||||
console.log('Ответ сервера на /api/settings/rpc:', response.data);
|
||||
if (response.data && response.data.success) {
|
||||
securitySettings.rpcConfigs = response.data.data || [];
|
||||
securitySettings.rpcConfigs = (response.data.data || []).map(rpc => ({
|
||||
networkId: rpc.network_id,
|
||||
rpcUrl: rpc.rpc_url,
|
||||
chainId: rpc.chain_id
|
||||
}));
|
||||
console.log('[BlockchainSettingsView] RPC конфигурации успешно загружены:', securitySettings.rpcConfigs);
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[BlockchainSettingsView] Ошибка при загрузке RPC конфигураций:', error);
|
||||
// Если нужно, установить дефолтные RPC
|
||||
// setDefaultRpcConfigs();
|
||||
}
|
||||
};
|
||||
|
||||
// Функция сохранения настроек RPC на сервер
|
||||
const saveRpcSettings = async () => {
|
||||
try {
|
||||
console.log('Отправляемые RPC:', securitySettings.rpcConfigs);
|
||||
const response = await axios.post('/api/settings/rpc', {
|
||||
rpcConfigs: JSON.parse(JSON.stringify(securitySettings.rpcConfigs))
|
||||
});
|
||||
|
||||
@@ -15,7 +15,6 @@
|
||||
<!-- Добавить другие языки по необходимости -->
|
||||
</select>
|
||||
</div>
|
||||
<button class="btn btn-primary" @click="saveLanguageSetting">Сохранить язык</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
138
frontend/src/views/settings/RpcProvidersSettings.vue
Normal file
138
frontend/src/views/settings/RpcProvidersSettings.vue
Normal file
@@ -0,0 +1,138 @@
|
||||
<template>
|
||||
<div class="rpc-providers-settings">
|
||||
<h4>RPC Провайдеры</h4>
|
||||
<div v-if="Array.isArray(rpcConfigs) && rpcConfigs.length > 0" class="rpc-list">
|
||||
<div v-for="(rpc, index) in rpcConfigs" :key="rpc.networkId" class="rpc-entry">
|
||||
<span><strong>ID Сети:</strong> {{ rpc.networkId }}</span>
|
||||
<span><strong>URL:</strong> {{ rpc.rpcUrl }}</span>
|
||||
<span v-if="rpc.chainId"><strong>Chain ID:</strong> {{ rpc.chainId }}</span>
|
||||
<button class="btn btn-info btn-sm" @click="testRpc(rpc)" :disabled="testingRpc && testingRpcId === rpc.networkId">
|
||||
<i class="fas" :class="testingRpc && testingRpcId === rpc.networkId ? 'fa-spinner fa-spin' : 'fa-check-circle'"></i>
|
||||
{{ testingRpc && testingRpcId === rpc.networkId ? 'Проверка...' : 'Тест' }}
|
||||
</button>
|
||||
<button class="btn btn-danger btn-sm" @click="removeRpc(index)">Удалить</button>
|
||||
</div>
|
||||
</div>
|
||||
<p v-else>Нет добавленных RPC конфигураций.</p>
|
||||
<div class="add-rpc-form">
|
||||
<h5>Добавить новую RPC конфигурацию:</h5>
|
||||
<div class="form-group">
|
||||
<label>ID Сети:</label>
|
||||
<select v-model="networkEntry.networkId" class="form-control">
|
||||
<optgroup v-for="(group, groupIndex) in networkGroups" :key="groupIndex" :label="group.label">
|
||||
<option v-for="option in group.options" :key="option.value" :value="option.value">
|
||||
{{ option.label }}
|
||||
</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
<div v-if="networkEntry.networkId === 'custom'" class="mt-2">
|
||||
<label>Пользовательский ID:</label>
|
||||
<input type="text" v-model="networkEntry.customNetworkId" class="form-control" placeholder="Введите ID сети">
|
||||
<label class="mt-2">Chain ID:</label>
|
||||
<input type="number" v-model.number="networkEntry.customChainId" class="form-control" placeholder="Например, 1 для Ethereum">
|
||||
<small>Chain ID - уникальный идентификатор блокчейн-сети (целое число)</small>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label>RPC URL:</label>
|
||||
<input type="text" v-model="networkEntry.rpcUrl" class="form-control" placeholder="https://...">
|
||||
</div>
|
||||
<button class="btn btn-secondary" @click="addRpc">Добавить RPC</button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, toRefs } from 'vue';
|
||||
import useBlockchainNetworks from '@/composables/useBlockchainNetworks';
|
||||
import api from '@/api/axios';
|
||||
const props = defineProps({
|
||||
rpcConfigs: { type: Array, required: true, default: () => [] }
|
||||
});
|
||||
const emit = defineEmits(['update', 'test']);
|
||||
|
||||
const {
|
||||
networkGroups,
|
||||
networkEntry,
|
||||
validateAndPrepareNetworkConfig,
|
||||
resetNetworkEntry,
|
||||
testRpcConnection,
|
||||
testingRpc,
|
||||
testingRpcId
|
||||
} = useBlockchainNetworks();
|
||||
|
||||
async function addRpc() {
|
||||
const result = validateAndPrepareNetworkConfig();
|
||||
if (!result.valid) {
|
||||
alert(result.error);
|
||||
return;
|
||||
}
|
||||
const { networkId, rpcUrl, chainId } = result.networkConfig;
|
||||
if (props.rpcConfigs.some(rpc => rpc.networkId === networkId)) {
|
||||
alert(`Ошибка: RPC конфигурация для сети с ID '${networkId}' уже существует.`);
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await api.post('/api/settings/rpc', { networkId, rpcUrl, chainId });
|
||||
emit('update'); // сигнал родителю перезагрузить список
|
||||
resetNetworkEntry();
|
||||
} catch (e) {
|
||||
alert('Ошибка при добавлении RPC: ' + (e.response?.data?.error || e.message));
|
||||
}
|
||||
}
|
||||
|
||||
async function removeRpc(index) {
|
||||
const rpc = props.rpcConfigs[index];
|
||||
if (!rpc) return;
|
||||
if (!confirm(`Удалить RPC для сети ${rpc.networkId}?`)) return;
|
||||
try {
|
||||
await api.delete(`/api/settings/rpc/${rpc.networkId}`);
|
||||
emit('update');
|
||||
} catch (e) {
|
||||
alert('Ошибка при удалении RPC: ' + (e.response?.data?.error || e.message));
|
||||
}
|
||||
}
|
||||
|
||||
async function testRpc(rpc) {
|
||||
if (!rpc.networkId || !rpc.rpcUrl) {
|
||||
alert('Для теста RPC нужно указать и ID сети, и URL');
|
||||
return;
|
||||
}
|
||||
const result = await testRpcConnection(rpc.networkId, rpc.rpcUrl);
|
||||
if (result.success) {
|
||||
alert(result.message);
|
||||
} else {
|
||||
alert(`Ошибка при подключении к ${rpc.networkId}: ${result.error}`);
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.rpc-list { margin-bottom: 1rem; }
|
||||
.rpc-entry { display: flex; gap: 1rem; align-items: center; margin-bottom: 0.5rem; }
|
||||
.add-rpc-form { margin-top: 1rem; }
|
||||
.suggestion {
|
||||
background-color: rgba(76, 175, 80, 0.1);
|
||||
border-left: 3px solid var(--color-primary, #4caf50);
|
||||
padding: 6px 10px;
|
||||
margin-top: 8px;
|
||||
border-radius: 0 4px 4px 0;
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
.btn-link {
|
||||
background: none;
|
||||
border: none;
|
||||
padding: 0;
|
||||
color: var(--color-primary, #4caf50);
|
||||
text-decoration: underline;
|
||||
cursor: pointer;
|
||||
font-size: 0.875rem;
|
||||
}
|
||||
.btn-link:hover {
|
||||
color: var(--color-primary-dark, #388e3c);
|
||||
text-decoration: none;
|
||||
}
|
||||
.mt-2 { margin-top: 10px; }
|
||||
</style>
|
||||
@@ -38,134 +38,27 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Раскрывающиеся настройки RPC -->
|
||||
<div v-if="showRpcSettings" class="detail-panel">
|
||||
<h3>Настройки RPC Провайдеров</h3>
|
||||
<p class="env-note">Эти настройки сохраняются в .env файле на бэкенде.</p>
|
||||
|
||||
<!-- Список добавленных RPC -->
|
||||
<div v-if="securitySettings.rpcConfigs.length > 0" class="configs-list">
|
||||
<h4>Добавленные RPC конфигурации:</h4>
|
||||
<div v-for="(rpc, index) in securitySettings.rpcConfigs" :key="index" class="config-item">
|
||||
<div class="config-details">
|
||||
<div><strong>ID Сети:</strong> {{ rpc.networkId }}</div>
|
||||
<div><strong>URL:</strong> {{ rpc.rpcUrl }}</div>
|
||||
<div v-if="rpc.chainId"><strong>Chain ID:</strong> {{ rpc.chainId }}</div>
|
||||
</div>
|
||||
<div class="config-actions">
|
||||
<button class="btn btn-info" @click="testRpcHandler(rpc)" :disabled="testingRpc && testingRpcId === rpc.networkId">
|
||||
<i class="fas" :class="testingRpc && testingRpcId === rpc.networkId ? 'fa-spinner fa-spin' : 'fa-check-circle'"></i>
|
||||
{{ testingRpc && testingRpcId === rpc.networkId ? 'Проверка...' : 'Тест' }}
|
||||
</button>
|
||||
<button class="btn btn-danger" @click="removeRpcConfig(index)">
|
||||
<i class="fas fa-trash"></i> Удалить
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<p v-else>Нет добавленных RPC конфигураций.</p>
|
||||
|
||||
<!-- Форма добавления нового RPC -->
|
||||
<div class="setting-form add-form">
|
||||
<h4>Добавить новую RPC конфигурацию:</h4>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="newRpcNetworkId">ID Сети:</label>
|
||||
<select id="newRpcNetworkId" v-model="networkEntry.networkId" class="form-control">
|
||||
<optgroup v-for="(group, groupIndex) in networkGroups" :key="groupIndex" :label="group.label">
|
||||
<option v-for="option in group.options" :key="option.value" :value="option.value">
|
||||
{{ option.label }}
|
||||
</option>
|
||||
</optgroup>
|
||||
</select>
|
||||
<div v-if="networkEntry.networkId === 'custom'" class="mt-2">
|
||||
<label class="form-label" for="customNetworkId">Пользовательский ID:</label>
|
||||
<input type="text" id="customNetworkId" v-model="networkEntry.customNetworkId" class="form-control" placeholder="Введите ID сети">
|
||||
|
||||
<label class="form-label mt-2" for="customChainId">Chain ID:</label>
|
||||
<input type="number" id="customChainId" v-model="networkEntry.customChainId" class="form-control" placeholder="Например, 1 для Ethereum">
|
||||
<small>Chain ID - уникальный идентификатор блокчейн-сети (целое число)</small>
|
||||
</div>
|
||||
<small>ID сети должен совпадать со значением в выпадающем списке сетей при создании DLE</small>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="newRpcUrl">RPC URL:</label>
|
||||
<input type="text" id="newRpcUrl" v-model="networkEntry.rpcUrl" class="form-control" placeholder="https://...">
|
||||
<!-- Предложение URL на основе выбранной сети -->
|
||||
<small v-if="defaultRpcUrlSuggestion" class="suggestion">
|
||||
Предложение: {{ defaultRpcUrlSuggestion }}
|
||||
<button class="btn-link" @click="useDefaultRpcUrl">Использовать</button>
|
||||
</small>
|
||||
</div>
|
||||
<button class="btn btn-secondary" @click="addRpcConfig">Добавить RPC</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Раскрывающиеся настройки Аутентификации -->
|
||||
<div v-if="showAuthSettings" class="detail-panel">
|
||||
<h3>Настройки Аутентификации</h3>
|
||||
<p class="env-note">Эти настройки сохраняются в .env файле на бэкенде.</p>
|
||||
|
||||
<!-- Список токенов для проверки баланса -->
|
||||
<div v-if="securitySettings.authTokens.length > 0" class="configs-list">
|
||||
<h4>Токены для проверки при авторизации:</h4>
|
||||
<div v-for="(token, index) in securitySettings.authTokens" :key="index" class="config-item">
|
||||
<div class="config-details">
|
||||
<div><strong>Название:</strong> {{ token.name }}</div>
|
||||
<div><strong>Адрес:</strong> {{ token.address }}</div>
|
||||
<div><strong>Сеть:</strong> {{ token.network }}</div>
|
||||
<div><strong>Мин. баланс:</strong> {{ token.minBalance }}</div>
|
||||
</div>
|
||||
<button class="btn btn-danger" @click="removeAuthToken(index)">
|
||||
<i class="fas fa-trash"></i> Удалить
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<p v-else>Нет добавленных токенов для проверки при авторизации.</p>
|
||||
|
||||
<!-- Форма добавления нового токена для авторизации -->
|
||||
<div class="setting-form add-form">
|
||||
<h4>Добавить новый токен для проверки при авторизации:</h4>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="newTokenName">Название токена:</label>
|
||||
<input type="text" id="newTokenName" v-model="newAuthToken.name" class="form-control" placeholder="Например, MyToken">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="newTokenAddress">Адрес контракта:</label>
|
||||
<input type="text" id="newTokenAddress" v-model="newAuthToken.address" class="form-control" placeholder="0x...">
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="newTokenNetwork">Сеть:</label>
|
||||
<select id="newTokenNetwork" v-model="newAuthToken.network" class="form-control">
|
||||
<option v-for="option in networkOptions" :key="option.value" :value="option.value">
|
||||
{{ option.label }}
|
||||
</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="form-group">
|
||||
<label class="form-label" for="newTokenMinBalance">Минимальный баланс:</label>
|
||||
<input type="text" id="newTokenMinBalance" v-model="newAuthToken.minBalance" class="form-control" placeholder="1.0">
|
||||
<small>Минимальный баланс токена для успешной авторизации</small>
|
||||
</div>
|
||||
<button class="btn btn-secondary" @click="addAuthToken">Добавить токен</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="sub-settings-panel save-panel">
|
||||
<button class="btn btn-primary btn-lg" @click="saveSecuritySettings" :disabled="isSaving">
|
||||
<span v-if="isSaving">
|
||||
<i class="fas fa-spinner fa-spin"></i>
|
||||
Сохранение...
|
||||
</span>
|
||||
<span v-else>Сохранить все настройки</span>
|
||||
</button>
|
||||
</div>
|
||||
<RpcProvidersSettings
|
||||
v-if="showRpcSettings"
|
||||
:rpcConfigs="securitySettings.rpcConfigs"
|
||||
@update="loadSettings"
|
||||
@test="testRpcHandler"
|
||||
/>
|
||||
<AuthTokensSettings
|
||||
v-if="showAuthSettings"
|
||||
:authTokens="securitySettings.authTokens"
|
||||
@update="loadSettings"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { reactive, ref, onMounted, computed } from 'vue';
|
||||
import { reactive, ref, onMounted, computed, provide } from 'vue';
|
||||
import api from '@/api/axios';
|
||||
import useBlockchainNetworks from '@/composables/useBlockchainNetworks';
|
||||
import eventBus from '@/utils/eventBus';
|
||||
import RpcProvidersSettings from './RpcProvidersSettings.vue';
|
||||
import AuthTokensSettings from './AuthTokensSettings.vue';
|
||||
|
||||
// Состояние для отображения/скрытия дополнительных настроек
|
||||
const showRpcSettings = ref(false);
|
||||
@@ -183,8 +76,6 @@ const securitySettings = reactive({
|
||||
const {
|
||||
networkGroups,
|
||||
networkEntry,
|
||||
defaultRpcUrlSuggestion,
|
||||
useDefaultRpcUrl,
|
||||
validateAndPrepareNetworkConfig,
|
||||
resetNetworkEntry,
|
||||
testRpcConnection,
|
||||
@@ -239,13 +130,22 @@ const loadSettings = async () => {
|
||||
// Загрузка RPC конфигураций
|
||||
const rpcResponse = await api.get('/api/settings/rpc');
|
||||
if (rpcResponse.data && rpcResponse.data.success) {
|
||||
securitySettings.rpcConfigs = rpcResponse.data.data || [];
|
||||
securitySettings.rpcConfigs = (rpcResponse.data.data || []).map(rpc => ({
|
||||
networkId: rpc.network_id,
|
||||
rpcUrl: rpc.rpc_url,
|
||||
chainId: rpc.chain_id
|
||||
}));
|
||||
}
|
||||
|
||||
// Загрузка токенов для аутентификации
|
||||
const authResponse = await api.get('/api/settings/auth-tokens');
|
||||
if (authResponse.data && authResponse.data.success) {
|
||||
securitySettings.authTokens = authResponse.data.data || [];
|
||||
securitySettings.authTokens = (authResponse.data.data || []).map(token => ({
|
||||
name: token.name,
|
||||
address: token.address,
|
||||
network: token.network,
|
||||
minBalance: token.min_balance
|
||||
}));
|
||||
}
|
||||
|
||||
console.log('[SecuritySettingsView] Настройки успешно загружены с бэкенда');
|
||||
@@ -263,79 +163,8 @@ const loadSettings = async () => {
|
||||
|
||||
// Установка дефолтных значений
|
||||
const setDefaultSettings = () => {
|
||||
securitySettings.rpcConfigs = [
|
||||
{ networkId: 'ethereum', rpcUrl: 'https://mainnet.infura.io/v3/YOUR_API_KEY' },
|
||||
{ networkId: 'sepolia', rpcUrl: 'https://sepolia.infura.io/v3/YOUR_API_KEY' },
|
||||
{ networkId: 'bsc', rpcUrl: 'https://bsc-dataseed1.binance.org' },
|
||||
{ networkId: 'arbitrum', rpcUrl: 'https://arb1.arbitrum.io/rpc' },
|
||||
{ networkId: 'polygon', rpcUrl: 'https://polygon-rpc.com' }
|
||||
];
|
||||
|
||||
securitySettings.authTokens = [
|
||||
{ name: 'Ethereum Token', address: '0xd95a45fc46a7300e6022885afec3d618d7d3f27c', network: 'eth', minBalance: '1.0' },
|
||||
{ name: 'BSC Token', address: '0x4B294265720B09ca39BFBA18c7E368413c0f68eB', network: 'bsc', minBalance: '10.0' },
|
||||
{ name: 'Arbitrum Token', address: '0xdce769b847a0a697239777d0b1c7dd33b6012ba0', network: 'arbitrum', minBalance: '0.5' },
|
||||
{ name: 'Custom Token', address: '0x351f59de4fedbdf7601f5592b93db3b9330c1c1d', network: 'eth', minBalance: '5.0' }
|
||||
];
|
||||
};
|
||||
|
||||
// Функция добавления новой RPC конфигурации
|
||||
const addRpcConfig = () => {
|
||||
const result = validateAndPrepareNetworkConfig();
|
||||
|
||||
if (!result.valid) {
|
||||
alert(result.error);
|
||||
return;
|
||||
}
|
||||
|
||||
const { networkId, rpcUrl, chainId } = result.networkConfig;
|
||||
|
||||
// Проверка на дубликат ID
|
||||
if (securitySettings.rpcConfigs.some(rpc => rpc.networkId === networkId)) {
|
||||
alert(`Ошибка: RPC конфигурация для сети с ID '${networkId}' уже существует.`);
|
||||
return;
|
||||
}
|
||||
|
||||
securitySettings.rpcConfigs.push({ networkId, rpcUrl, chainId });
|
||||
|
||||
// Очистка полей ввода
|
||||
resetNetworkEntry();
|
||||
};
|
||||
|
||||
// Функция удаления RPC конфигурации
|
||||
const removeRpcConfig = (index) => {
|
||||
securitySettings.rpcConfigs.splice(index, 1);
|
||||
};
|
||||
|
||||
// Функция добавления нового токена для авторизации
|
||||
const addAuthToken = () => {
|
||||
const name = newAuthToken.name.trim();
|
||||
const address = newAuthToken.address.trim();
|
||||
const minBalance = newAuthToken.minBalance.trim();
|
||||
const network = newAuthToken.network;
|
||||
|
||||
if (name && address) {
|
||||
// Проверка на дубликат адреса
|
||||
if (securitySettings.authTokens.some(token => token.address.toLowerCase() === address.toLowerCase())) {
|
||||
alert(`Ошибка: Токен с адресом '${address}' уже добавлен.`);
|
||||
return;
|
||||
}
|
||||
|
||||
securitySettings.authTokens.push({ name, address, minBalance, network });
|
||||
|
||||
// Очистка полей ввода
|
||||
newAuthToken.name = '';
|
||||
newAuthToken.address = '';
|
||||
newAuthToken.minBalance = '1.0';
|
||||
newAuthToken.network = 'eth';
|
||||
} else {
|
||||
alert('Пожалуйста, заполните название и адрес токена.');
|
||||
}
|
||||
};
|
||||
|
||||
// Функция удаления токена авторизации
|
||||
const removeAuthToken = (index) => {
|
||||
securitySettings.authTokens.splice(index, 1);
|
||||
securitySettings.rpcConfigs = [];
|
||||
securitySettings.authTokens = [];
|
||||
};
|
||||
|
||||
// Сохранение всех настроек безопасности на бэкенд
|
||||
@@ -343,8 +172,12 @@ const saveSecuritySettings = async () => {
|
||||
isSaving.value = true;
|
||||
try {
|
||||
// Подготовка данных для отправки
|
||||
const validRpcConfigs = securitySettings.rpcConfigs.filter(
|
||||
c => c.networkId && c.rpcUrl
|
||||
);
|
||||
|
||||
const settingsData = {
|
||||
rpcConfigs: JSON.parse(JSON.stringify(securitySettings.rpcConfigs)),
|
||||
rpcConfigs: validRpcConfigs,
|
||||
authTokens: JSON.parse(JSON.stringify(securitySettings.authTokens))
|
||||
};
|
||||
|
||||
@@ -362,6 +195,8 @@ const saveSecuritySettings = async () => {
|
||||
|
||||
if (rpcResponse.data.success && authResponse.data.success) {
|
||||
alert('Все настройки безопасности успешно сохранены.');
|
||||
eventBus.emit('settings-updated');
|
||||
eventBus.emit('auth-settings-saved', { tokens: settingsData.authTokens });
|
||||
} else {
|
||||
alert('Произошла ошибка при сохранении настроек. Проверьте консоль разработчика.');
|
||||
}
|
||||
@@ -373,7 +208,31 @@ const saveSecuritySettings = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
// Функция-обработчик для тестирования RPC соединения
|
||||
// Загрузка настроек при монтировании компонента
|
||||
onMounted(() => {
|
||||
loadSettings();
|
||||
});
|
||||
|
||||
// --- Методы для RPC ---
|
||||
const addRpcConfig = () => {
|
||||
const result = validateAndPrepareNetworkConfig();
|
||||
if (!result.valid) {
|
||||
alert(result.error);
|
||||
return;
|
||||
}
|
||||
const { networkId, rpcUrl, chainId } = result.networkConfig;
|
||||
if (securitySettings.rpcConfigs.some(rpc => rpc.networkId === networkId)) {
|
||||
alert(`Ошибка: RPC конфигурация для сети с ID '${networkId}' уже существует.`);
|
||||
return;
|
||||
}
|
||||
securitySettings.rpcConfigs.push({ networkId, rpcUrl, chainId });
|
||||
resetNetworkEntry();
|
||||
};
|
||||
|
||||
const removeRpcConfig = (index) => {
|
||||
securitySettings.rpcConfigs.splice(index, 1);
|
||||
};
|
||||
|
||||
const testRpcHandler = async (rpc) => {
|
||||
try {
|
||||
const result = await testRpcConnection(rpc.networkId, rpc.rpcUrl);
|
||||
@@ -388,10 +247,47 @@ const testRpcHandler = async (rpc) => {
|
||||
}
|
||||
};
|
||||
|
||||
// Загрузка настроек при монтировании компонента
|
||||
onMounted(() => {
|
||||
loadSettings();
|
||||
});
|
||||
// --- Методы для токенов аутентификации ---
|
||||
const addAuthToken = () => {
|
||||
const name = newAuthToken.name.trim();
|
||||
const address = newAuthToken.address.trim();
|
||||
const minBalance = newAuthToken.minBalance.trim();
|
||||
const network = newAuthToken.network;
|
||||
if (name && address) {
|
||||
if (securitySettings.authTokens.some(token => token.address.toLowerCase() === address.toLowerCase())) {
|
||||
alert(`Ошибка: Токен с адресом '${address}' уже добавлен.`);
|
||||
return;
|
||||
}
|
||||
securitySettings.authTokens.push({ name, address, minBalance, network });
|
||||
newAuthToken.name = '';
|
||||
newAuthToken.address = '';
|
||||
newAuthToken.minBalance = '1.0';
|
||||
newAuthToken.network = 'eth';
|
||||
} else {
|
||||
alert('Пожалуйста, заполните название и адрес токена.');
|
||||
}
|
||||
};
|
||||
|
||||
const removeAuthToken = (index) => {
|
||||
securitySettings.authTokens.splice(index, 1);
|
||||
};
|
||||
|
||||
// provide для дочерних компонентов
|
||||
provide('rpcConfigs', securitySettings.rpcConfigs);
|
||||
provide('removeRpcConfig', removeRpcConfig);
|
||||
provide('addRpcConfig', addRpcConfig);
|
||||
provide('testRpcHandler', testRpcHandler);
|
||||
provide('testingRpc', testingRpc);
|
||||
provide('testingRpcId', testingRpcId);
|
||||
provide('networkGroups', networkGroups);
|
||||
provide('networkEntry', networkEntry);
|
||||
provide('validateAndPrepareNetworkConfig', validateAndPrepareNetworkConfig);
|
||||
provide('resetNetworkEntry', resetNetworkEntry);
|
||||
provide('authTokens', securitySettings.authTokens);
|
||||
provide('removeAuthToken', removeAuthToken);
|
||||
provide('addAuthToken', addAuthToken);
|
||||
provide('newAuthToken', newAuthToken);
|
||||
provide('networks', networks);
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -609,14 +505,6 @@ small {
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
.save-panel {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: var(--spacing-lg, 20px);
|
||||
padding-top: var(--spacing-md, 15px);
|
||||
border-top: 1px solid var(--color-grey-light, #eee);
|
||||
}
|
||||
|
||||
.loading-overlay {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
|
||||
Reference in New Issue
Block a user