ваше сообщение коммита
This commit is contained in:
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
184
frontend/src/components/WebSshForm.vue
Normal file
184
frontend/src/components/WebSshForm.vue
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
<template>
|
||||||
|
<div>
|
||||||
|
<!-- Статус подключения -->
|
||||||
|
<div class="connection-status">
|
||||||
|
<div class="status-indicator" :class="{ 'active': isConnected, 'inactive': !isConnected }"></div>
|
||||||
|
<span class="status-text">{{ connectionStatus }}</span>
|
||||||
|
<button v-if="isConnected" @click="disconnectTunnel" class="disconnect-btn">Отключить</button>
|
||||||
|
</div>
|
||||||
|
<!-- Форма настроек -->
|
||||||
|
<form @submit.prevent="handleSubmit" class="tunnel-form">
|
||||||
|
<div class="form-section">
|
||||||
|
<h3>Настройки домена</h3>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="domain">Домен *</label>
|
||||||
|
<input id="domain" v-model="form.domain" type="text" placeholder="example.com" required :disabled="isConnected" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="email">Email для SSL *</label>
|
||||||
|
<input id="email" v-model="form.email" type="email" placeholder="admin@example.com" required :disabled="isConnected" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-section">
|
||||||
|
<h3>Настройки SSH сервера</h3>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="sshHost">SSH Host/IP *</label>
|
||||||
|
<input id="sshHost" v-model="form.sshHost" type="text" placeholder="192.168.1.100 или server.example.com" required :disabled="isConnected" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="sshUser">SSH Пользователь *</label>
|
||||||
|
<input id="sshUser" v-model="form.sshUser" type="text" placeholder="root" required :disabled="isConnected" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="sshKey">SSH Приватный ключ *</label>
|
||||||
|
<textarea id="sshKey" v-model="form.sshKey" placeholder="-----BEGIN OPENSSH PRIVATE KEY-----\n...\n-----END OPENSSH PRIVATE KEY-----" rows="6" required :disabled="isConnected"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-section advanced-section">
|
||||||
|
<h3>Дополнительные настройки</h3>
|
||||||
|
<div class="form-row">
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="localPort">Локальный порт</label>
|
||||||
|
<input id="localPort" v-model="form.localPort" type="number" min="1" max="65535" :disabled="isConnected" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="serverPort">Порт сервера</label>
|
||||||
|
<input id="serverPort" v-model="form.serverPort" type="number" min="1" max="65535" :disabled="isConnected" />
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label for="sshPort">SSH порт</label>
|
||||||
|
<input id="sshPort" v-model="form.sshPort" type="number" min="1" max="65535" :disabled="isConnected" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-actions">
|
||||||
|
<button type="submit" :disabled="isLoading || isConnected" class="publish-btn">
|
||||||
|
{{ isLoading ? 'Настройка...' : 'Опубликовать' }}
|
||||||
|
</button>
|
||||||
|
<button type="button" @click="resetForm" :disabled="isLoading || isConnected" class="reset-btn">Сбросить</button>
|
||||||
|
</div>
|
||||||
|
</form>
|
||||||
|
<!-- Лог операций -->
|
||||||
|
<div class="operation-log" v-if="logs.length > 0">
|
||||||
|
<h3>Лог операций</h3>
|
||||||
|
<div class="log-container">
|
||||||
|
<div v-for="(log, index) in logs" :key="index" class="log-entry" :class="log.type">
|
||||||
|
<span class="log-time">{{ formatTime(log.timestamp) }}</span>
|
||||||
|
<span class="log-message">{{ log.message }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, reactive } from 'vue';
|
||||||
|
import { useWebSshService } from '../services/webSshService';
|
||||||
|
const webSshService = useWebSshService();
|
||||||
|
const isLoading = ref(false);
|
||||||
|
const isConnected = ref(false);
|
||||||
|
const connectionStatus = ref('Не подключено');
|
||||||
|
const logs = ref([]);
|
||||||
|
const form = reactive({
|
||||||
|
domain: '',
|
||||||
|
email: '',
|
||||||
|
sshHost: '',
|
||||||
|
sshUser: '',
|
||||||
|
sshKey: '',
|
||||||
|
localPort: 5173,
|
||||||
|
serverPort: 9000,
|
||||||
|
sshPort: 22
|
||||||
|
});
|
||||||
|
function validatePrivateKey(key) {
|
||||||
|
if (!key) return false;
|
||||||
|
const trimmed = key.trim();
|
||||||
|
if (!trimmed.startsWith('-----BEGIN OPENSSH PRIVATE KEY-----')) return false;
|
||||||
|
if (!trimmed.endsWith('-----END OPENSSH PRIVATE KEY-----')) return false;
|
||||||
|
if (trimmed.split('\n').length < 3) return false;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const handleSubmit = async () => {
|
||||||
|
if (!validateForm()) return;
|
||||||
|
if (!validatePrivateKey(form.sshKey)) {
|
||||||
|
addLog('error', 'Проверьте формат приватного ключа!');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
isLoading.value = true;
|
||||||
|
addLog('info', 'Запуск публикации...');
|
||||||
|
try {
|
||||||
|
const result = await webSshService.createTunnel(form);
|
||||||
|
if (result.success) {
|
||||||
|
isConnected.value = true;
|
||||||
|
connectionStatus.value = `Подключено к ${form.domain}`;
|
||||||
|
addLog('success', 'SSH туннель успешно создан и настроен');
|
||||||
|
addLog('info', `Ваше приложение доступно по адресу: https://${form.domain}`);
|
||||||
|
} else {
|
||||||
|
addLog('error', result.message || 'Ошибка при создании туннеля');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
addLog('error', `Ошибка: ${error.message}`);
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const disconnectTunnel = async () => {
|
||||||
|
isLoading.value = true;
|
||||||
|
addLog('info', 'Отключаю SSH туннель...');
|
||||||
|
try {
|
||||||
|
const result = await webSshService.disconnectTunnel();
|
||||||
|
if (result.success) {
|
||||||
|
isConnected.value = false;
|
||||||
|
connectionStatus.value = 'Не подключено';
|
||||||
|
addLog('success', 'SSH туннель отключен');
|
||||||
|
} else {
|
||||||
|
addLog('error', result.message || 'Ошибка при отключении туннеля');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
addLog('error', `Ошибка: ${error.message}`);
|
||||||
|
} finally {
|
||||||
|
isLoading.value = false;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const validateForm = () => {
|
||||||
|
if (!form.domain || !form.email || !form.sshHost || !form.sshUser || !form.sshKey) {
|
||||||
|
addLog('error', 'Заполните все обязательные поля');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!form.email.includes('@')) {
|
||||||
|
addLog('error', 'Введите корректный email');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (!form.sshKey.includes('-----BEGIN') || !form.sshKey.includes('-----END')) {
|
||||||
|
addLog('error', 'SSH ключ должен быть в формате OpenSSH');
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
};
|
||||||
|
const resetForm = () => {
|
||||||
|
Object.assign(form, {
|
||||||
|
domain: '',
|
||||||
|
email: '',
|
||||||
|
sshHost: '',
|
||||||
|
sshUser: '',
|
||||||
|
sshKey: '',
|
||||||
|
localPort: 5173,
|
||||||
|
serverPort: 9000,
|
||||||
|
sshPort: 22
|
||||||
|
});
|
||||||
|
logs.value = [];
|
||||||
|
};
|
||||||
|
const addLog = (type, message) => {
|
||||||
|
logs.value.push({
|
||||||
|
type,
|
||||||
|
message,
|
||||||
|
timestamp: new Date()
|
||||||
|
});
|
||||||
|
};
|
||||||
|
const formatTime = (timestamp) => {
|
||||||
|
return timestamp.toLocaleTimeString();
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
/* Ваши стили для формы */
|
||||||
|
</style>
|
||||||
@@ -52,6 +52,13 @@ const routes = [
|
|||||||
path: 'interface',
|
path: 'interface',
|
||||||
name: 'settings-interface',
|
name: 'settings-interface',
|
||||||
component: SettingsInterfaceView,
|
component: SettingsInterfaceView,
|
||||||
|
// children: [
|
||||||
|
// {
|
||||||
|
// path: 'webssh',
|
||||||
|
// name: 'settings-interface-webssh',
|
||||||
|
// component: () => import('../views/settings/Interface/InterfaceWebSshView.vue'),
|
||||||
|
// }
|
||||||
|
// ]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'webssh',
|
path: 'webssh',
|
||||||
@@ -91,6 +98,12 @@ const routes = [
|
|||||||
name: 'ai-assistant-settings',
|
name: 'ai-assistant-settings',
|
||||||
component: () => import('@/views/settings/AI/AiAssistantSettings.vue'),
|
component: () => import('@/views/settings/AI/AiAssistantSettings.vue'),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: '/settings/interface/webssh',
|
||||||
|
name: 'webssh-settings',
|
||||||
|
component: () => import('@/views/settings/Interface/InterfaceWebSshView.vue'),
|
||||||
|
meta: { requiresAuth: true }
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/tables',
|
path: '/tables',
|
||||||
name: 'tables-list',
|
name: 'tables-list',
|
||||||
|
|||||||
@@ -48,12 +48,39 @@
|
|||||||
Подробнее
|
Подробнее
|
||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- WEB SSH -->
|
||||||
|
<div class="web3-service-block">
|
||||||
|
<div class="service-header">
|
||||||
|
<h3>WEB SSH</h3>
|
||||||
|
<span class="service-badge webssh">Публикация через SSH-туннель</span>
|
||||||
|
</div>
|
||||||
|
<p>Автоматическая публикация приложения в интернете через SSH-туннель.</p>
|
||||||
|
<div class="service-features">
|
||||||
|
<span class="feature">✓ Быстрое подключение</span>
|
||||||
|
<span class="feature">✓ Безопасно</span>
|
||||||
|
<span class="feature">✓ Для локальных и VPS</span>
|
||||||
|
</div>
|
||||||
|
<button class="btn-primary" @click="goToWebSsh">Подробнее</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Модальное окно с формой WEB SSH -->
|
||||||
|
<NoAccessModal v-if="showWebSsh" @close="showWebSsh = false">
|
||||||
|
<div style="padding:2rem;max-width:600px">
|
||||||
|
<h3>WEB SSH Туннель (форма)</h3>
|
||||||
|
<!-- Здесь будет компонент WebSshForm.vue -->
|
||||||
|
<div style="color:#888">Здесь появится форма WEB SSH (будет вынесена из WebSshSettingsView.vue)</div>
|
||||||
|
<button class="btn-primary" @click="showWebSsh = false" style="margin-top:1.5rem">Закрыть</button>
|
||||||
|
</div>
|
||||||
|
</NoAccessModal>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup>
|
<script setup>
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { useAuthContext } from '@/composables/useAuth';
|
import { useAuthContext } from '@/composables/useAuth';
|
||||||
|
import NoAccessModal from '@/components/NoAccessModal.vue';
|
||||||
|
import { ref } from 'vue';
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { isAdmin } = useAuthContext();
|
const { isAdmin } = useAuthContext();
|
||||||
const goBack = () => router.push('/settings');
|
const goBack = () => router.push('/settings');
|
||||||
@@ -67,6 +94,10 @@ const goToAkashDetails = () => {
|
|||||||
const goToFluxDetails = () => {
|
const goToFluxDetails = () => {
|
||||||
window.open('https://runonflux.io/', '_blank');
|
window.open('https://runonflux.io/', '_blank');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const goToWebSsh = () => router.push('/settings/interface/webssh');
|
||||||
|
|
||||||
|
const showWebSsh = ref(false);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
@@ -137,6 +168,10 @@ h2:first-of-type {
|
|||||||
background: linear-gradient(135deg, #4ecdc4, #44a08d);
|
background: linear-gradient(135deg, #4ecdc4, #44a08d);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.service-badge.webssh {
|
||||||
|
background: linear-gradient(135deg, #6c757d, #343a40);
|
||||||
|
}
|
||||||
|
|
||||||
.service-features {
|
.service-features {
|
||||||
display: flex;
|
display: flex;
|
||||||
flex-wrap: wrap;
|
flex-wrap: wrap;
|
||||||
|
|||||||
@@ -0,0 +1,87 @@
|
|||||||
|
<template>
|
||||||
|
<Header :is-sidebar-open="showSidebar" @toggle-sidebar="toggleSidebar" />
|
||||||
|
<Sidebar
|
||||||
|
v-model="showSidebar"
|
||||||
|
:is-authenticated="isAuthenticated"
|
||||||
|
:identities="identities"
|
||||||
|
:token-balances="tokenBalances"
|
||||||
|
:is-loading-tokens="isLoadingTokens"
|
||||||
|
:telegram-auth="telegramAuth"
|
||||||
|
:email-auth="emailAuth"
|
||||||
|
/>
|
||||||
|
<div class="webssh-settings-block">
|
||||||
|
<button class="close-btn" @click="goBack">×</button>
|
||||||
|
<h2>WEB SSH: интеграция и настройки</h2>
|
||||||
|
<p class="desc">Автоматическая публикация приложения через SSH-туннель и NGINX.</p>
|
||||||
|
<WebSshForm />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref } from 'vue';
|
||||||
|
import { useRouter } from 'vue-router';
|
||||||
|
import WebSshForm from '@/components/WebSshForm.vue';
|
||||||
|
import Header from '@/components/Header.vue';
|
||||||
|
import Sidebar from '@/components/Sidebar.vue';
|
||||||
|
import { useAuthContext } from '@/composables/useAuth';
|
||||||
|
|
||||||
|
const router = useRouter();
|
||||||
|
const goBack = () => router.push('/settings/interface');
|
||||||
|
const showSidebar = ref(false);
|
||||||
|
const toggleSidebar = () => {
|
||||||
|
showSidebar.value = !showSidebar.value;
|
||||||
|
};
|
||||||
|
|
||||||
|
const auth = useAuthContext();
|
||||||
|
const isAuthenticated = auth.isAuthenticated.value;
|
||||||
|
const identities = auth.identities?.value || [];
|
||||||
|
const tokenBalances = auth.tokenBalances?.value || [];
|
||||||
|
const isLoadingTokens = false;
|
||||||
|
|
||||||
|
// Дефолтные объекты для Sidebar
|
||||||
|
const telegramAuth = {
|
||||||
|
showVerification: false,
|
||||||
|
botLink: '',
|
||||||
|
verificationCode: '',
|
||||||
|
error: ''
|
||||||
|
};
|
||||||
|
const emailAuth = {
|
||||||
|
showForm: false,
|
||||||
|
showVerification: false
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
.webssh-settings-block {
|
||||||
|
background: #fff;
|
||||||
|
border-radius: var(--radius-lg);
|
||||||
|
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.1);
|
||||||
|
padding: 20px;
|
||||||
|
margin-top: 20px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
width: 100%;
|
||||||
|
position: relative;
|
||||||
|
overflow-x: auto;
|
||||||
|
}
|
||||||
|
.close-btn {
|
||||||
|
position: absolute;
|
||||||
|
top: 18px;
|
||||||
|
right: 18px;
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
font-size: 2rem;
|
||||||
|
cursor: pointer;
|
||||||
|
color: #bbb;
|
||||||
|
transition: color 0.2s;
|
||||||
|
}
|
||||||
|
.close-btn:hover {
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
h2 {
|
||||||
|
margin-bottom: 0.5rem;
|
||||||
|
}
|
||||||
|
.desc {
|
||||||
|
color: #666;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -20,11 +20,6 @@
|
|||||||
<p>Настройки внешнего вида, локализации и пользовательского опыта.</p>
|
<p>Настройки внешнего вида, локализации и пользовательского опыта.</p>
|
||||||
<button class="details-btn" @click="$router.push('/settings/interface')">Подробнее</button>
|
<button class="details-btn" @click="$router.push('/settings/interface')">Подробнее</button>
|
||||||
</div>
|
</div>
|
||||||
<div class="main-block">
|
|
||||||
<h3>WEB SSH</h3>
|
|
||||||
<p>Автоматическая публикация приложения в интернете через SSH-туннель.</p>
|
|
||||||
<button class="details-btn" @click="$router.push('/settings/webssh')">Подробнее</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
@@ -44,157 +44,6 @@ sudo bash webssh-agent/install.sh</code></pre>
|
|||||||
<button @click="checkAgent" class="check-btn">Проверить</button>
|
<button @click="checkAgent" class="check-btn">Проверить</button>
|
||||||
<div v-if="copied" class="copied-indicator">Скопировано!</div>
|
<div v-if="copied" class="copied-indicator">Скопировано!</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Форма публикации всегда доступна -->
|
|
||||||
<div>
|
|
||||||
<!-- Статус подключения -->
|
|
||||||
<div class="connection-status">
|
|
||||||
<div class="status-indicator" :class="{ 'active': isConnected, 'inactive': !isConnected }"></div>
|
|
||||||
<span class="status-text">{{ connectionStatus }}</span>
|
|
||||||
<button v-if="isConnected" @click="disconnectTunnel" class="disconnect-btn">Отключить</button>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<!-- Форма настроек -->
|
|
||||||
<form @submit.prevent="handleSubmit" class="tunnel-form">
|
|
||||||
<div class="form-section">
|
|
||||||
<h3>Настройки домена</h3>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="domain">Домен *</label>
|
|
||||||
<input
|
|
||||||
id="domain"
|
|
||||||
v-model="form.domain"
|
|
||||||
type="text"
|
|
||||||
placeholder="example.com"
|
|
||||||
required
|
|
||||||
:disabled="isConnected"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="email">Email для SSL *</label>
|
|
||||||
<input
|
|
||||||
id="email"
|
|
||||||
v-model="form.email"
|
|
||||||
type="email"
|
|
||||||
placeholder="admin@example.com"
|
|
||||||
required
|
|
||||||
:disabled="isConnected"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-section">
|
|
||||||
<h3>Настройки SSH сервера</h3>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="sshHost">SSH Host/IP *</label>
|
|
||||||
<input
|
|
||||||
id="sshHost"
|
|
||||||
v-model="form.sshHost"
|
|
||||||
type="text"
|
|
||||||
placeholder="192.168.1.100 или server.example.com"
|
|
||||||
required
|
|
||||||
:disabled="isConnected"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="sshUser">SSH Пользователь *</label>
|
|
||||||
<input
|
|
||||||
id="sshUser"
|
|
||||||
v-model="form.sshUser"
|
|
||||||
type="text"
|
|
||||||
placeholder="root"
|
|
||||||
required
|
|
||||||
:disabled="isConnected"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="sshKey">SSH Приватный ключ *</label>
|
|
||||||
<textarea
|
|
||||||
id="sshKey"
|
|
||||||
v-model="form.sshKey"
|
|
||||||
placeholder="-----BEGIN OPENSSH PRIVATE KEY-----
|
|
||||||
...
|
|
||||||
-----END OPENSSH PRIVATE KEY-----"
|
|
||||||
rows="6"
|
|
||||||
required
|
|
||||||
:disabled="isConnected"
|
|
||||||
></textarea>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-section advanced-section">
|
|
||||||
<h3>Дополнительные настройки</h3>
|
|
||||||
<div class="form-row">
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="localPort">Локальный порт</label>
|
|
||||||
<input
|
|
||||||
id="localPort"
|
|
||||||
v-model="form.localPort"
|
|
||||||
type="number"
|
|
||||||
min="1"
|
|
||||||
max="65535"
|
|
||||||
:disabled="isConnected"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="serverPort">Порт сервера</label>
|
|
||||||
<input
|
|
||||||
id="serverPort"
|
|
||||||
v-model="form.serverPort"
|
|
||||||
type="number"
|
|
||||||
min="1"
|
|
||||||
max="65535"
|
|
||||||
:disabled="isConnected"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
<div class="form-group">
|
|
||||||
<label for="sshPort">SSH порт</label>
|
|
||||||
<input
|
|
||||||
id="sshPort"
|
|
||||||
v-model="form.sshPort"
|
|
||||||
type="number"
|
|
||||||
min="1"
|
|
||||||
max="65535"
|
|
||||||
:disabled="isConnected"
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="form-actions">
|
|
||||||
<button
|
|
||||||
type="submit"
|
|
||||||
:disabled="isLoading || isConnected"
|
|
||||||
class="publish-btn"
|
|
||||||
>
|
|
||||||
{{ isLoading ? 'Настройка...' : 'Опубликовать' }}
|
|
||||||
</button>
|
|
||||||
<button
|
|
||||||
type="button"
|
|
||||||
@click="resetForm"
|
|
||||||
:disabled="isLoading || isConnected"
|
|
||||||
class="reset-btn"
|
|
||||||
>
|
|
||||||
Сбросить
|
|
||||||
</button>
|
|
||||||
</div>
|
|
||||||
</form>
|
|
||||||
|
|
||||||
<!-- Лог операций -->
|
|
||||||
<div class="operation-log" v-if="logs.length > 0">
|
|
||||||
<h3>Лог операций</h3>
|
|
||||||
<div class="log-container">
|
|
||||||
<div
|
|
||||||
v-for="(log, index) in logs"
|
|
||||||
:key="index"
|
|
||||||
class="log-entry"
|
|
||||||
:class="log.type"
|
|
||||||
>
|
|
||||||
<span class="log-time">{{ formatTime(log.timestamp) }}</span>
|
|
||||||
<span class="log-message">{{ log.message }}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user