3x-ui/web/html/modals/node_modal.html

229 lines
9.1 KiB
HTML
Raw Normal View History

2026-01-05 21:12:53 +00:00
{{define "modals/nodeModal"}}
2026-01-10 19:51:10 +00:00
<a-modal id="node-modal" v-model="nodeModal.visible" :title="nodeModal.title"
@ok="nodeModal.ok" @cancel="nodeModal.cancel" :ok-text="nodeModal.okText" :width="600"
:confirm-loading="nodeModal.registering" :ok-button-props="{ disabled: nodeModal.registering }">
<div v-if="!nodeModal.registering && !nodeModal.showProgress">
<a-form layout="vertical">
<a-form-item label='{{ i18n "pages.nodes.nodeName" }}'>
<a-input v-model.trim="nodeModal.formData.name" placeholder="e.g., Node-1"></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.nodes.nodeAddress" }}'>
<a-input v-model.trim="nodeModal.formData.address" placeholder='{{ i18n "pages.nodes.fullUrlHint" }}'></a-input>
</a-form-item>
<a-form-item label='{{ i18n "pages.nodes.nodePort" }}'>
<a-input-number v-model.number="nodeModal.formData.port" :min="1" :max="65535" :style="{ width: '100%' }"></a-input-number>
</a-form-item>
<!-- API key is now auto-generated during registration, no need for user input -->
</a-form>
</div>
<!-- Progress animation during registration -->
<div v-if="nodeModal.showProgress" style="padding: 20px 0; text-align: center;">
<a-steps :current="nodeModal.currentStep" direction="vertical" size="small">
<a-step title='{{ i18n "pages.nodes.connecting" }}' :status="nodeModal.steps.connecting">
<template slot="description">
<a-spin v-if="nodeModal.steps.connecting === 'process'" size="small" style="margin-right: 8px;"></a-spin>
<span v-if="nodeModal.steps.connecting === 'finish'">✓ {{ i18n "pages.nodes.connectionEstablished" }}</span>
<span v-if="nodeModal.steps.connecting === 'error'">✗ {{ i18n "pages.nodes.connectionError" }}</span>
</template>
</a-step>
<a-step title='{{ i18n "pages.nodes.generatingApiKey" }}' :status="nodeModal.steps.generating">
<template slot="description">
<a-spin v-if="nodeModal.steps.generating === 'process'" size="small" style="margin-right: 8px;"></a-spin>
<span v-if="nodeModal.steps.generating === 'finish'">✓ {{ i18n "pages.nodes.apiKeyGenerated" }}</span>
<span v-if="nodeModal.steps.generating === 'error'">✗ {{ i18n "pages.nodes.generationError" }}</span>
</template>
</a-step>
<a-step title='{{ i18n "pages.nodes.registeringNode" }}' :status="nodeModal.steps.registering">
<template slot="description">
<a-spin v-if="nodeModal.steps.registering === 'process'" size="small" style="margin-right: 8px;"></a-spin>
<span v-if="nodeModal.steps.registering === 'finish'">✓ {{ i18n "pages.nodes.nodeRegistered" }}</span>
<span v-if="nodeModal.steps.registering === 'error'">✗ {{ i18n "pages.nodes.registrationError" }}</span>
</template>
</a-step>
<a-step title='{{ i18n "pages.nodes.done" }}' :status="nodeModal.steps.completed">
<template slot="description">
<span v-if="nodeModal.steps.completed === 'finish'" style="color: #52c41a; font-weight: bold;">✓ {{ i18n "pages.nodes.nodeAddedSuccessfully" }}</span>
</template>
</a-step>
</a-steps>
</div>
2026-01-05 21:12:53 +00:00
</a-modal>
<script>
const nodeModal = window.nodeModal = {
visible: false,
title: '',
2026-01-10 19:51:10 +00:00
okText: 'OK',
registering: false,
showProgress: false,
currentStep: 0,
steps: {
connecting: 'wait',
generating: 'wait',
registering: 'wait',
completed: 'wait'
},
2026-01-05 21:12:53 +00:00
formData: {
name: '',
address: '',
port: 8080
// apiKey is now auto-generated during registration
2026-01-05 21:12:53 +00:00
},
ok() {
2026-01-10 19:51:10 +00:00
// Валидация полей - используем nodeModal напрямую для правильного контекста
if (!nodeModal.formData.name || !nodeModal.formData.name.trim()) {
if (typeof app !== 'undefined' && app.$message) {
app.$message.error('{{ i18n "pages.nodes.enterNodeName" }}');
} else if (typeof Vue !== 'undefined' && Vue.prototype && Vue.prototype.$message) {
Vue.prototype.$message.error('{{ i18n "pages.nodes.enterNodeName" }}');
}
2026-01-05 21:12:53 +00:00
return;
}
2026-01-10 19:51:10 +00:00
if (!nodeModal.formData.address || !nodeModal.formData.address.trim()) {
if (typeof app !== 'undefined' && app.$message) {
app.$message.error('{{ i18n "pages.nodes.enterNodeAddress" }}');
} else if (typeof Vue !== 'undefined' && Vue.prototype && Vue.prototype.$message) {
Vue.prototype.$message.error('{{ i18n "pages.nodes.enterNodeAddress" }}');
}
2026-01-05 21:12:53 +00:00
return;
}
2026-01-10 19:51:10 +00:00
// API key is now auto-generated during registration, no validation needed
2026-01-05 21:12:53 +00:00
2026-01-10 19:51:10 +00:00
// Если все поля заполнены, формируем полный адрес с портом
const dataToSend = { ...nodeModal.formData };
// Всегда добавляем порт к адресу
let fullAddress = dataToSend.address.trim();
const port = dataToSend.port && dataToSend.port > 0 ? dataToSend.port : 8080;
// Правильно добавляем порт к URL
// Парсим URL: http://192.168.0.7 -> http://192.168.0.7:8080
const urlMatch = fullAddress.match(/^(https?:\/\/)([^\/:]+)(\/.*)?$/);
if (urlMatch) {
const protocol = urlMatch[1]; // http:// или https://
const host = urlMatch[2]; // 192.168.0.7
const path = urlMatch[3] || ''; // /path или ''
fullAddress = `${protocol}${host}:${port}${path}`;
2026-01-05 21:12:53 +00:00
} else {
2026-01-10 19:51:10 +00:00
// Если не удалось распарсить, просто добавляем порт
fullAddress = `${fullAddress}:${port}`;
2026-01-05 21:12:53 +00:00
}
2026-01-10 19:51:10 +00:00
// Удаляем порт из данных, так как он теперь в адресе
delete dataToSend.port;
dataToSend.address = fullAddress;
// Если это режим редактирования, просто вызываем confirm
if (nodeModal.isEdit) {
if (nodeModal.confirm) {
nodeModal.confirm(dataToSend);
}
nodeModal.visible = false;
return;
}
// Для добавления новой ноды показываем прогресс регистрации
nodeModal.registering = true;
nodeModal.showProgress = true;
nodeModal.currentStep = 0;
// Сброс всех шагов
nodeModal.steps = {
connecting: 'wait',
generating: 'wait',
registering: 'wait',
completed: 'wait'
};
// Вызываем confirm с объединенным адресом (это запустит регистрацию)
2026-01-10 19:51:10 +00:00
if (nodeModal.confirm) {
nodeModal.confirm(dataToSend);
}
},
cancel() {
this.visible = false;
this.resetProgress();
2026-01-10 19:51:10 +00:00
},
show({ title = '', okText = 'OK', node = null, confirm = (data) => { }, isEdit = false }) {
2026-01-05 21:12:53 +00:00
this.title = title;
this.okText = okText;
this.confirm = confirm;
this.isEdit = isEdit;
this.registering = false;
this.showProgress = false;
this.currentStep = 0;
this.steps = {
connecting: 'wait',
generating: 'wait',
registering: 'wait',
completed: 'wait'
};
2026-01-05 21:12:53 +00:00
if (node) {
2026-01-10 19:51:10 +00:00
// Извлекаем адрес и порт из полного URL
let address = node.address || '';
let port = 8080;
// Всегда извлекаем порт из адреса, если он там есть
if (address) {
const urlMatch = address.match(/^(https?:\/\/[^\/:]+)(:(\d+))?(\/.*)?$/);
if (urlMatch) {
// Убираем порт из адреса для отображения
const protocol = urlMatch[1].match(/^(https?:\/\/)/)[1];
const host = urlMatch[1].replace(/^https?:\/\//, '');
const path = urlMatch[4] || '';
address = `${protocol}${host}${path}`;
// Если порт был в адресе, извлекаем его
if (urlMatch[3]) {
port = parseInt(urlMatch[3], 10);
}
}
}
2026-01-05 21:12:53 +00:00
this.formData = {
name: node.name || '',
2026-01-10 19:51:10 +00:00
address: address,
port: port
// apiKey is not shown in edit mode (it's managed by the system)
2026-01-05 21:12:53 +00:00
};
} else {
this.formData = {
name: '',
address: '',
port: 8080
// apiKey is auto-generated during registration
2026-01-05 21:12:53 +00:00
};
}
this.visible = true;
},
close() {
2026-01-10 19:51:10 +00:00
this.visible = false;
this.resetProgress();
},
resetProgress() {
this.registering = false;
this.showProgress = false;
this.currentStep = 0;
this.steps = {
connecting: 'wait',
generating: 'wait',
registering: 'wait',
completed: 'wait'
};
2026-01-05 21:12:53 +00:00
}
};
2026-01-10 19:51:10 +00:00
const nodeModalVueInstance = new Vue({
delimiters: ['[[', ']]'],
el: '#node-modal',
data: {
nodeModal: nodeModal
2026-01-05 21:12:53 +00:00
}
2026-01-10 19:51:10 +00:00
});
2026-01-05 21:12:53 +00:00
</script>
{{end}}