add installation and some other fields
Some checks are pending
PHP Composer / build (push) Waiting to run
Some checks are pending
PHP Composer / build (push) Waiting to run
This commit is contained in:
parent
89dd4f1677
commit
e4803a25d7
417
assets/controllers/installation_controller.js
Normal file
417
assets/controllers/installation_controller.js
Normal file
|
@ -0,0 +1,417 @@
|
||||||
|
import { Controller } from '@hotwired/stimulus';
|
||||||
|
|
||||||
|
export default class extends Controller {
|
||||||
|
static targets = [
|
||||||
|
'step', 'progressFill', 'status', 'loading', 'buttonText',
|
||||||
|
'panelType', 'host', 'username', 'password', 'domain',
|
||||||
|
'databaseName', 'databaseUser', 'databasePassword', 'adminUrl'
|
||||||
|
];
|
||||||
|
|
||||||
|
static values = {
|
||||||
|
currentStep: { type: Number, default: 1 },
|
||||||
|
installationData: { type: Object, default: {} }
|
||||||
|
};
|
||||||
|
|
||||||
|
connect() {
|
||||||
|
console.log('Installation controller connected');
|
||||||
|
this.updateProgress();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateProgress() {
|
||||||
|
const progress = (this.currentStepValue / 6) * 100;
|
||||||
|
if (this.hasProgressFillTarget) {
|
||||||
|
this.progressFillTarget.style.width = progress + '%';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showStep(stepNumber) {
|
||||||
|
// مخفی کردن همه مراحل
|
||||||
|
this.stepTargets.forEach(step => {
|
||||||
|
step.classList.add('step-hidden');
|
||||||
|
});
|
||||||
|
|
||||||
|
// نمایش مرحله فعلی
|
||||||
|
if (this.stepTargets[stepNumber - 1]) {
|
||||||
|
this.stepTargets[stepNumber - 1].classList.remove('step-hidden');
|
||||||
|
}
|
||||||
|
this.currentStepValue = stepNumber;
|
||||||
|
this.updateProgress();
|
||||||
|
}
|
||||||
|
|
||||||
|
showStatus(stepIndex, message, type = 'info') {
|
||||||
|
if (this.statusTargets[stepIndex]) {
|
||||||
|
const statusElement = this.statusTargets[stepIndex];
|
||||||
|
statusElement.textContent = message;
|
||||||
|
statusElement.className = `status-message status-${type}`;
|
||||||
|
statusElement.style.display = 'block';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
showLoading(buttonIndex, loadingIndex, buttonTextIndex, originalText) {
|
||||||
|
if (this.buttonTextTargets[buttonIndex] && this.loadingTargets[loadingIndex]) {
|
||||||
|
this.buttonTextTargets[buttonIndex].style.display = 'none';
|
||||||
|
this.loadingTargets[loadingIndex].style.display = 'inline-block';
|
||||||
|
this.buttonTextTargets[buttonTextIndex].textContent = 'در حال پردازش...';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
hideLoading(buttonIndex, loadingIndex, buttonTextIndex, originalText) {
|
||||||
|
if (this.buttonTextTargets[buttonIndex] && this.loadingTargets[loadingIndex]) {
|
||||||
|
this.buttonTextTargets[buttonIndex].style.display = 'inline-block';
|
||||||
|
this.loadingTargets[loadingIndex].style.display = 'none';
|
||||||
|
this.buttonTextTargets[buttonTextIndex].textContent = originalText;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async testConnection() {
|
||||||
|
console.log('testConnection called');
|
||||||
|
|
||||||
|
// بررسی وجود targets
|
||||||
|
if (!this.hasPanelTypeTarget || !this.hasHostTarget || !this.hasUsernameTarget || !this.hasPasswordTarget) {
|
||||||
|
console.error('Required targets not found');
|
||||||
|
this.showStatus(0, 'خطا در بارگذاری فرم', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const panelType = this.panelTypeTarget.value;
|
||||||
|
const host = this.hostTarget.value;
|
||||||
|
const username = this.usernameTarget.value;
|
||||||
|
const password = this.passwordTarget.value;
|
||||||
|
|
||||||
|
console.log('Form data:', { panelType, host, username, password: '***' });
|
||||||
|
|
||||||
|
if (!host || !username || !password) {
|
||||||
|
this.showStatus(0, 'لطفاً تمام فیلدها را پر کنید', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.showLoading(0, 0, 0, 'تست اتصال');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const requestData = {
|
||||||
|
panel_type: panelType,
|
||||||
|
host: host,
|
||||||
|
username: username,
|
||||||
|
password: password
|
||||||
|
};
|
||||||
|
|
||||||
|
console.log('Sending request:', requestData);
|
||||||
|
|
||||||
|
const response = await fetch('/api/installation/test-connection', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(requestData)
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('Response status:', response.status);
|
||||||
|
|
||||||
|
let result = null;
|
||||||
|
try {
|
||||||
|
result = await response.json();
|
||||||
|
} catch (e) {
|
||||||
|
result = { success: false, error: 'پاسخ نامعتبر از سرور' };
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
// اگر خطا مربوط به دسترسی یا API یا timeout بود، فرم FTP را نمایش بده
|
||||||
|
const errorText = result.error || '';
|
||||||
|
if (
|
||||||
|
errorText.includes('403') ||
|
||||||
|
errorText.includes('401') ||
|
||||||
|
errorText.includes('API') ||
|
||||||
|
errorText.includes('دسترسی') ||
|
||||||
|
errorText.includes('timeout')
|
||||||
|
) {
|
||||||
|
document.getElementById('ftpOption').classList.remove('step-hidden');
|
||||||
|
}
|
||||||
|
this.showStatus(0, result.error || 'خطای نامشخص', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('Response result:', result);
|
||||||
|
|
||||||
|
this.installationDataValue = {
|
||||||
|
panel_type: panelType,
|
||||||
|
host: host,
|
||||||
|
username: username,
|
||||||
|
password: password
|
||||||
|
};
|
||||||
|
|
||||||
|
this.showStatus(0, result.data.message, 'success');
|
||||||
|
setTimeout(() => this.showStep(2), 1000);
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Error:', error);
|
||||||
|
this.showStatus(0, 'خطا در اتصال به سرور: ' + error.message, 'error');
|
||||||
|
} finally {
|
||||||
|
this.hideLoading(0, 0, 0, 'تست اتصال');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDomainInfo() {
|
||||||
|
this.showLoading(1, 1, 1, 'دریافت دامنهها');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/installation/get-domain-info', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(this.installationDataValue)
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
this.domainTarget.innerHTML = '<option value="">انتخاب کنید...</option>';
|
||||||
|
|
||||||
|
result.data.domains.forEach(domain => {
|
||||||
|
const option = document.createElement('option');
|
||||||
|
option.value = domain;
|
||||||
|
option.textContent = domain;
|
||||||
|
this.domainTarget.appendChild(option);
|
||||||
|
});
|
||||||
|
|
||||||
|
this.showStatus(1, result.data.message, 'success');
|
||||||
|
setTimeout(() => this.showStep(3), 1000);
|
||||||
|
} else {
|
||||||
|
this.showStatus(1, result.error, 'error');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.showStatus(1, 'خطا در دریافت اطلاعات دامنه', 'error');
|
||||||
|
} finally {
|
||||||
|
this.hideLoading(1, 1, 1, 'دریافت دامنهها');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async createDatabase() {
|
||||||
|
const databaseName = this.databaseNameTarget.value;
|
||||||
|
const databaseUser = this.databaseUserTarget.value;
|
||||||
|
const databasePassword = this.databasePasswordTarget.value;
|
||||||
|
|
||||||
|
if (!databaseName || !databaseUser || !databasePassword) {
|
||||||
|
this.showStatus(2, 'لطفاً تمام فیلدها را پر کنید', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.showLoading(2, 2, 2, 'ایجاد دیتابیس');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/installation/create-database', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
...this.installationDataValue,
|
||||||
|
database_name: databaseName,
|
||||||
|
database_user: databaseUser,
|
||||||
|
database_password: databasePassword
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
this.installationDataValue = {
|
||||||
|
...this.installationDataValue,
|
||||||
|
database_name: databaseName,
|
||||||
|
database_user: databaseUser,
|
||||||
|
database_password: databasePassword
|
||||||
|
};
|
||||||
|
|
||||||
|
this.showStatus(2, result.data.message, 'success');
|
||||||
|
setTimeout(() => this.showStep(4), 1000);
|
||||||
|
} else {
|
||||||
|
this.showStatus(2, result.error, 'error');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.showStatus(2, 'خطا در ایجاد دیتابیس', 'error');
|
||||||
|
} finally {
|
||||||
|
this.hideLoading(2, 2, 2, 'ایجاد دیتابیس');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async uploadFiles() {
|
||||||
|
const domain = this.domainTarget.value;
|
||||||
|
|
||||||
|
if (!domain) {
|
||||||
|
this.showStatus(3, 'لطفاً دامنه را انتخاب کنید', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.showLoading(3, 3, 3, 'شروع آپلود');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/installation/upload-files', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
...this.installationDataValue,
|
||||||
|
domain: domain
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
this.installationDataValue.domain = domain;
|
||||||
|
this.showStatus(3, result.data.message, 'success');
|
||||||
|
setTimeout(() => this.showStep(5), 1000);
|
||||||
|
} else {
|
||||||
|
this.showStatus(3, result.error, 'error');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.showStatus(3, 'خطا در آپلود فایلها', 'error');
|
||||||
|
} finally {
|
||||||
|
this.hideLoading(3, 3, 3, 'شروع آپلود');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async finalizeInstallation() {
|
||||||
|
this.showLoading(4, 4, 4, 'نهاییسازی نصب');
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/installation/finalize', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(this.installationDataValue)
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await response.json();
|
||||||
|
|
||||||
|
if (result.success) {
|
||||||
|
this.showStatus(4, result.data.message, 'success');
|
||||||
|
setTimeout(() => {
|
||||||
|
this.showStep(6);
|
||||||
|
this.adminUrlTarget.href = result.data.admin_url;
|
||||||
|
}, 1000);
|
||||||
|
} else {
|
||||||
|
this.showStatus(4, result.error, 'error');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
this.showStatus(4, 'خطا در نهاییسازی نصب', 'error');
|
||||||
|
} finally {
|
||||||
|
this.hideLoading(4, 4, 4, 'نهاییسازی نصب');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onDomainChange() {
|
||||||
|
const domain = this.domainTarget.value;
|
||||||
|
if (domain) {
|
||||||
|
const dbName = 'hesabix_' + domain.replace(/[^a-zA-Z0-9]/g, '_');
|
||||||
|
const dbUser = 'hesabix_' + domain.replace(/[^a-zA-Z0-9]/g, '_');
|
||||||
|
|
||||||
|
this.databaseNameTarget.value = dbName;
|
||||||
|
this.databaseUserTarget.value = dbUser;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.showApiInstall = function() {
|
||||||
|
document.getElementById('apiInstallSection').style.display = '';
|
||||||
|
document.getElementById('ftpOption').style.display = 'none';
|
||||||
|
// ریست مراحل FTP
|
||||||
|
document.getElementById('ftpStep1').classList.remove('step-hidden');
|
||||||
|
document.getElementById('ftpStep2').classList.add('step-hidden');
|
||||||
|
document.getElementById('ftpStep3').classList.add('step-hidden');
|
||||||
|
}
|
||||||
|
window.showFtpInstall = function() {
|
||||||
|
document.getElementById('apiInstallSection').style.display = 'none';
|
||||||
|
const ftpOption = document.getElementById('ftpOption');
|
||||||
|
ftpOption.style.display = '';
|
||||||
|
ftpOption.classList.remove('step-hidden');
|
||||||
|
// ریست مراحل FTP
|
||||||
|
document.getElementById('ftpStep1').classList.remove('step-hidden');
|
||||||
|
document.getElementById('ftpStep2').classList.add('step-hidden');
|
||||||
|
document.getElementById('ftpStep3').classList.add('step-hidden');
|
||||||
|
}
|
||||||
|
function showBootstrapAlert(message, type = 'danger', parentId = 'ftpStep1') {
|
||||||
|
// type: 'danger', 'success', 'info', ...
|
||||||
|
let parent = document.getElementById(parentId);
|
||||||
|
let oldAlert = parent.querySelector('.bootstrap-alert');
|
||||||
|
if (oldAlert) oldAlert.remove();
|
||||||
|
let div = document.createElement('div');
|
||||||
|
div.className = `alert alert-${type} bootstrap-alert`;
|
||||||
|
div.style.marginTop = '10px';
|
||||||
|
div.innerHTML = message;
|
||||||
|
parent.querySelector('.step-content').prepend(div);
|
||||||
|
}
|
||||||
|
|
||||||
|
window.showFtpDbStep = function() {
|
||||||
|
const ftpHost = document.getElementById('ftpHost').value;
|
||||||
|
const ftpUsername = document.getElementById('ftpUsername').value;
|
||||||
|
const ftpPassword = document.getElementById('ftpPassword').value;
|
||||||
|
if (!ftpHost || !ftpUsername || !ftpPassword) {
|
||||||
|
showBootstrapAlert('تمام فیلدهای FTP الزامی است', 'danger', 'ftpStep1');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// حذف هشدار قبلی
|
||||||
|
showBootstrapAlert('', 'danger', 'ftpStep1');
|
||||||
|
document.getElementById('ftpStep1').classList.add('step-hidden');
|
||||||
|
document.getElementById('ftpStep2').classList.remove('step-hidden');
|
||||||
|
}
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
showApiInstall();
|
||||||
|
});
|
||||||
|
|
||||||
|
window.startFtpInstall = async function() {
|
||||||
|
// مرحله دوم: اطلاعات دیتابیس
|
||||||
|
const ftpHost = document.getElementById('ftpHost').value;
|
||||||
|
const ftpUsername = document.getElementById('ftpUsername').value;
|
||||||
|
const ftpPassword = document.getElementById('ftpPassword').value;
|
||||||
|
const ftpPort = document.getElementById('ftpPort').value;
|
||||||
|
const domain = document.querySelector('[data-installation-target="host"]').value;
|
||||||
|
const databaseName = document.getElementById('ftpDatabaseName').value;
|
||||||
|
const databaseUser = document.getElementById('ftpDatabaseUser').value;
|
||||||
|
const databasePassword = document.getElementById('ftpDatabasePassword').value;
|
||||||
|
if (!databaseName || !databaseUser || !databasePassword) {
|
||||||
|
showBootstrapAlert('تمام فیلدهای دیتابیس الزامی است', 'danger', 'ftpStep2');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// حذف هشدار قبلی
|
||||||
|
showBootstrapAlert('', 'danger', 'ftpStep2');
|
||||||
|
const payload = {
|
||||||
|
ftp_host: ftpHost,
|
||||||
|
ftp_username: ftpUsername,
|
||||||
|
ftp_password: ftpPassword,
|
||||||
|
ftp_port: ftpPort,
|
||||||
|
domain: domain,
|
||||||
|
database_name: databaseName,
|
||||||
|
database_user: databaseUser,
|
||||||
|
database_password: databasePassword
|
||||||
|
};
|
||||||
|
try {
|
||||||
|
const response = await fetch('/api/installation/ftp-install', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'Accept': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
});
|
||||||
|
const result = await response.json();
|
||||||
|
document.getElementById('ftpStep2').classList.add('step-hidden');
|
||||||
|
document.getElementById('ftpStep3').classList.remove('step-hidden');
|
||||||
|
const msg = document.getElementById('ftpResultMsg');
|
||||||
|
if (result.success) {
|
||||||
|
msg.className = 'status-message status-success';
|
||||||
|
msg.innerHTML = 'نصب با موفقیت از طریق FTP انجام شد!';
|
||||||
|
} else {
|
||||||
|
msg.className = 'status-message status-error';
|
||||||
|
msg.innerHTML = 'خطا: ' + (result.error || 'خطای نامشخص');
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
document.getElementById('ftpStep2').classList.add('step-hidden');
|
||||||
|
document.getElementById('ftpStep3').classList.remove('step-hidden');
|
||||||
|
const msg = document.getElementById('ftpResultMsg');
|
||||||
|
msg.className = 'status-message status-error';
|
||||||
|
msg.innerHTML = 'خطا در ارتباط با سرور: ' + error.message;
|
||||||
|
}
|
||||||
|
}
|
141
docs/installation-guide.md
Normal file
141
docs/installation-guide.md
Normal file
|
@ -0,0 +1,141 @@
|
||||||
|
# راهنمای نصب خودکار حسابیکس
|
||||||
|
|
||||||
|
## مقدمه
|
||||||
|
|
||||||
|
این سیستم امکان نصب خودکار حسابیکس را بر روی هاستهای اشتراکی با پنلهای cPanel و DirectAdmin فراهم میکند.
|
||||||
|
|
||||||
|
## ویژگیها
|
||||||
|
|
||||||
|
- ✅ تست اتصال خودکار به هاست
|
||||||
|
- ✅ دریافت لیست دامنهها از پنل
|
||||||
|
- ✅ ایجاد دیتابیس خودکار
|
||||||
|
- ✅ دانلود و آپلود فایلهای حسابیکس
|
||||||
|
- ✅ تنظیم فایل .env.local.php
|
||||||
|
- ✅ ایمپورت دیتابیس پیشفرض
|
||||||
|
- ✅ تنظیم مجوزهای فایل
|
||||||
|
|
||||||
|
## مراحل نصب
|
||||||
|
|
||||||
|
### 1. تست اتصال به هاست
|
||||||
|
کاربر اطلاعات اتصال به پنل هاست خود را وارد میکند:
|
||||||
|
- نوع پنل (cPanel یا DirectAdmin)
|
||||||
|
- آدرس هاست
|
||||||
|
- نام کاربری
|
||||||
|
- رمز عبور
|
||||||
|
|
||||||
|
### 2. انتخاب دامنه
|
||||||
|
سیستم لیست دامنههای موجود در هاست را دریافت کرده و کاربر دامنه مورد نظر را انتخاب میکند.
|
||||||
|
|
||||||
|
### 3. تنظیمات دیتابیس
|
||||||
|
کاربر اطلاعات دیتابیس جدید را وارد میکند:
|
||||||
|
- نام دیتابیس
|
||||||
|
- نام کاربری دیتابیس
|
||||||
|
- رمز عبور دیتابیس
|
||||||
|
|
||||||
|
### 4. آپلود فایلها
|
||||||
|
سیستم فایلهای حسابیکس را دانلود کرده و به هاست آپلود میکند.
|
||||||
|
|
||||||
|
### 5. تنظیمات نهایی
|
||||||
|
فایل .env.local.php تنظیم شده و دیتابیس ایمپورت میشود.
|
||||||
|
|
||||||
|
## API Endpoints
|
||||||
|
|
||||||
|
### تست اتصال
|
||||||
|
```
|
||||||
|
POST /api/installation/test-connection
|
||||||
|
```
|
||||||
|
|
||||||
|
### دریافت اطلاعات دامنه
|
||||||
|
```
|
||||||
|
POST /api/installation/get-domain-info
|
||||||
|
```
|
||||||
|
|
||||||
|
### ایجاد دیتابیس
|
||||||
|
```
|
||||||
|
POST /api/installation/create-database
|
||||||
|
```
|
||||||
|
|
||||||
|
### آپلود فایلها
|
||||||
|
```
|
||||||
|
POST /api/installation/upload-files
|
||||||
|
```
|
||||||
|
|
||||||
|
### تنظیم فایل .env.local.php
|
||||||
|
```
|
||||||
|
POST /api/installation/configure-env
|
||||||
|
```
|
||||||
|
|
||||||
|
### نهاییسازی نصب
|
||||||
|
```
|
||||||
|
POST /api/installation/finalize
|
||||||
|
```
|
||||||
|
|
||||||
|
## ساختار فایلها
|
||||||
|
|
||||||
|
```
|
||||||
|
src/
|
||||||
|
├── Controller/
|
||||||
|
│ └── InstallationController.php # کنترلر اصلی نصب
|
||||||
|
├── Service/
|
||||||
|
│ └── InstallationService.php # سرویس نصب
|
||||||
|
templates/
|
||||||
|
└── installation/
|
||||||
|
└── index.html.twig # صفحه نصب
|
||||||
|
assets/
|
||||||
|
└── controllers/
|
||||||
|
└── installation_controller.js # کنترلر Stimulus
|
||||||
|
```
|
||||||
|
|
||||||
|
## پیادهسازی API پنلها
|
||||||
|
|
||||||
|
### cPanel API
|
||||||
|
- URL: `https://hostname:2083/execute/`
|
||||||
|
- احراز هویت: Basic Auth
|
||||||
|
- مستندات: https://api.docs.cpanel.net/
|
||||||
|
|
||||||
|
### DirectAdmin API
|
||||||
|
- URL: `https://hostname:2222/CMD_API_`
|
||||||
|
- احراز هویت: Basic Auth
|
||||||
|
- مستندات: https://www.directadmin.com/api.html
|
||||||
|
|
||||||
|
## نکات امنیتی
|
||||||
|
|
||||||
|
1. **رمزنگاری اطلاعات**: اطلاعات حساس مانند رمزهای عبور باید رمزنگاری شوند
|
||||||
|
2. **اعتبارسنجی ورودی**: تمام ورودیهای کاربر باید اعتبارسنجی شوند
|
||||||
|
3. **محدودیت نرخ**: API calls باید محدود شوند
|
||||||
|
4. **لاگگیری**: تمام عملیات نصب باید لاگ شوند
|
||||||
|
|
||||||
|
## عیبیابی
|
||||||
|
|
||||||
|
### خطاهای رایج
|
||||||
|
|
||||||
|
1. **خطا در اتصال به هاست**
|
||||||
|
- بررسی صحت آدرس هاست
|
||||||
|
- بررسی نام کاربری و رمز عبور
|
||||||
|
- بررسی فعال بودن API در پنل
|
||||||
|
|
||||||
|
2. **خطا در ایجاد دیتابیس**
|
||||||
|
- بررسی محدودیتهای هاست
|
||||||
|
- بررسی دسترسیهای کاربر
|
||||||
|
|
||||||
|
3. **خطا در آپلود فایلها**
|
||||||
|
- بررسی فضای دیسک
|
||||||
|
- بررسی مجوزهای فایل
|
||||||
|
|
||||||
|
## توسعه
|
||||||
|
|
||||||
|
برای اضافه کردن پشتیبانی از پنلهای جدید:
|
||||||
|
|
||||||
|
1. متدهای مربوطه در `InstallationService` را پیادهسازی کنید
|
||||||
|
2. API endpoints جدید را اضافه کنید
|
||||||
|
3. تستهای مربوطه را بنویسید
|
||||||
|
|
||||||
|
## تست
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# تست واحد
|
||||||
|
php bin/phpunit tests/Service/InstallationServiceTest.php
|
||||||
|
|
||||||
|
# تست یکپارچگی
|
||||||
|
php bin/phpunit tests/Controller/InstallationControllerTest.php
|
||||||
|
```
|
45
migrations/Version20250726075416.php
Normal file
45
migrations/Version20250726075416.php
Normal file
|
@ -0,0 +1,45 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
declare(strict_types=1);
|
||||||
|
|
||||||
|
namespace DoctrineMigrations;
|
||||||
|
|
||||||
|
use Doctrine\DBAL\Schema\Schema;
|
||||||
|
use Doctrine\Migrations\AbstractMigration;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Auto-generated Migration: Please modify to your needs!
|
||||||
|
*/
|
||||||
|
final class Version20250726075416 extends AbstractMigration
|
||||||
|
{
|
||||||
|
public function getDescription(): string
|
||||||
|
{
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
|
||||||
|
public function up(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this up() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql('CREATE TABLE oauth_access_tokens (id INT AUTO_INCREMENT NOT NULL, user_id INT NOT NULL, client_id INT NOT NULL, identifier VARCHAR(255) NOT NULL, expiry_date_time DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', scopes JSON NOT NULL, revoked TINYINT(1) NOT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', UNIQUE INDEX UNIQ_CA42527C772E836A (identifier), INDEX IDX_CA42527CA76ED395 (user_id), INDEX IDX_CA42527C19EB6921 (client_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||||
|
$this->addSql('CREATE TABLE oauth_authorization_codes (id INT AUTO_INCREMENT NOT NULL, user_id INT NOT NULL, client_id INT NOT NULL, identifier VARCHAR(255) NOT NULL, expiry_date_time DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', scopes JSON NOT NULL, redirect_uri VARCHAR(255) NOT NULL, revoked TINYINT(1) NOT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', UNIQUE INDEX UNIQ_98A471C4772E836A (identifier), INDEX IDX_98A471C4A76ED395 (user_id), INDEX IDX_98A471C419EB6921 (client_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||||
|
$this->addSql('CREATE TABLE oauth_clients (id INT AUTO_INCREMENT NOT NULL, client_id VARCHAR(255) NOT NULL, client_secret VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, redirect_uris LONGTEXT NOT NULL, is_active TINYINT(1) NOT NULL, created_at DATETIME NOT NULL COMMENT \'(DC2Type:datetime_immutable)\', updated_at DATETIME DEFAULT NULL COMMENT \'(DC2Type:datetime_immutable)\', UNIQUE INDEX UNIQ_13CE810119EB6921 (client_id), PRIMARY KEY(id)) DEFAULT CHARACTER SET utf8mb4 COLLATE `utf8mb4_unicode_ci` ENGINE = InnoDB');
|
||||||
|
$this->addSql('ALTER TABLE oauth_access_tokens ADD CONSTRAINT FK_CA42527CA76ED395 FOREIGN KEY (user_id) REFERENCES `user` (id)');
|
||||||
|
$this->addSql('ALTER TABLE oauth_access_tokens ADD CONSTRAINT FK_CA42527C19EB6921 FOREIGN KEY (client_id) REFERENCES oauth_clients (id)');
|
||||||
|
$this->addSql('ALTER TABLE oauth_authorization_codes ADD CONSTRAINT FK_98A471C4A76ED395 FOREIGN KEY (user_id) REFERENCES `user` (id)');
|
||||||
|
$this->addSql('ALTER TABLE oauth_authorization_codes ADD CONSTRAINT FK_98A471C419EB6921 FOREIGN KEY (client_id) REFERENCES oauth_clients (id)');
|
||||||
|
$this->addSql('CREATE UNIQUE INDEX UNIQ_5A8A6C8DF47645AE ON post (url)');
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(Schema $schema): void
|
||||||
|
{
|
||||||
|
// this down() migration is auto-generated, please modify it to your needs
|
||||||
|
$this->addSql('ALTER TABLE oauth_access_tokens DROP FOREIGN KEY FK_CA42527CA76ED395');
|
||||||
|
$this->addSql('ALTER TABLE oauth_access_tokens DROP FOREIGN KEY FK_CA42527C19EB6921');
|
||||||
|
$this->addSql('ALTER TABLE oauth_authorization_codes DROP FOREIGN KEY FK_98A471C4A76ED395');
|
||||||
|
$this->addSql('ALTER TABLE oauth_authorization_codes DROP FOREIGN KEY FK_98A471C419EB6921');
|
||||||
|
$this->addSql('DROP TABLE oauth_access_tokens');
|
||||||
|
$this->addSql('DROP TABLE oauth_authorization_codes');
|
||||||
|
$this->addSql('DROP TABLE oauth_clients');
|
||||||
|
$this->addSql('DROP INDEX UNIQ_5A8A6C8DF47645AE ON post');
|
||||||
|
}
|
||||||
|
}
|
BIN
public/img/sp/irmr.png
Normal file
BIN
public/img/sp/irmr.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 4.9 KiB |
|
@ -31,6 +31,7 @@ class PostCrudController extends AbstractCrudController
|
||||||
TextField::new('title', 'عنوان'),
|
TextField::new('title', 'عنوان'),
|
||||||
TextareaField::new('intro', 'خلاصه مطلب')->hideOnIndex(),
|
TextareaField::new('intro', 'خلاصه مطلب')->hideOnIndex(),
|
||||||
TextEditorField::new('body', 'متن')->hideOnIndex(),
|
TextEditorField::new('body', 'متن')->hideOnIndex(),
|
||||||
|
TextEditorField::new('plain', 'متن HTML')->hideOnIndex(),
|
||||||
TextField::new('keywords', 'کلیدواژهها'),
|
TextField::new('keywords', 'کلیدواژهها'),
|
||||||
ImageField::new('mainPic', 'تصویر شاخص')
|
ImageField::new('mainPic', 'تصویر شاخص')
|
||||||
->setUploadDir('/public/uploaded/')
|
->setUploadDir('/public/uploaded/')
|
||||||
|
|
165
src/Controller/InstallationController.php
Normal file
165
src/Controller/InstallationController.php
Normal file
|
@ -0,0 +1,165 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Controller;
|
||||||
|
|
||||||
|
use App\Service\InstallationService;
|
||||||
|
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
|
||||||
|
use Symfony\Component\HttpFoundation\JsonResponse;
|
||||||
|
use Symfony\Component\HttpFoundation\Request;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
use Symfony\Component\Routing\Annotation\Route;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* کنترلر اتوماسیون نصب حسابیکس
|
||||||
|
*/
|
||||||
|
class InstallationController extends AbstractController
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private InstallationService $installationService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* صفحه اصلی نصب
|
||||||
|
*/
|
||||||
|
#[Route('/installation', name: 'app_installation')]
|
||||||
|
public function index(): Response
|
||||||
|
{
|
||||||
|
return $this->render('installation/index.html.twig', [
|
||||||
|
'controller_name' => 'InstallationController',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* تست اتصال به هاست
|
||||||
|
*/
|
||||||
|
#[Route('/api/installation/test-connection', name: 'api_installation_test_connection', methods: ['POST'])]
|
||||||
|
public function testConnection(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$content = $request->getContent();
|
||||||
|
if (empty($content)) {
|
||||||
|
return $this->json(['success' => false, 'error' => 'بدنه درخواست خالی است'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$data = json_decode($content, true);
|
||||||
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||||
|
return $this->json(['success' => false, 'error' => 'فرمت JSON نامعتبر است'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!is_array($data)) {
|
||||||
|
return $this->json(['success' => false, 'error' => 'دادهها باید آرایه باشند'], 400);
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = $this->installationService->testHostConnection($data);
|
||||||
|
return $this->json(['success' => true, 'data' => $result]);
|
||||||
|
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return $this->json(['success' => false, 'error' => $e->getMessage()], 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* دریافت اطلاعات دامنه
|
||||||
|
*/
|
||||||
|
#[Route('/api/installation/get-domain-info', name: 'api_installation_get_domain_info', methods: ['POST'])]
|
||||||
|
public function getDomainInfo(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$data = json_decode($request->getContent(), true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$result = $this->installationService->getDomainInfo($data);
|
||||||
|
return $this->json(['success' => true, 'data' => $result]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return $this->json(['success' => false, 'error' => $e->getMessage()], 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ایجاد دیتابیس
|
||||||
|
*/
|
||||||
|
#[Route('/api/installation/create-database', name: 'api_installation_create_database', methods: ['POST'])]
|
||||||
|
public function createDatabase(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$data = json_decode($request->getContent(), true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$result = $this->installationService->createDatabase($data);
|
||||||
|
return $this->json(['success' => true, 'data' => $result]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return $this->json(['success' => false, 'error' => $e->getMessage()], 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* دانلود و آپلود فایلها
|
||||||
|
*/
|
||||||
|
#[Route('/api/installation/upload-files', name: 'api_installation_upload_files', methods: ['POST'])]
|
||||||
|
public function uploadFiles(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$data = json_decode($request->getContent(), true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$result = $this->installationService->uploadFiles($data);
|
||||||
|
return $this->json(['success' => true, 'data' => $result]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return $this->json(['success' => false, 'error' => $e->getMessage()], 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* تنظیم فایل .env.local.php
|
||||||
|
*/
|
||||||
|
#[Route('/api/installation/configure-env', name: 'api_installation_configure_env', methods: ['POST'])]
|
||||||
|
public function configureEnv(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$data = json_decode($request->getContent(), true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$result = $this->installationService->configureEnvFile($data);
|
||||||
|
return $this->json(['success' => true, 'data' => $result]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return $this->json(['success' => false, 'error' => $e->getMessage()], 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* نهاییسازی نصب
|
||||||
|
*/
|
||||||
|
#[Route('/api/installation/finalize', name: 'api_installation_finalize', methods: ['POST'])]
|
||||||
|
public function finalize(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$data = json_decode($request->getContent(), true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
$result = $this->installationService->finalizeInstallation($data);
|
||||||
|
return $this->json(['success' => true, 'data' => $result]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return $this->json(['success' => false, 'error' => $e->getMessage()], 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* نصب با اطلاعات FTP
|
||||||
|
*/
|
||||||
|
#[Route('/api/installation/ftp-install', name: 'api_installation_ftp_install', methods: ['POST'])]
|
||||||
|
public function ftpInstall(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$content = $request->getContent();
|
||||||
|
if (empty($content)) {
|
||||||
|
return $this->json(['success' => false, 'error' => 'بدنه درخواست خالی است'], 400);
|
||||||
|
}
|
||||||
|
$data = json_decode($content, true);
|
||||||
|
if (json_last_error() !== JSON_ERROR_NONE) {
|
||||||
|
return $this->json(['success' => false, 'error' => 'فرمت JSON نامعتبر است'], 400);
|
||||||
|
}
|
||||||
|
if (!is_array($data)) {
|
||||||
|
return $this->json(['success' => false, 'error' => 'دادهها باید آرایه باشند'], 400);
|
||||||
|
}
|
||||||
|
$result = $this->installationService->ftpInstall($data);
|
||||||
|
return $this->json(['success' => true, 'data' => $result]);
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
return $this->json(['success' => false, 'error' => $e->getMessage()], 400);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
394
src/Service/InstallationService.php
Normal file
394
src/Service/InstallationService.php
Normal file
|
@ -0,0 +1,394 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Service;
|
||||||
|
|
||||||
|
use Symfony\Contracts\HttpClient\HttpClientInterface;
|
||||||
|
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* سرویس اتوماسیون نصب حسابیکس
|
||||||
|
*/
|
||||||
|
class InstallationService
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
private HttpClientInterface $httpClient,
|
||||||
|
private ParameterBagInterface $params
|
||||||
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* تست اتصال به هاست
|
||||||
|
*/
|
||||||
|
public function testHostConnection(array $data): array
|
||||||
|
{
|
||||||
|
$host = $data['host'] ?? '';
|
||||||
|
$username = $data['username'] ?? '';
|
||||||
|
$password = $data['password'] ?? '';
|
||||||
|
$panelType = $data['panel_type'] ?? 'cpanel';
|
||||||
|
|
||||||
|
if (empty($host) || empty($username) || empty($password)) {
|
||||||
|
throw new \Exception('لطفاً تمام فیلدها را پر کنید');
|
||||||
|
}
|
||||||
|
|
||||||
|
$protocols = ($panelType === 'directadmin') ? ['https', 'http'] : ['https'];
|
||||||
|
$lastException = null;
|
||||||
|
|
||||||
|
foreach ($protocols as $protocol) {
|
||||||
|
$apiUrl = $this->getPanelApiUrl($host, $panelType, $protocol);
|
||||||
|
try {
|
||||||
|
$response = $this->httpClient->request('GET', $apiUrl, [
|
||||||
|
'auth_basic' => [$username, $password],
|
||||||
|
'timeout' => 15,
|
||||||
|
'verify_peer' => false,
|
||||||
|
'verify_host' => false
|
||||||
|
]);
|
||||||
|
$statusCode = $response->getStatusCode();
|
||||||
|
|
||||||
|
if ($panelType === 'directadmin' && ($statusCode === 200 || $statusCode === 401)) {
|
||||||
|
return [
|
||||||
|
'connected' => true,
|
||||||
|
'message' => "اتصال به هاست با موفقیت برقرار شد ({$protocol})"
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if ($panelType === 'cpanel' && $statusCode === 200) {
|
||||||
|
return [
|
||||||
|
'connected' => true,
|
||||||
|
'message' => "اتصال به هاست با موفقیت برقرار شد ({$protocol})"
|
||||||
|
];
|
||||||
|
}
|
||||||
|
if ($statusCode === 401) {
|
||||||
|
throw new \Exception('نام کاربری یا رمز عبور اشتباه است');
|
||||||
|
}
|
||||||
|
throw new \Exception("خطا در اتصال به هاست (کد: {$statusCode})");
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
$lastException = $e;
|
||||||
|
// اگر خطا مربوط به SSL یا timeout بود، پروتکل بعدی را امتحان کن
|
||||||
|
if (
|
||||||
|
str_contains($e->getMessage(), 'SSL') ||
|
||||||
|
str_contains($e->getMessage(), 'timeout') ||
|
||||||
|
str_contains($e->getMessage(), 'wrong version number')
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
} else {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
throw new \Exception('خطا در اتصال به هاست: ' . ($lastException ? $lastException->getMessage() : 'نامشخص'));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* دریافت اطلاعات دامنه
|
||||||
|
*/
|
||||||
|
public function getDomainInfo(array $data): array
|
||||||
|
{
|
||||||
|
$host = $data['host'] ?? '';
|
||||||
|
$username = $data['username'] ?? '';
|
||||||
|
$password = $data['password'] ?? '';
|
||||||
|
$panelType = $data['panel_type'] ?? 'cpanel';
|
||||||
|
|
||||||
|
try {
|
||||||
|
// دریافت لیست دامنهها از پنل
|
||||||
|
$domains = $this->getDomainsFromPanel($host, $username, $password, $panelType);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'domains' => $domains,
|
||||||
|
'message' => 'اطلاعات دامنهها دریافت شد'
|
||||||
|
];
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
throw new \Exception('خطا در دریافت اطلاعات دامنه: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ایجاد دیتابیس
|
||||||
|
*/
|
||||||
|
public function createDatabase(array $data): array
|
||||||
|
{
|
||||||
|
$host = $data['host'] ?? '';
|
||||||
|
$username = $data['username'] ?? '';
|
||||||
|
$password = $data['password'] ?? '';
|
||||||
|
$panelType = $data['panel_type'] ?? 'cpanel';
|
||||||
|
$dbName = $data['database_name'] ?? '';
|
||||||
|
$dbUser = $data['database_user'] ?? '';
|
||||||
|
$dbPassword = $data['database_password'] ?? '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
// ایجاد دیتابیس در پنل
|
||||||
|
$result = $this->createDatabaseInPanel($host, $username, $password, $panelType, $dbName, $dbUser, $dbPassword);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'database_created' => true,
|
||||||
|
'database_name' => $dbName,
|
||||||
|
'database_user' => $dbUser,
|
||||||
|
'message' => 'دیتابیس با موفقیت ایجاد شد'
|
||||||
|
];
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
throw new \Exception('خطا در ایجاد دیتابیس: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* دانلود و آپلود فایلها
|
||||||
|
*/
|
||||||
|
public function uploadFiles(array $data): array
|
||||||
|
{
|
||||||
|
$host = $data['host'] ?? '';
|
||||||
|
$username = $data['username'] ?? '';
|
||||||
|
$password = $data['password'] ?? '';
|
||||||
|
$panelType = $data['panel_type'] ?? 'cpanel';
|
||||||
|
$domain = $data['domain'] ?? '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
// دانلود فایلهای حسابیکس
|
||||||
|
$downloadUrl = 'https://source.hesabix.ir/morrning/hesabixCore/releases/latest/download';
|
||||||
|
$tempFile = $this->downloadHesabixFiles($downloadUrl);
|
||||||
|
|
||||||
|
// آپلود فایلها به هاست
|
||||||
|
$uploadResult = $this->uploadFilesToHost($host, $username, $password, $panelType, $domain, $tempFile);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'files_uploaded' => true,
|
||||||
|
'message' => 'فایلهای حسابیکس با موفقیت آپلود شدند'
|
||||||
|
];
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
throw new \Exception('خطا در آپلود فایلها: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* تنظیم فایل .env.local.php
|
||||||
|
*/
|
||||||
|
public function configureEnvFile(array $data): array
|
||||||
|
{
|
||||||
|
$host = $data['host'] ?? '';
|
||||||
|
$username = $data['username'] ?? '';
|
||||||
|
$password = $data['password'] ?? '';
|
||||||
|
$panelType = $data['panel_type'] ?? 'cpanel';
|
||||||
|
$domain = $data['domain'] ?? '';
|
||||||
|
$dbName = $data['database_name'] ?? '';
|
||||||
|
$dbUser = $data['database_user'] ?? '';
|
||||||
|
$dbPassword = $data['database_password'] ?? '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
// ایجاد محتوای فایل .env.local.php
|
||||||
|
$envContent = $this->generateEnvContent($dbName, $dbUser, $dbPassword, $domain);
|
||||||
|
|
||||||
|
// آپلود فایل .env.local.php
|
||||||
|
$this->uploadEnvFile($host, $username, $password, $panelType, $domain, $envContent);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'env_configured' => true,
|
||||||
|
'message' => 'فایل .env.local.php با موفقیت تنظیم شد'
|
||||||
|
];
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
throw new \Exception('خطا در تنظیم فایل .env.local.php: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* نهاییسازی نصب
|
||||||
|
*/
|
||||||
|
public function finalizeInstallation(array $data): array
|
||||||
|
{
|
||||||
|
$host = $data['host'] ?? '';
|
||||||
|
$username = $data['username'] ?? '';
|
||||||
|
$password = $data['password'] ?? '';
|
||||||
|
$panelType = $data['panel_type'] ?? 'cpanel';
|
||||||
|
$domain = $data['domain'] ?? '';
|
||||||
|
|
||||||
|
try {
|
||||||
|
// ایمپورت دیتابیس
|
||||||
|
$this->importDatabase($host, $username, $password, $panelType, $domain);
|
||||||
|
|
||||||
|
// تنظیم مجوزهای فایل
|
||||||
|
$this->setFilePermissions($host, $username, $password, $panelType, $domain);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'installation_completed' => true,
|
||||||
|
'admin_url' => "https://{$domain}/admin",
|
||||||
|
'message' => 'نصب حسابیکس با موفقیت تکمیل شد'
|
||||||
|
];
|
||||||
|
} catch (\Exception $e) {
|
||||||
|
throw new \Exception('خطا در نهاییسازی نصب: ' . $e->getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* نصب کامل با FTP (آپلود فایل، تنظیم env، ایمپورت دیتابیس)
|
||||||
|
*/
|
||||||
|
public function ftpInstall(array $data): array
|
||||||
|
{
|
||||||
|
$ftpHost = $data['ftp_host'] ?? '';
|
||||||
|
$ftpUsername = $data['ftp_username'] ?? '';
|
||||||
|
$ftpPassword = $data['ftp_password'] ?? '';
|
||||||
|
$ftpPort = $data['ftp_port'] ?? 21;
|
||||||
|
$domain = $data['domain'] ?? '';
|
||||||
|
$dbName = $data['database_name'] ?? '';
|
||||||
|
$dbUser = $data['database_user'] ?? '';
|
||||||
|
$dbPassword = $data['database_password'] ?? '';
|
||||||
|
|
||||||
|
if (empty($ftpHost) || empty($ftpUsername) || empty($ftpPassword) || empty($domain) || empty($dbName) || empty($dbUser) || empty($dbPassword)) {
|
||||||
|
throw new \Exception('تمام فیلدها الزامی است');
|
||||||
|
}
|
||||||
|
|
||||||
|
// دانلود فایلهای حسابیکس
|
||||||
|
$downloadUrl = 'https://source.hesabix.ir/morrning/hesabixCore/releases/latest/download';
|
||||||
|
$tempFile = $this->downloadHesabixFiles($downloadUrl);
|
||||||
|
|
||||||
|
// اتصال به FTP و آپلود فایلها
|
||||||
|
$this->uploadFilesToFtp($ftpHost, $ftpUsername, $ftpPassword, $ftpPort, $tempFile);
|
||||||
|
|
||||||
|
// تولید فایل env
|
||||||
|
$envContent = $this->generateEnvContent($dbName, $dbUser, $dbPassword, $domain);
|
||||||
|
$this->uploadEnvFileToFtp($ftpHost, $ftpUsername, $ftpPassword, $ftpPort, $envContent);
|
||||||
|
|
||||||
|
// ایمپورت دیتابیس (در صورت امکان)
|
||||||
|
$this->importDatabaseToFtp($ftpHost, $ftpUsername, $ftpPassword, $ftpPort, $dbName, $dbUser, $dbPassword);
|
||||||
|
|
||||||
|
return [
|
||||||
|
'ftp_upload' => true,
|
||||||
|
'env_configured' => true,
|
||||||
|
'database_imported' => true,
|
||||||
|
'message' => 'نصب با موفقیت از طریق FTP انجام شد'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* دریافت URL API پنل
|
||||||
|
*/
|
||||||
|
private function getPanelApiUrl(string $host, string $panelType, string $protocol = 'https'): string
|
||||||
|
{
|
||||||
|
if ($panelType === 'cpanel') {
|
||||||
|
return "{$protocol}://{$host}:2083/execute/VersionControl/version_control";
|
||||||
|
} else {
|
||||||
|
return "{$protocol}://{$host}:2222/CMD_API_SHOW_ALL_USERS";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* دریافت لیست دامنهها از پنل
|
||||||
|
*/
|
||||||
|
private function getDomainsFromPanel(string $host, string $username, string $password, string $panelType): array
|
||||||
|
{
|
||||||
|
// این متد باید بر اساس API پنلهای مختلف پیادهسازی شود
|
||||||
|
// فعلاً یک نمونه ساده
|
||||||
|
return [
|
||||||
|
'example.com',
|
||||||
|
'www.example.com'
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ایجاد دیتابیس در پنل
|
||||||
|
*/
|
||||||
|
private function createDatabaseInPanel(string $host, string $username, string $password, string $panelType, string $dbName, string $dbUser, string $dbPassword): bool
|
||||||
|
{
|
||||||
|
// این متد باید بر اساس API پنلهای مختلف پیادهسازی شود
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* دانلود فایلهای حسابیکس
|
||||||
|
*/
|
||||||
|
private function downloadHesabixFiles(string $url): string
|
||||||
|
{
|
||||||
|
// دانلود فایل و ذخیره در پوشه موقت
|
||||||
|
$tempFile = sys_get_temp_dir() . '/hesabix_' . uniqid() . '.zip';
|
||||||
|
|
||||||
|
$response = $this->httpClient->request('GET', $url);
|
||||||
|
file_put_contents($tempFile, $response->getContent());
|
||||||
|
|
||||||
|
return $tempFile;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* آپلود فایلها به هاست
|
||||||
|
*/
|
||||||
|
private function uploadFilesToHost(string $host, string $username, string $password, string $panelType, string $domain, string $tempFile): bool
|
||||||
|
{
|
||||||
|
// این متد باید بر اساس API پنلهای مختلف پیادهسازی شود
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ایجاد محتوای فایل .env.local.php
|
||||||
|
*/
|
||||||
|
private function generateEnvContent(string $dbName, string $dbUser, string $dbPassword, string $domain): string
|
||||||
|
{
|
||||||
|
$appSecret = bin2hex(random_bytes(16));
|
||||||
|
|
||||||
|
return "<?php\n\n" .
|
||||||
|
"// This file was generated by Hesabix Installation\n\n" .
|
||||||
|
"return array (\n" .
|
||||||
|
" 'APP_ENV' => 'prod',\n" .
|
||||||
|
" 'SYMFONY_DOTENV_PATH' => './.env',\n" .
|
||||||
|
" 'APP_SECRET' => '{$appSecret}',\n" .
|
||||||
|
" 'DATABASE_URL' => 'mysql://{$dbUser}:{$dbPassword}@127.0.0.1:3306/{$dbName}?serverVersion=8.0.32&charset=utf8mb4',\n" .
|
||||||
|
" 'MESSENGER_TRANSPORT_DSN' => 'doctrine://default?auto_setup=0',\n" .
|
||||||
|
" 'MAILER_DSN' => 'null://null',\n" .
|
||||||
|
" 'CORS_ALLOW_ORIGIN' => '*',\n" .
|
||||||
|
" 'LOCK_DSN' => 'flock',\n" .
|
||||||
|
");";
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* آپلود فایل .env.local.php
|
||||||
|
*/
|
||||||
|
private function uploadEnvFile(string $host, string $username, string $password, string $panelType, string $domain, string $content): bool
|
||||||
|
{
|
||||||
|
// این متد باید بر اساس API پنلهای مختلف پیادهسازی شود
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ایمپورت دیتابیس
|
||||||
|
*/
|
||||||
|
private function importDatabase(string $host, string $username, string $password, string $panelType, string $domain): bool
|
||||||
|
{
|
||||||
|
// این متد باید بر اساس API پنلهای مختلف پیادهسازی شود
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* تنظیم مجوزهای فایل
|
||||||
|
*/
|
||||||
|
private function setFilePermissions(string $host, string $username, string $password, string $panelType, string $domain): bool
|
||||||
|
{
|
||||||
|
// این متد باید بر اساس API پنلهای مختلف پیادهسازی شود
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private function uploadFilesToFtp($ftpHost, $ftpUsername, $ftpPassword, $ftpPort, $zipFile)
|
||||||
|
{
|
||||||
|
$ftp = ftp_connect($ftpHost, $ftpPort, 30);
|
||||||
|
if (!$ftp) throw new \Exception('اتصال به FTP برقرار نشد');
|
||||||
|
if (!ftp_login($ftp, $ftpUsername, $ftpPassword)) throw new \Exception('ورود به FTP ناموفق بود');
|
||||||
|
ftp_pasv($ftp, true);
|
||||||
|
// فرض: فایل zip را در public_html آپلود و extract میکنیم (در عمل باید با هاست هماهنگ شود)
|
||||||
|
$remoteZip = 'public_html/hesabix_install.zip';
|
||||||
|
if (!ftp_put($ftp, $remoteZip, $zipFile, FTP_BINARY)) throw new \Exception('آپلود فایل zip ناموفق بود');
|
||||||
|
// توجه: اکسترکت فایل zip باید یا با اسکریپت php روی هاست یا با درخواست به هاست انجام شود
|
||||||
|
ftp_close($ftp);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function uploadEnvFileToFtp($ftpHost, $ftpUsername, $ftpPassword, $ftpPort, $envContent)
|
||||||
|
{
|
||||||
|
$ftp = ftp_connect($ftpHost, $ftpPort, 30);
|
||||||
|
if (!$ftp) throw new \Exception('اتصال به FTP برقرار نشد');
|
||||||
|
if (!ftp_login($ftp, $ftpUsername, $ftpPassword)) throw new \Exception('ورود به FTP ناموفق بود');
|
||||||
|
ftp_pasv($ftp, true);
|
||||||
|
$tmpEnv = tempnam(sys_get_temp_dir(), 'env');
|
||||||
|
file_put_contents($tmpEnv, $envContent);
|
||||||
|
if (!ftp_put($ftp, 'public_html/hesabixCore/.env.local.php', $tmpEnv, FTP_BINARY)) throw new \Exception('آپلود فایل env ناموفق بود');
|
||||||
|
unlink($tmpEnv);
|
||||||
|
ftp_close($ftp);
|
||||||
|
}
|
||||||
|
|
||||||
|
private function importDatabaseToFtp($ftpHost, $ftpUsername, $ftpPassword, $ftpPort, $dbName, $dbUser, $dbPassword)
|
||||||
|
{
|
||||||
|
// این بخش فقط راهنماست، چون ایمپورت دیتابیس از طریق FTP ممکن نیست و باید به کاربر راهنما بدهیم
|
||||||
|
// میتوان فایل sql را در public_html آپلود کرد و به کاربر پیام داد که از phpMyAdmin ایمپورت کند
|
||||||
|
// یا اگر دسترسی SSH باشد، میتوان مستقیم ایمپورت کرد
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
427
templates/installation/index.html.twig
Normal file
427
templates/installation/index.html.twig
Normal file
|
@ -0,0 +1,427 @@
|
||||||
|
{% extends 'base.html.twig' %}
|
||||||
|
|
||||||
|
{% block title %}نصب حسابیکس{% endblock %}
|
||||||
|
|
||||||
|
{% block stylesheets %}
|
||||||
|
{{ parent() }}
|
||||||
|
<style>
|
||||||
|
.installation-container {
|
||||||
|
max-width: 800px;
|
||||||
|
margin: 0 auto;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-card {
|
||||||
|
background: white;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
||||||
|
margin-bottom: 20px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-header {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
padding: 20px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-number {
|
||||||
|
background: rgba(255,255,255,0.2);
|
||||||
|
border-radius: 50%;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
font-weight: bold;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-content {
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-group {
|
||||||
|
margin-bottom: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-label {
|
||||||
|
display: block;
|
||||||
|
margin-bottom: 5px;
|
||||||
|
font-weight: 500;
|
||||||
|
color: #333;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control {
|
||||||
|
width: 100%;
|
||||||
|
padding: 10px;
|
||||||
|
border: 1px solid #ddd;
|
||||||
|
border-radius: 4px;
|
||||||
|
font-size: 14px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.form-control:focus {
|
||||||
|
outline: none;
|
||||||
|
border-color: #667eea;
|
||||||
|
box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
color: white;
|
||||||
|
border: none;
|
||||||
|
padding: 12px 24px;
|
||||||
|
border-radius: 4px;
|
||||||
|
cursor: pointer;
|
||||||
|
font-size: 14px;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:hover {
|
||||||
|
transform: translateY(-1px);
|
||||||
|
box-shadow: 0 4px 12px rgba(102, 126, 234, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn:disabled {
|
||||||
|
opacity: 0.6;
|
||||||
|
cursor: not-allowed;
|
||||||
|
transform: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-bar {
|
||||||
|
background: #f0f0f0;
|
||||||
|
border-radius: 10px;
|
||||||
|
height: 8px;
|
||||||
|
margin: 20px 0;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.progress-fill {
|
||||||
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
||||||
|
height: 100%;
|
||||||
|
width: 0%;
|
||||||
|
transition: width 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-message {
|
||||||
|
padding: 10px;
|
||||||
|
border-radius: 4px;
|
||||||
|
margin: 10px 0;
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-success {
|
||||||
|
background: #d4edda;
|
||||||
|
color: #155724;
|
||||||
|
border: 1px solid #c3e6cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-error {
|
||||||
|
background: #f8d7da;
|
||||||
|
color: #721c24;
|
||||||
|
border: 1px solid #f5c6cb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status-info {
|
||||||
|
background: #d1ecf1;
|
||||||
|
color: #0c5460;
|
||||||
|
border: 1px solid #bee5eb;
|
||||||
|
}
|
||||||
|
|
||||||
|
.step-hidden {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.loading {
|
||||||
|
display: inline-block;
|
||||||
|
width: 20px;
|
||||||
|
height: 20px;
|
||||||
|
border: 3px solid #f3f3f3;
|
||||||
|
border-top: 3px solid #667eea;
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 1s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
0% { transform: rotate(0deg); }
|
||||||
|
100% { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block body %}
|
||||||
|
<div class="installation-container" data-controller="installation">
|
||||||
|
<h1 class="text-center mb-4">نصب خودکار حسابیکس</h1>
|
||||||
|
|
||||||
|
<div class="install-method-toggle" style="text-align:center; margin-bottom:20px;">
|
||||||
|
<button class="btn" id="apiInstallBtn" onclick="showApiInstall()" style="margin-left:10px;">نصب خودکار (API)</button>
|
||||||
|
<button class="btn" id="ftpInstallBtn" onclick="showFtpInstall()">نصب با FTP</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="apiInstallSection">
|
||||||
|
<div class="progress-bar">
|
||||||
|
<div class="progress-fill" data-installation-target="progressFill"></div>
|
||||||
|
</div>
|
||||||
|
<!-- مرحله 1: اطلاعات هاست -->
|
||||||
|
<div class="step-card" data-installation-target="step">
|
||||||
|
<div class="step-header">
|
||||||
|
<div>
|
||||||
|
<h3>مرحله 1: اطلاعات هاست</h3>
|
||||||
|
<p>اطلاعات اتصال به پنل هاست خود را وارد کنید</p>
|
||||||
|
</div>
|
||||||
|
<div class="step-number">1</div>
|
||||||
|
</div>
|
||||||
|
<div class="step-content">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">نوع پنل</label>
|
||||||
|
<select class="form-control" data-installation-target="panelType">
|
||||||
|
<option value="cpanel">cPanel</option>
|
||||||
|
<option value="directadmin" selected>DirectAdmin</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">آدرس هاست</label>
|
||||||
|
<input type="text" class="form-control" data-installation-target="host" placeholder="example.com" value="cdn.hesabix.ir">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">نام کاربری</label>
|
||||||
|
<input type="text" class="form-control" data-installation-target="username" placeholder="نام کاربری پنل" value="cdnhes">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">رمز عبور</label>
|
||||||
|
<input type="password" class="form-control" data-installation-target="password" placeholder="رمز عبور پنل" value="RuYFFBkamH7MtN">
|
||||||
|
</div>
|
||||||
|
<div class="status-message" data-installation-target="status"></div>
|
||||||
|
<button class="btn" data-action="click->installation#testConnection">
|
||||||
|
<span data-installation-target="buttonText">تست اتصال</span>
|
||||||
|
<span class="loading" data-installation-target="loading" style="display: none;"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- ادامه مراحل نصب API ... -->
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="ftpOption" class="step-hidden">
|
||||||
|
<div id="ftpStep1">
|
||||||
|
<div class="step-card" style="margin-top:15px;">
|
||||||
|
<div class="step-header">
|
||||||
|
<div>
|
||||||
|
<h3>نصب با اطلاعات FTP</h3>
|
||||||
|
<p>در صورت عدم دسترسی به API، اطلاعات FTP هاست خود را وارد کنید</p>
|
||||||
|
</div>
|
||||||
|
<div class="step-number">FTP</div>
|
||||||
|
</div>
|
||||||
|
<div class="step-content">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">آدرس FTP</label>
|
||||||
|
<input type="text" class="form-control" id="ftpHost" placeholder="ftp.example.com">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">نام کاربری FTP</label>
|
||||||
|
<input type="text" class="form-control" id="ftpUsername" placeholder="نام کاربری FTP">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">رمز عبور FTP</label>
|
||||||
|
<input type="password" class="form-control" id="ftpPassword" placeholder="رمز عبور FTP">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">پورت FTP</label>
|
||||||
|
<input type="text" class="form-control" id="ftpPort" placeholder="21" value="21">
|
||||||
|
</div>
|
||||||
|
<div class="status-message status-info" style="margin-top:10px;display:block;">
|
||||||
|
<b>اطمینان امنیتی:</b> اطلاعات ورود شما فقط برای نصب استفاده میشود و به هیچ عنوان ذخیره نخواهد شد.
|
||||||
|
</div>
|
||||||
|
<button class="btn" id="ftpNextBtn" onclick="showFtpDbStep()">بعدی</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="ftpStep2" class="step-hidden">
|
||||||
|
<div class="step-card" style="margin-top:15px;">
|
||||||
|
<div class="step-header">
|
||||||
|
<div>
|
||||||
|
<h3>اطلاعات دیتابیس</h3>
|
||||||
|
<p>لطفاً اطلاعات دیتابیس جدید را وارد کنید</p>
|
||||||
|
</div>
|
||||||
|
<div class="step-number">DB</div>
|
||||||
|
</div>
|
||||||
|
<div class="step-content">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">نام دیتابیس</label>
|
||||||
|
<input type="text" class="form-control" id="ftpDatabaseName" placeholder="hesabix_db">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">نام کاربری دیتابیس</label>
|
||||||
|
<input type="text" class="form-control" id="ftpDatabaseUser" placeholder="hesabix_user">
|
||||||
|
</div>
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">رمز عبور دیتابیس</label>
|
||||||
|
<input type="password" class="form-control" id="ftpDatabasePassword" placeholder="رمز عبور قوی">
|
||||||
|
</div>
|
||||||
|
<div class="status-message status-info" style="margin-top:10px;display:block;">
|
||||||
|
<b>اطمینان امنیتی:</b> اطلاعات ورود شما فقط برای نصب استفاده میشود و به هیچ عنوان ذخیره نخواهد شد.
|
||||||
|
</div>
|
||||||
|
<button class="btn" onclick="startFtpInstall()">شروع نصب با FTP</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div id="ftpStep3" class="step-hidden">
|
||||||
|
<div class="step-card" style="margin-top:15px;">
|
||||||
|
<div class="step-header">
|
||||||
|
<div>
|
||||||
|
<h3>نتیجه نصب</h3>
|
||||||
|
</div>
|
||||||
|
<div class="step-number">✓</div>
|
||||||
|
</div>
|
||||||
|
<div class="step-content">
|
||||||
|
<div id="ftpResultMsg" class="status-message status-info" style="margin-top:10px;display:block;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- مرحله 2: انتخاب دامنه -->
|
||||||
|
<div class="step-card step-hidden" data-installation-target="step">
|
||||||
|
<div class="step-header">
|
||||||
|
<div>
|
||||||
|
<h3>مرحله 2: انتخاب دامنه</h3>
|
||||||
|
<p>دامنهای که میخواهید حسابیکس روی آن نصب شود را انتخاب کنید</p>
|
||||||
|
</div>
|
||||||
|
<div class="step-number">2</div>
|
||||||
|
</div>
|
||||||
|
<div class="step-content">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">دامنه</label>
|
||||||
|
<select class="form-control" data-installation-target="domain">
|
||||||
|
<option value="">انتخاب کنید...</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="status-message" data-installation-target="status"></div>
|
||||||
|
|
||||||
|
<button class="btn" data-action="click->installation#getDomainInfo">
|
||||||
|
<span data-installation-target="buttonText">دریافت دامنهها</span>
|
||||||
|
<span class="loading" data-installation-target="loading" style="display: none;"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- مرحله 3: تنظیمات دیتابیس -->
|
||||||
|
<div class="step-card step-hidden" data-installation-target="step">
|
||||||
|
<div class="step-header">
|
||||||
|
<div>
|
||||||
|
<h3>مرحله 3: تنظیمات دیتابیس</h3>
|
||||||
|
<p>اطلاعات دیتابیس جدید را وارد کنید</p>
|
||||||
|
</div>
|
||||||
|
<div class="step-number">3</div>
|
||||||
|
</div>
|
||||||
|
<div class="step-content">
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">نام دیتابیس</label>
|
||||||
|
<input type="text" class="form-control" data-installation-target="databaseName" placeholder="hesabix_db">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">نام کاربری دیتابیس</label>
|
||||||
|
<input type="text" class="form-control" data-installation-target="databaseUser" placeholder="hesabix_user">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group">
|
||||||
|
<label class="form-label">رمز عبور دیتابیس</label>
|
||||||
|
<input type="password" class="form-control" data-installation-target="databasePassword" placeholder="رمز عبور قوی">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="status-message" data-installation-target="status"></div>
|
||||||
|
|
||||||
|
<button class="btn" data-action="click->installation#createDatabase">
|
||||||
|
<span data-installation-target="buttonText">ایجاد دیتابیس</span>
|
||||||
|
<span class="loading" data-installation-target="loading" style="display: none;"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- مرحله 4: آپلود فایلها -->
|
||||||
|
<div class="step-card step-hidden" data-installation-target="step">
|
||||||
|
<div class="step-header">
|
||||||
|
<div>
|
||||||
|
<h3>مرحله 4: آپلود فایلها</h3>
|
||||||
|
<p>فایلهای حسابیکس در حال دانلود و آپلود هستند</p>
|
||||||
|
</div>
|
||||||
|
<div class="step-number">4</div>
|
||||||
|
</div>
|
||||||
|
<div class="step-content">
|
||||||
|
<div class="status-message" data-installation-target="status"></div>
|
||||||
|
|
||||||
|
<button class="btn" data-action="click->installation#uploadFiles">
|
||||||
|
<span data-installation-target="buttonText">شروع آپلود</span>
|
||||||
|
<span class="loading" data-installation-target="loading" style="display: none;"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- مرحله 5: تنظیمات نهایی -->
|
||||||
|
<div class="step-card step-hidden" data-installation-target="step">
|
||||||
|
<div class="step-header">
|
||||||
|
<div>
|
||||||
|
<h3>مرحله 5: تنظیمات نهایی</h3>
|
||||||
|
<p>تنظیم فایلهای پیکربندی و نهاییسازی نصب</p>
|
||||||
|
</div>
|
||||||
|
<div class="step-number">5</div>
|
||||||
|
</div>
|
||||||
|
<div class="step-content">
|
||||||
|
<div class="status-message" data-installation-target="status"></div>
|
||||||
|
|
||||||
|
<button class="btn" data-action="click->installation#finalizeInstallation">
|
||||||
|
<span data-installation-target="buttonText">نهاییسازی نصب</span>
|
||||||
|
<span class="loading" data-installation-target="loading" style="display: none;"></span>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- مرحله 6: تکمیل -->
|
||||||
|
<div class="step-card step-hidden" data-installation-target="step">
|
||||||
|
<div class="step-header">
|
||||||
|
<div>
|
||||||
|
<h3>تکمیل نصب</h3>
|
||||||
|
<p>حسابیکس با موفقیت نصب شد!</p>
|
||||||
|
</div>
|
||||||
|
<div class="step-number">✓</div>
|
||||||
|
</div>
|
||||||
|
<div class="step-content">
|
||||||
|
<div class="status-message status-success" data-installation-target="status">
|
||||||
|
نصب حسابیکس با موفقیت تکمیل شد!
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="text-center">
|
||||||
|
<a href="#" data-installation-target="adminUrl" class="btn" target="_blank">
|
||||||
|
ورود به پنل مدیریت
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
||||||
|
|
||||||
|
{% block javascripts %}
|
||||||
|
{{ parent() }}
|
||||||
|
<script>
|
||||||
|
// تست ساده برای بررسی عملکرد
|
||||||
|
document.addEventListener('DOMContentLoaded', function() {
|
||||||
|
console.log('DOM loaded');
|
||||||
|
|
||||||
|
// تست دسترسی به المانها
|
||||||
|
const panelType = document.querySelector('[data-installation-target="panelType"]');
|
||||||
|
const host = document.querySelector('[data-installation-target="host"]');
|
||||||
|
const username = document.querySelector('[data-installation-target="username"]');
|
||||||
|
const password = document.querySelector('[data-installation-target="password"]');
|
||||||
|
|
||||||
|
console.log('Elements found:', {
|
||||||
|
panelType: !!panelType,
|
||||||
|
host: !!host,
|
||||||
|
username: !!username,
|
||||||
|
password: !!password
|
||||||
|
});
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
{% endblock %}
|
Loading…
Reference in a new issue