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', 'عنوان'),
|
||||
TextareaField::new('intro', 'خلاصه مطلب')->hideOnIndex(),
|
||||
TextEditorField::new('body', 'متن')->hideOnIndex(),
|
||||
TextEditorField::new('plain', 'متن HTML')->hideOnIndex(),
|
||||
TextField::new('keywords', 'کلیدواژهها'),
|
||||
ImageField::new('mainPic', 'تصویر شاخص')
|
||||
->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