From b1f8af83f460799f990fe0837b09a3e152082255 Mon Sep 17 00:00:00 2001 From: Babak Alizadeh Date: Sat, 12 Jul 2025 11:19:24 +0000 Subject: [PATCH] support for change repositories --- .../System/UpdateCoreController.php | 186 +++++++++++++++++- webUI/src/i18n/fa_lang.ts | 13 ++ .../user/manager/settings/update-core.vue | 102 +++++++++- 3 files changed, 293 insertions(+), 8 deletions(-) diff --git a/hesabixCore/src/Controller/System/UpdateCoreController.php b/hesabixCore/src/Controller/System/UpdateCoreController.php index 33e8ddaa..73987384 100644 --- a/hesabixCore/src/Controller/System/UpdateCoreController.php +++ b/hesabixCore/src/Controller/System/UpdateCoreController.php @@ -23,8 +23,9 @@ final class UpdateCoreController extends AbstractController public function api_admin_updatecore_run(): JsonResponse { $projectDir = $this->getParameter('kernel.project_dir'); + $gitRoot = dirname($projectDir); // رفتن به ریشه پروژه $uuid = uniqid(); - $stateFile = $projectDir . '/../hesabixBackup/update_state_' . $uuid . '.json'; + $stateFile = $gitRoot . '/hesabixBackup/update_state_' . $uuid . '.json'; if (!file_exists(dirname($stateFile))) { mkdir(dirname($stateFile), 0755, true); @@ -41,7 +42,7 @@ final class UpdateCoreController extends AbstractController 'COMPOSER_HOME' => '/var/www/.composer', ]); - $process = new Process(['php', 'bin/console', 'hesabix:update', $stateFile], $projectDir, $env); + $process = new Process(['php', 'hesabixCore/bin/console', 'hesabix:update', $stateFile], $gitRoot, $env); $process->setTimeout(7200); // افزایش تایم‌اوت به 2 ساعت $process->start(function ($type, $buffer) use ($stateFile) { $state = json_decode(file_get_contents($stateFile), true) ?? ['uuid' => uniqid(), 'log' => '']; @@ -70,7 +71,9 @@ final class UpdateCoreController extends AbstractController ], 400); } - $stateFile = $this->getParameter('kernel.project_dir') . '/../hesabixBackup/update_state_' . $uuid . '.json'; + $projectDir = $this->getParameter('kernel.project_dir'); + $gitRoot = dirname($projectDir); + $stateFile = $gitRoot . '/hesabixBackup/update_state_' . $uuid . '.json'; if (!file_exists($stateFile)) { return new JsonResponse([ @@ -97,7 +100,7 @@ final class UpdateCoreController extends AbstractController } if (!$isRunning) { - $backupDir = $this->getParameter('kernel.project_dir') . '/../hesabixBackup'; + $backupDir = $gitRoot . '/hesabixBackup'; $stateFiles = glob($backupDir . '/update_state_*.json'); foreach ($stateFiles as $file) { if (is_file($file)) { @@ -128,7 +131,9 @@ final class UpdateCoreController extends AbstractController return new JsonResponse(['status' => 'error', 'message' => 'UUID is required'], 400); } - $stateFile = $this->getParameter('kernel.project_dir') . '/../hesabixBackup/update_state_' . $uuid . '.json'; + $projectDir = $this->getParameter('kernel.project_dir'); + $gitRoot = dirname($projectDir); + $stateFile = $gitRoot . '/hesabixBackup/update_state_' . $uuid . '.json'; return new StreamedResponse(function () use ($stateFile) { header('Content-Type: text/event-stream'); @@ -167,13 +172,14 @@ final class UpdateCoreController extends AbstractController public function api_admin_updatecore_commits(): JsonResponse { $projectDir = $this->getParameter('kernel.project_dir'); + $gitRoot = dirname($projectDir); // رفتن به ریشه پروژه - $currentProcess = new Process(['git', 'rev-parse', 'HEAD'], $projectDir); + $currentProcess = new Process(['git', 'rev-parse', 'HEAD'], $gitRoot); $currentProcess->setTimeout(7200); // افزایش تایم‌اوت $currentProcess->run(); $currentCommit = $currentProcess->isSuccessful() ? trim($currentProcess->getOutput()) : 'unknown'; - $targetProcess = new Process(['git', 'ls-remote', 'origin', 'HEAD'], $projectDir); + $targetProcess = new Process(['git', 'ls-remote', 'origin', 'HEAD'], $gitRoot); $targetProcess->setTimeout(7200); // افزایش تایم‌اوت $targetProcess->run(); $targetOutput = $targetProcess->isSuccessful() ? explode("\t", trim($targetProcess->getOutput()))[0] : 'unknown'; @@ -428,4 +434,170 @@ final class UpdateCoreController extends AbstractController ], 500); } } + + #[Route('/api/admin/updatecore/current-source', name: 'api_admin_updatecore_current_source', methods: ['GET'])] + public function api_admin_updatecore_current_source(): JsonResponse + { + $projectDir = $this->getParameter('kernel.project_dir'); + $gitRoot = dirname($projectDir); // رفتن به ریشه پروژه + $output = ''; + + try { + // بررسی اینکه آیا پروژه یک مخزن Git است + if (!is_dir($gitRoot . '/.git')) { + return new JsonResponse([ + 'status' => 'error', + 'message' => 'این پروژه یک مخزن Git نیست', + 'sourceUrl' => '', + ], 400); + } + + // دریافت آدرس مخزن origin فعلی + $process = new Process(['git', 'remote', 'get-url', 'origin'], $gitRoot); + $process->setTimeout(7200); // افزایش تایم‌اوت + $process->run(); + + if (!$process->isSuccessful()) { + return new JsonResponse([ + 'status' => 'error', + 'message' => 'خطا در دریافت آدرس مخزن: ' . $process->getErrorOutput(), + 'sourceUrl' => '', + ], 500); + } + + $sourceUrl = trim($process->getOutput()); + $output .= "آدرس مخزن فعلی: $sourceUrl\n"; + + return new JsonResponse([ + 'status' => 'success', + 'sourceUrl' => $sourceUrl, + 'output' => $output, + ]); + + } catch (\Exception $e) { + return new JsonResponse([ + 'status' => 'error', + 'message' => 'خطا در بررسی مخزن: ' . $e->getMessage(), + 'sourceUrl' => '', + ], 500); + } + } + + #[Route('/api/admin/updatecore/change-source', name: 'api_admin_updatecore_change_source', methods: ['POST'])] + public function api_admin_updatecore_change_source(Request $request): JsonResponse + { + $sourceUrl = $request->getPayload()->get('sourceUrl'); + $output = ''; + + if (!$sourceUrl || !filter_var($sourceUrl, FILTER_VALIDATE_URL) && !preg_match('/^git@[^:]+:[^\/]+\/[^\/]+\.git$/', $sourceUrl)) { + return new JsonResponse([ + 'status' => 'error', + 'message' => 'آدرس مخزن نامعتبر است. لطفاً یک آدرس HTTP یا SSH معتبر وارد کنید.', + 'output' => $output, + ], 400); + } + + $projectDir = $this->getParameter('kernel.project_dir'); + $gitRoot = dirname($projectDir); // رفتن به ریشه پروژه + + try { + // بررسی اینکه آیا پروژه یک مخزن Git است + if (!is_dir($gitRoot . '/.git')) { + return new JsonResponse([ + 'status' => 'error', + 'message' => 'این پروژه یک مخزن Git نیست', + 'output' => $output, + ], 400); + } + + $output .= "شروع تغییر آدرس مخزن...\n"; + + // دریافت آدرس مخزن فعلی + $currentProcess = new Process(['git', 'remote', 'get-url', 'origin'], $gitRoot); + $currentProcess->setTimeout(7200); + $currentProcess->run(); + $currentUrl = $currentProcess->isSuccessful() ? trim($currentProcess->getOutput()) : ''; + + if ($currentUrl) { + $output .= "آدرس مخزن فعلی: $currentUrl\n"; + } + + // تغییر آدرس مخزن origin + $changeProcess = new Process(['git', 'remote', 'set-url', 'origin', $sourceUrl], $gitRoot); + $changeProcess->setTimeout(7200); + $changeProcess->run(); + + if (!$changeProcess->isSuccessful()) { + return new JsonResponse([ + 'status' => 'error', + 'message' => 'خطا در تغییر آدرس مخزن: ' . $changeProcess->getErrorOutput(), + 'output' => $output, + ], 500); + } + + $output .= "آدرس مخزن به $sourceUrl تغییر یافت\n"; + + // بررسی اتصال به مخزن جدید + $testProcess = new Process(['git', 'remote', 'show', 'origin'], $gitRoot); + $testProcess->setTimeout(7200); + $testProcess->run(); + + if (!$testProcess->isSuccessful()) { + return new JsonResponse([ + 'status' => 'error', + 'message' => 'خطا در اتصال به مخزن جدید: ' . $testProcess->getErrorOutput(), + 'output' => $output, + ], 500); + } + + $output .= "اتصال به مخزن جدید با موفقیت برقرار شد\n"; + + // دریافت اطلاعات مخزن جدید + $fetchProcess = new Process(['git', 'fetch', 'origin'], $gitRoot); + $fetchProcess->setTimeout(7200); + $fetchProcess->run(); + + if (!$fetchProcess->isSuccessful()) { + $output .= "هشدار: خطا در دریافت اطلاعات از مخزن جدید: " . $fetchProcess->getErrorOutput() . "\n"; + } else { + $output .= "اطلاعات مخزن جدید با موفقیت دریافت شد\n"; + } + + // بررسی branch های موجود + $branchProcess = new Process(['git', 'branch', '-r'], $gitRoot); + $branchProcess->setTimeout(7200); + $branchProcess->run(); + + if ($branchProcess->isSuccessful()) { + $branches = trim($branchProcess->getOutput()); + if ($branches) { + $output .= "شاخه‌های موجود در مخزن جدید:\n$branches\n"; + } else { + $output .= "هیچ شاخه‌ای در مخزن جدید یافت نشد\n"; + } + } + + // پاک کردن کش Git + $cleanProcess = new Process(['git', 'gc', '--prune=now'], $gitRoot); + $cleanProcess->setTimeout(7200); + $cleanProcess->run(); + + if ($cleanProcess->isSuccessful()) { + $output .= "کش Git پاک شد\n"; + } + + return new JsonResponse([ + 'status' => 'success', + 'message' => 'آدرس مخزن با موفقیت تغییر یافت و اتصال برقرار شد', + 'output' => $output, + ]); + + } catch (\Exception $e) { + return new JsonResponse([ + 'status' => 'error', + 'message' => 'خطا در تغییر آدرس مخزن: ' . $e->getMessage(), + 'output' => $output, + ], 500); + } + } } \ No newline at end of file diff --git a/webUI/src/i18n/fa_lang.ts b/webUI/src/i18n/fa_lang.ts index cd1a9fcc..d12fcc03 100644 --- a/webUI/src/i18n/fa_lang.ts +++ b/webUI/src/i18n/fa_lang.ts @@ -268,6 +268,19 @@ const fa_lang = { "fetchError": "خطا در دریافت", "cancel": "لغو", "confirm": "تأیید", + "updateSourceTitle": "منبع به‌روزرسانی", + "updateSourceLabel": "آدرس منبع به‌روزرسانی", + "changeSourceButton": "تغییر منبع", + "sourceUrlRequired": "لطفاً آدرس منبع به‌روزرسانی را وارد کنید", + "changingSourceMessage": "در حال تغییر منبع به‌روزرسانی...", + "sourceChangeSuccess": "منبع به‌روزرسانی با موفقیت تغییر یافت", + "sourceChangeError": "خطایی در تغییر منبع به‌روزرسانی رخ داد", + "notGitRepository": "این پروژه یک مخزن Git نیست", + "invalidRepositoryUrl": "آدرس مخزن نامعتبر است. لطفاً یک آدرس HTTP یا SSH معتبر وارد کنید.", + "repositoryUrlError": "خطا در دریافت آدرس مخزن", + "repositoryChangeError": "خطا در تغییر آدرس مخزن", + "repositoryConnectionError": "خطا در اتصال به مخزن جدید", + "repositoryChangeSuccess": "آدرس مخزن با موفقیت تغییر یافت و اتصال برقرار شد", }, static: { not_found: "صفحه مورد نظر یافت نشد", diff --git a/webUI/src/views/user/manager/settings/update-core.vue b/webUI/src/views/user/manager/settings/update-core.vue index b3910628..57550592 100644 --- a/webUI/src/views/user/manager/settings/update-core.vue +++ b/webUI/src/views/user/manager/settings/update-core.vue @@ -31,7 +31,7 @@

{{ $t('updateSoftware.distroVersion') }}: {{ systemInfo.distroVersion }}

{{ $t('updateSoftware.webServer') }}: {{ systemInfo.webServer }}

{{ $t('updateSoftware.dbName') }}: {{ systemInfo.dbName }}

-

{{ $t('updateSoftware.dbVersion') }}: {{ systemInfo.dbVersion }}

+

{{ $t('updateSoftware.dbVersion') }}: {{ systemInfo.dbVersion }}

{{ $t('updateSoftware.currentEnv') }}: {{ selectedEnv }}

@@ -76,6 +76,39 @@ + + + + {{ $t('updateSoftware.updateSourceTitle') }} + + + + + + + + + {{ $t('updateSoftware.changeSourceButton') }} + + + + + + @@ -201,6 +234,8 @@ export default { const isLoadingLogs = ref(false); const isClearingLogs = ref(false); const isPolling = ref(false); + const updateSourceUrl = ref(''); + const isChangingSource = ref(false); return { isUpdating, @@ -229,6 +264,8 @@ export default { isLoadingLogs, isClearingLogs, isPolling, + updateSourceUrl, + isChangingSource, }; }, computed: { @@ -508,6 +545,32 @@ export default { }; } }, + async fetchCurrentSource() { + try { + const response = await axios.get('/api/admin/updatecore/current-source', { + headers: { 'X-Requested-With': 'XMLHttpRequest' }, + timeout: 7200000 // تایم‌اوت 2 ساعته + }); + + if (response.data.status === 'success') { + this.updateSourceUrl = response.data.sourceUrl || ''; + } else { + console.error('Failed to fetch current source:', response.data.message); + this.updateSourceUrl = ''; + } + } catch (error) { + console.error('Failed to fetch current source:', error); + this.updateSourceUrl = ''; + + // نمایش پیام خطا به کاربر + if (error.response?.data?.message) { + this.showResultDialog = true; + this.dialogTitle = this.$t('updateSoftware.dialogErrorTitle'); + this.dialogMessage = error.response.data.message; + this.dialogColor = 'error'; + } + } + }, async fetchCurrentEnv() { try { const response = await axios.get('/api/admin/updatecore/current-env', { @@ -570,6 +633,42 @@ export default { this.isClearingLogs = false; } }, + async changeUpdateSource() { + if (!this.updateSourceUrl.trim()) { + this.showResultDialog = true; + this.dialogTitle = this.$t('updateSoftware.dialogErrorTitle'); + this.dialogMessage = this.$t('updateSoftware.sourceUrlRequired'); + this.dialogColor = 'error'; + return; + } + + this.isChangingSource = true; + this.showOutput = true; + this.output = this.$t('updateSoftware.changingSourceMessage') + '\n'; + + try { + const response = await axios.post('/api/admin/updatecore/change-source', { + sourceUrl: this.updateSourceUrl.trim() + }, { + headers: { 'X-Requested-With': 'XMLHttpRequest' }, + timeout: 7200000 // تایم‌اوت 2 ساعته + }); + + this.output += response.data.output || response.data.message + '\n'; + this.showResultDialog = true; + this.dialogTitle = this.$t('updateSoftware.dialogSuccessTitle'); + this.dialogMessage = response.data.message || this.$t('updateSoftware.repositoryChangeSuccess'); + this.dialogColor = 'success'; + } catch (error) { + this.output += 'خطا: ' + (error.response?.data?.message || error.message) + '\n'; + this.showResultDialog = true; + this.dialogTitle = this.$t('updateSoftware.dialogErrorTitle'); + this.dialogMessage = error.response?.data?.message || this.$t('updateSoftware.sourceChangeError'); + this.dialogColor = 'error'; + } finally { + this.isChangingSource = false; + } + }, copyLogsToClipboard() { const plainLogs = this.systemLogs.replace(/\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+\+\d{2}:\d{2}\]/g, '\n[$&]') .replace(/\s+\[\]/g, ' []') @@ -593,6 +692,7 @@ export default { this.fetchCommits(); this.fetchSystemInfo(); this.fetchCurrentEnv(); + this.fetchCurrentSource(); this.buttonText = this.$t('updateSoftware.startButton'); this.refreshLogs(); },