diff --git a/hesabixCore/src/Command/UpdateSoftwareCommand.php b/hesabixCore/src/Command/UpdateSoftwareCommand.php index e2b48ab..787d191 100644 --- a/hesabixCore/src/Command/UpdateSoftwareCommand.php +++ b/hesabixCore/src/Command/UpdateSoftwareCommand.php @@ -272,8 +272,88 @@ class UpdateSoftwareCommand extends Command } } + /** + * Helper method to fix Git "dubious ownership" error + */ + private function fixGitOwnershipIssue(string $gitRoot): bool + { + try { + // Check if the directory is a Git repository + if (!is_dir($gitRoot . '/.git')) { + return false; + } + + // Always add the directory to safe.directory when this method is called + // This handles cases where Git detects dubious ownership for other reasons + $safeDirProcess = new Process(['git', 'config', '--global', '--add', 'safe.directory', $gitRoot], $gitRoot); + $safeDirProcess->setTimeout(300); + $safeDirProcess->run(); + + if ($safeDirProcess->isSuccessful()) { + $this->logger->info("Fixed Git ownership issue for directory: $gitRoot"); + return true; + } + + return false; + } catch (\Exception $e) { + $this->logger->warning("Failed to fix Git ownership issue: " . $e->getMessage()); + return false; + } + } + + /** + * Helper method to run Git command with ownership fix + */ + private function runGitCommand(array $command, string $workingDir, OutputInterface $output, int $retries = 3): void + { + $attempt = 0; + while ($attempt < $retries) { + try { + $process = new Process($command, $workingDir); + $process->setTimeout(3600); + + if ($output->isVerbose()) { + $process->mustRun(function ($type, $buffer) use ($output) { + $this->writeOutput($output, $buffer); + }); + } else { + $process->mustRun(); + $this->writeOutput($output, $process->getOutput()); + } + + $this->logger->info('Git command executed successfully: ' . implode(' ', $command)); + return; + } catch (ProcessFailedException $e) { + $attempt++; + $errorMessage = $e->getProcess()->getErrorOutput() ?: $e->getMessage(); + $this->logger->warning("Attempt $attempt failed for " . implode(' ', $command) . ": $errorMessage"); + $this->writeOutput($output, "Attempt $attempt failed: $errorMessage"); + + // If the command failed with "dubious ownership" error, try to fix it + if (str_contains($errorMessage, 'dubious ownership')) { + $this->writeOutput($output, "Detected Git ownership issue, attempting to fix..."); + if ($this->fixGitOwnershipIssue($workingDir)) { + $this->writeOutput($output, "Git ownership issue fixed, retrying command..."); + continue; // Retry the command without incrementing attempt + } + } + + if ($attempt === $retries) { + throw new \RuntimeException('Command "' . implode(' ', $command) . '" failed after ' . $retries . ' attempts: ' . $errorMessage); + } + sleep(5); + } + } + } + private function runProcess(array $command, string $workingDir, OutputInterface $output, int $retries = 3, bool $isComposer = false): void { + // If this is a Git command, use the specialized Git command runner + if (in_array($command[0], ['git'])) { + $this->runGitCommand($command, $workingDir, $output, $retries); + return; + } + $attempt = 0; while ($attempt < $retries) { try { @@ -318,13 +398,28 @@ class UpdateSoftwareCommand extends Command private function getCurrentGitHead(): string { - $process = new Process(['git', 'rev-parse', 'HEAD'], $this->rootDir); - $process->run(); - if (!$process->isSuccessful()) { - $this->logger->warning('Failed to get current Git HEAD: ' . $process->getErrorOutput()); + try { + $process = new Process(['git', 'rev-parse', 'HEAD'], $this->rootDir); + $process->run(); + + // If the command failed with "dubious ownership" error, try to fix it + if (!$process->isSuccessful() && str_contains($process->getErrorOutput(), 'dubious ownership')) { + if ($this->fixGitOwnershipIssue($this->rootDir)) { + // Retry the command after fixing ownership + $process = new Process(['git', 'rev-parse', 'HEAD'], $this->rootDir); + $process->run(); + } + } + + if (!$process->isSuccessful()) { + $this->logger->warning('Failed to get current Git HEAD: ' . $process->getErrorOutput()); + return 'unknown'; + } + return trim($process->getOutput()); + } catch (\Exception $e) { + $this->logger->warning('Failed to get current Git HEAD: ' . $e->getMessage()); return 'unknown'; } - return trim($process->getOutput()); } private function isUpToDate(): bool @@ -333,6 +428,16 @@ class UpdateSoftwareCommand extends Command $this->runProcess(['git', 'fetch', 'origin'], $this->rootDir, new \Symfony\Component\Console\Output\NullOutput()); $process = new Process(['git', 'status', '-uno'], $this->rootDir); $process->run(); + + // If the command failed with "dubious ownership" error, try to fix it + if (!$process->isSuccessful() && str_contains($process->getErrorOutput(), 'dubious ownership')) { + if ($this->fixGitOwnershipIssue($this->rootDir)) { + // Retry the command after fixing ownership + $process = new Process(['git', 'status', '-uno'], $this->rootDir); + $process->run(); + } + } + $status = $process->getOutput(); return strpos($status, 'Your branch is up to date') !== false; } catch (\Exception $e) { @@ -558,6 +663,16 @@ class UpdateSoftwareCommand extends Command $this->logger->warning('Command executed as root user.'); } + // Check and fix Git ownership issues proactively + if (is_dir($this->rootDir . '/.git')) { + $this->writeOutput($output, 'Checking Git repository ownership...'); + if ($this->fixGitOwnershipIssue($this->rootDir)) { + $this->writeOutput($output, 'Git ownership issue detected and fixed.'); + } else { + $this->writeOutput($output, 'Git repository ownership is correct.'); + } + } + $this->writeOutput($output, 'Pre-update checks completed successfully.'); } diff --git a/hesabixCore/src/Controller/System/UpdateCoreController.php b/hesabixCore/src/Controller/System/UpdateCoreController.php index 7398738..7e1bb87 100644 --- a/hesabixCore/src/Controller/System/UpdateCoreController.php +++ b/hesabixCore/src/Controller/System/UpdateCoreController.php @@ -19,6 +19,55 @@ final class UpdateCoreController extends AbstractController $this->connection = $connection; } + /** + * Helper method to fix Git "dubious ownership" error + */ + private function fixGitOwnershipIssue(string $gitRoot): bool + { + try { + // Check if the directory is a Git repository + if (!is_dir($gitRoot . '/.git')) { + return false; + } + + // Always add the directory to safe.directory when this method is called + // This handles cases where Git detects dubious ownership for other reasons + $safeDirProcess = new Process(['git', 'config', '--global', '--add', 'safe.directory', $gitRoot], $gitRoot); + $safeDirProcess->setTimeout(300); + $safeDirProcess->run(); + + if ($safeDirProcess->isSuccessful()) { + return true; + } + + return false; + } catch (\Exception $e) { + return false; + } + } + + /** + * Helper method to run Git command with ownership fix + */ + private function runGitCommand(array $command, string $gitRoot, int $timeout = 7200): Process + { + $process = new Process($command, $gitRoot); + $process->setTimeout($timeout); + $process->run(); + + // If the command failed with "dubious ownership" error, try to fix it + if (!$process->isSuccessful() && str_contains($process->getErrorOutput(), 'dubious ownership')) { + if ($this->fixGitOwnershipIssue($gitRoot)) { + // Retry the command after fixing ownership + $process = new Process($command, $gitRoot); + $process->setTimeout($timeout); + $process->run(); + } + } + + return $process; + } + #[Route('/api/admin/updatecore/run', name: 'api_admin_updatecore_run', methods: ['POST'])] public function api_admin_updatecore_run(): JsonResponse { @@ -42,20 +91,39 @@ final class UpdateCoreController extends AbstractController 'COMPOSER_HOME' => '/var/www/.composer', ]); + // اجرای command به صورت synchronous برای اطمینان از اجرا $process = new Process(['php', 'hesabixCore/bin/console', 'hesabix:update', $stateFile], $gitRoot, $env); $process->setTimeout(7200); // افزایش تایم‌اوت به 2 ساعت - $process->start(function ($type, $buffer) use ($stateFile) { + + // اجرای command و دریافت خروجی + $process->run(function ($type, $buffer) use ($stateFile) { $state = json_decode(file_get_contents($stateFile), true) ?? ['uuid' => uniqid(), 'log' => '']; $state['log'] .= $buffer; file_put_contents($stateFile, json_encode($state)); }); + // بررسی نتیجه اجرا + if (!$process->isSuccessful()) { + $state = json_decode(file_get_contents($stateFile), true) ?? ['uuid' => $uuid, 'log' => '']; + $state['error'] = $process->getErrorOutput(); + $state['log'] .= "\nError: " . $process->getErrorOutput(); + file_put_contents($stateFile, json_encode($state)); + + return new JsonResponse([ + 'status' => 'error', + 'message' => 'Update process failed: ' . $process->getErrorOutput(), + 'uuid' => $uuid, + ], 500); + } + + // خواندن وضعیت نهایی $state = json_decode(file_get_contents($stateFile), true) ?? ['uuid' => $uuid, 'log' => '']; return new JsonResponse([ 'status' => 'started', - 'message' => 'Update process started', + 'message' => 'Update process completed', 'uuid' => $uuid, + 'output' => $state['log'] ?? '', ]); } @@ -124,7 +192,7 @@ final class UpdateCoreController extends AbstractController } #[Route('/api/admin/updatecore/stream', name: 'api_admin_updatecore_stream', methods: ['GET'])] - public function api_admin_updatecore_stream(Request $request): StreamedResponse|JsonResponse + public function api_admin_updatecore_stream(Request $request): JsonResponse { $uuid = $request->query->get('uuid'); if (!$uuid) { @@ -135,37 +203,27 @@ final class UpdateCoreController extends AbstractController $gitRoot = dirname($projectDir); $stateFile = $gitRoot . '/hesabixBackup/update_state_' . $uuid . '.json'; - return new StreamedResponse(function () use ($stateFile) { - header('Content-Type: text/event-stream'); - header('Cache-Control: no-cache'); - header('Connection: keep-alive'); + if (!file_exists($stateFile)) { + return new JsonResponse(['status' => 'idle', 'output' => '']); + } - while (true) { - if (!file_exists($stateFile)) { - echo "data: " . json_encode(['status' => 'idle', 'output' => '']) . "\n\n"; - ob_flush(); - flush(); - break; - } + $state = json_decode(file_get_contents($stateFile), true) ?? ['log' => '']; + $output = $state['log'] ?? ''; - $state = json_decode(file_get_contents($stateFile), true) ?? ['log' => '']; - $output = $state['log'] ?? ''; + $isRunning = !isset($state['error']) && + !in_array('post_update_test', $state['completedSteps'] ?? []) && + !str_contains($output, 'No update needed') && + !str_contains($output, 'Software update completed successfully'); - $isRunning = !isset($state['error']) && - !in_array('post_update_test', $state['completedSteps'] ?? []); - - $status = isset($state['error']) ? 'error' : ($isRunning ? 'running' : 'success'); - echo "data: " . json_encode(['status' => $status, 'output' => $output]) . "\n\n"; - ob_flush(); - flush(); - - if (!$isRunning) { - break; - } - - sleep(1); - } - }); + $status = isset($state['error']) ? 'error' : ($isRunning ? 'running' : 'success'); + + return new JsonResponse([ + 'status' => $status, + 'output' => $output, + 'completedSteps' => $state['completedSteps'] ?? [], + 'error' => $state['error'] ?? null, + 'commit_hash' => $state['commit_hash'] ?? null + ]); } #[Route('/api/admin/updatecore/commits', name: 'api_admin_updatecore_commits', methods: ['GET'])] @@ -174,14 +232,10 @@ final class UpdateCoreController extends AbstractController $projectDir = $this->getParameter('kernel.project_dir'); $gitRoot = dirname($projectDir); // رفتن به ریشه پروژه - $currentProcess = new Process(['git', 'rev-parse', 'HEAD'], $gitRoot); - $currentProcess->setTimeout(7200); // افزایش تایم‌اوت - $currentProcess->run(); + $currentProcess = $this->runGitCommand(['git', 'rev-parse', 'HEAD'], $gitRoot); $currentCommit = $currentProcess->isSuccessful() ? trim($currentProcess->getOutput()) : 'unknown'; - $targetProcess = new Process(['git', 'ls-remote', 'origin', 'HEAD'], $gitRoot); - $targetProcess->setTimeout(7200); // افزایش تایم‌اوت - $targetProcess->run(); + $targetProcess = $this->runGitCommand(['git', 'ls-remote', 'origin', 'HEAD'], $gitRoot); $targetOutput = $targetProcess->isSuccessful() ? explode("\t", trim($targetProcess->getOutput()))[0] : 'unknown'; return new JsonResponse([ @@ -453,9 +507,7 @@ final class UpdateCoreController extends AbstractController } // دریافت آدرس مخزن origin فعلی - $process = new Process(['git', 'remote', 'get-url', 'origin'], $gitRoot); - $process->setTimeout(7200); // افزایش تایم‌اوت - $process->run(); + $process = $this->runGitCommand(['git', 'remote', 'get-url', 'origin'], $gitRoot); if (!$process->isSuccessful()) { return new JsonResponse([ @@ -483,6 +535,76 @@ final class UpdateCoreController extends AbstractController } } + #[Route('/api/admin/updatecore/run-manual', name: 'api_admin_updatecore_run_manual', methods: ['POST'])] + public function api_admin_updatecore_run_manual(Request $request): JsonResponse + { + $uuid = $request->getPayload()->get('uuid'); + if (!$uuid) { + return new JsonResponse([ + 'status' => 'error', + 'message' => 'UUID is required', + 'output' => '', + ], 400); + } + + $projectDir = $this->getParameter('kernel.project_dir'); + $gitRoot = dirname($projectDir); + $stateFile = $gitRoot . '/hesabixBackup/update_state_' . $uuid . '.json'; + + if (!file_exists($stateFile)) { + return new JsonResponse([ + 'status' => 'error', + 'message' => 'State file not found', + 'output' => '', + ], 404); + } + + $output = ''; + try { + $output .= "شروع اجرای دستی به‌روزرسانی...\n"; + + // اجرای command hesabix:update + $env = array_merge($_SERVER, [ + 'HOME' => '/var/www', + 'COMPOSER_HOME' => '/var/www/.composer', + ]); + + $process = new Process(['php', 'hesabixCore/bin/console', 'hesabix:update', $stateFile], $gitRoot, $env); + $process->setTimeout(7200); + $process->run(); + + $output .= $process->getOutput(); + + if (!$process->isSuccessful()) { + $output .= "\nخطا: " . $process->getErrorOutput(); + return new JsonResponse([ + 'status' => 'error', + 'message' => 'خطا در اجرای به‌روزرسانی', + 'output' => $output, + ], 500); + } + + // بررسی فایل state بعد از اجرا + if (file_exists($stateFile)) { + $state = json_decode(file_get_contents($stateFile), true) ?? []; + $output .= "\nوضعیت نهایی: " . json_encode($state, JSON_PRETTY_PRINT); + } + + return new JsonResponse([ + 'status' => 'success', + 'message' => 'به‌روزرسانی با موفقیت انجام شد', + 'output' => $output, + ]); + + } catch (\Exception $e) { + return new JsonResponse([ + 'status' => 'error', + 'message' => 'خطا در اجرای دستی: ' . $e->getMessage(), + 'output' => $output, + ], 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 { @@ -513,9 +635,7 @@ final class UpdateCoreController extends AbstractController $output .= "شروع تغییر آدرس مخزن...\n"; // دریافت آدرس مخزن فعلی - $currentProcess = new Process(['git', 'remote', 'get-url', 'origin'], $gitRoot); - $currentProcess->setTimeout(7200); - $currentProcess->run(); + $currentProcess = $this->runGitCommand(['git', 'remote', 'get-url', 'origin'], $gitRoot); $currentUrl = $currentProcess->isSuccessful() ? trim($currentProcess->getOutput()) : ''; if ($currentUrl) { @@ -523,9 +643,7 @@ final class UpdateCoreController extends AbstractController } // تغییر آدرس مخزن origin - $changeProcess = new Process(['git', 'remote', 'set-url', 'origin', $sourceUrl], $gitRoot); - $changeProcess->setTimeout(7200); - $changeProcess->run(); + $changeProcess = $this->runGitCommand(['git', 'remote', 'set-url', 'origin', $sourceUrl], $gitRoot); if (!$changeProcess->isSuccessful()) { return new JsonResponse([ @@ -538,9 +656,7 @@ final class UpdateCoreController extends AbstractController $output .= "آدرس مخزن به $sourceUrl تغییر یافت\n"; // بررسی اتصال به مخزن جدید - $testProcess = new Process(['git', 'remote', 'show', 'origin'], $gitRoot); - $testProcess->setTimeout(7200); - $testProcess->run(); + $testProcess = $this->runGitCommand(['git', 'remote', 'show', 'origin'], $gitRoot); if (!$testProcess->isSuccessful()) { return new JsonResponse([ @@ -553,9 +669,7 @@ final class UpdateCoreController extends AbstractController $output .= "اتصال به مخزن جدید با موفقیت برقرار شد\n"; // دریافت اطلاعات مخزن جدید - $fetchProcess = new Process(['git', 'fetch', 'origin'], $gitRoot); - $fetchProcess->setTimeout(7200); - $fetchProcess->run(); + $fetchProcess = $this->runGitCommand(['git', 'fetch', 'origin'], $gitRoot); if (!$fetchProcess->isSuccessful()) { $output .= "هشدار: خطا در دریافت اطلاعات از مخزن جدید: " . $fetchProcess->getErrorOutput() . "\n"; @@ -564,9 +678,7 @@ final class UpdateCoreController extends AbstractController } // بررسی branch های موجود - $branchProcess = new Process(['git', 'branch', '-r'], $gitRoot); - $branchProcess->setTimeout(7200); - $branchProcess->run(); + $branchProcess = $this->runGitCommand(['git', 'branch', '-r'], $gitRoot); if ($branchProcess->isSuccessful()) { $branches = trim($branchProcess->getOutput()); @@ -578,9 +690,7 @@ final class UpdateCoreController extends AbstractController } // پاک کردن کش Git - $cleanProcess = new Process(['git', 'gc', '--prune=now'], $gitRoot); - $cleanProcess->setTimeout(7200); - $cleanProcess->run(); + $cleanProcess = $this->runGitCommand(['git', 'gc', '--prune=now'], $gitRoot); if ($cleanProcess->isSuccessful()) { $output .= "کش Git پاک شد\n"; diff --git a/webUI/src/views/user/manager/settings/update-core.vue b/webUI/src/views/user/manager/settings/update-core.vue index 5755059..bc9e47e 100755 --- a/webUI/src/views/user/manager/settings/update-core.vue +++ b/webUI/src/views/user/manager/settings/update-core.vue @@ -112,17 +112,17 @@ - {{ buttonText }} {{ $t('updateSoftware.clearCacheButton') }} - {{ $t('updateSoftware.changeEnvButton') }} @@ -134,11 +134,28 @@ {{ $t('updateSoftware.progressTitle') }} + + mdi-sync + در حال به‌روزرسانی... + + + mdi-refresh + + + mdi-reload + + + mdi-play + mdi-content-copy + + mdi-information + اگر وضعیت به‌روزرسانی نمایش داده نمی‌شود، روی دکمه refresh کلیک کنید +

                                 
@@ -191,8 +208,8 @@ import { ref, onMounted, onUnmounted, nextTick } from 'vue'; import axios from 'axios'; -// تنظیم تایم‌اوت Axios به 2 ساعت (7200000 میلی‌ثانیه) -axios.defaults.timeout = 7200000; +// تنظیم تایم‌اوت Axios به 5 دقیقه (300000 میلی‌ثانیه) +axios.defaults.timeout = 300000; export default { name: 'UpdateSoftware', @@ -234,6 +251,9 @@ export default { const isLoadingLogs = ref(false); const isClearingLogs = ref(false); const isPolling = ref(false); + const isStreaming = ref(false); + const isCheckingStatus = ref(false); + const isRunningManually = ref(false); const updateSourceUrl = ref(''); const isChangingSource = ref(false); @@ -264,6 +284,9 @@ export default { isLoadingLogs, isClearingLogs, isPolling, + isStreaming, + isCheckingStatus, + isRunningManually, updateSourceUrl, isChangingSource, }; @@ -327,110 +350,414 @@ export default { async startUpdate() { if (this.isUpdating || this.status === 'running') return; + console.log('=== شروع فرآیند به‌روزرسانی ==='); + console.log('وضعیت فعلی:', { isUpdating: this.isUpdating, status: this.status }); + this.isUpdating = true; - this.buttonText = this.$t('updateSoftware.updatingButton'); + this.isStreaming = false; + this.buttonText = 'در حال اجرا...'; this.buttonColor = 'primary'; this.showOutput = true; this.output = this.$t('updateSoftware.startingMessage') + '\n'; + + console.log('درخواست شروع به‌روزرسانی ارسال می‌شود...'); try { + console.log('ارسال درخواست POST به /api/admin/updatecore/run'); const response = await axios.post('/api/admin/updatecore/run', {}, { headers: { 'X-Requested-With': 'XMLHttpRequest' }, - timeout: 7200000 // تایم‌اوت 2 ساعته برای درخواست اولیه + timeout: 120000 // افزایش timeout به 2 دقیقه برای اجرای کامل }); + console.log('پاسخ دریافتی:', response.data); + if (response.data.status === 'started') { this.updateUuid = response.data.uuid; - this.output += this.$t('updateSoftware.startedMessage') + '\n'; - this.startLogStream(); + console.log('UUID دریافت شد:', this.updateUuid); + + // اگر خروجی در پاسخ وجود دارد، آن را نمایش بده + if (response.data.output) { + this.output = response.data.output; + console.log('خروجی مستقیم دریافت شد:', response.data.output); + } else { + this.output += this.$t('updateSoftware.startedMessage') + '\n'; + console.log('شروع polling برای فرآیند طولانی...'); + this.startLogStream(); + } + + // بررسی اینکه آیا فرآیند تکمیل شده + if (response.data.output && ( + response.data.output.includes('Software is already up to date') || + response.data.output.includes('No update needed') || + response.data.output.includes('completed successfully') + )) { + console.log('فرآیند تکمیل شده - توقف polling'); + this.status = 'success'; + this.isPolling = false; + this.isStreaming = false; + this.isUpdating = false; + this.buttonText = this.$t('updateSoftware.completedButton'); + this.buttonColor = 'success'; + + // نمایش پیام مناسب + if (response.data.output.includes('Software is already up to date')) { + this.showResultDialog = true; + this.dialogTitle = 'اطلاعیه'; + this.dialogMessage = 'نرم‌افزار در حال حاضر به‌روز است. هیچ به‌روزرسانی جدیدی موجود نیست.'; + this.dialogColor = 'info'; + } else { + this.showResultDialog = true; + this.dialogTitle = this.$t('updateSoftware.dialogSuccessTitle'); + this.dialogMessage = 'به‌روزرسانی با موفقیت انجام شد'; + this.dialogColor = 'success'; + } + } else { + // شروع polling برای فرآیندهای طولانی + this.startLogStream(); + } } else if (response.data.status === 'error') { + console.log('خطا در پاسخ:', response.data.message); this.output += '\n' + this.$t('updateSoftware.errorPrefix') + response.data.message; this.buttonColor = 'error'; this.buttonText = this.$t('updateSoftware.failedButton'); this.isUpdating = false; this.showResultDialog = true; this.dialogTitle = this.$t('updateSoftware.dialogErrorTitle'); - this.dialogMessage = this.$t('updateSoftware.dialogErrorSimpleMessage'); + this.dialogMessage = response.data.message || this.$t('updateSoftware.dialogErrorSimpleMessage'); this.dialogColor = 'error'; } } catch (error) { + console.log('خطا در درخواست شروع:', error); + console.log('جزئیات خطا:', { + message: error.message, + code: error.code, + response: error.response?.data, + status: error.response?.status + }); + this.output += '\n' + this.$t('updateSoftware.errorPrefix') + (error.response?.data?.message || error.message); this.buttonColor = 'error'; this.buttonText = this.$t('updateSoftware.failedButton'); this.isUpdating = false; this.showResultDialog = true; this.dialogTitle = this.$t('updateSoftware.dialogErrorTitle'); - this.dialogMessage = this.$t('updateSoftware.dialogErrorSimpleMessage'); + this.dialogMessage = error.response?.data?.message || error.message || this.$t('updateSoftware.dialogErrorSimpleMessage'); this.dialogColor = 'error'; } }, async startLogStream() { - const pollInterval = 2000; // افزایش فاصله polling به 2 ثانیه + console.log('=== شروع polling ==='); + console.log('UUID برای polling:', this.updateUuid); + + const pollInterval = 2000; // فاصله polling 2 ثانیه + const maxPollingTime = 300000; // حداکثر 5 دقیقه polling + const startTime = Date.now(); + this.isPolling = true; + this.isStreaming = true; this.isUpdating = true; + let retryCount = 0; + const maxRetries = 10; const pollStream = async () => { - if (!this.isPolling) return; + if (!this.isPolling) { + console.log('Polling متوقف شد'); + return; + } + + // بررسی timeout + const elapsedTime = Date.now() - startTime; + if (elapsedTime > maxPollingTime) { + console.log('زمان polling تمام شد'); + this.output += '\nزمان انتظار تمام شد. فرآیند به‌روزرسانی احتمالاً تکمیل شده است.\n'; + this.isPolling = false; + this.isStreaming = false; + this.isUpdating = false; + this.buttonText = this.$t('updateSoftware.completedButton'); + this.buttonColor = 'success'; + + this.showResultDialog = true; + this.dialogTitle = 'اطلاعیه'; + this.dialogMessage = 'زمان انتظار تمام شد. فرآیند به‌روزرسانی احتمالاً تکمیل شده است.'; + this.dialogColor = 'info'; + return; + } + + console.log(`--- Polling attempt ${retryCount + 1} --- (${Math.round(elapsedTime/1000)}s elapsed)`); + console.log('ارسال درخواست GET به /api/admin/updatecore/stream با UUID:', this.updateUuid); try { const response = await axios.get(`/api/admin/updatecore/stream`, { params: { uuid: this.updateUuid }, - timeout: 7200000 // تایم‌اوت 2 ساعته برای استریم + timeout: 10000 // کاهش timeout برای polling }); + console.log('پاسخ polling دریافتی:', response.data); const data = response.data; - if (typeof data === 'string' && data.startsWith('data: ')) { - try { - const jsonStr = data.substring(data.indexOf('{')); - const parsedData = JSON.parse(jsonStr); - - if (parsedData.output && parsedData.output !== this.output) { - this.output = parsedData.output; - } - - this.status = parsedData.status; - } catch (parseError) { - console.error('خطا در پردازش پاسخ:', parseError); - } + retryCount = 0; // reset retry count on success + + console.log('وضعیت دریافتی:', data.status); + console.log('خروجی قبلی:', this.output); + console.log('خروجی جدید:', data.output); + + if (data.output && data.output !== this.output) { + console.log('خروجی به‌روزرسانی شد'); + this.output = data.output; + } else { + console.log('خروجی تغییری نکرده'); } + this.status = data.status; + await nextTick(); if (this.$refs.outputPre) { this.$refs.outputPre.scrollTop = this.$refs.outputPre.scrollHeight; } if (this.status === 'success' || this.status === 'error') { + console.log('فرآیند تکمیل شد. وضعیت:', this.status); this.isPolling = false; + this.isStreaming = false; this.isUpdating = false; this.buttonText = this.status === 'success' ? this.$t('updateSoftware.completedButton') : this.$t('updateSoftware.failedButton'); this.buttonColor = this.status === 'success' ? 'success' : 'error'; + this.showResultDialog = true; this.dialogTitle = this.status === 'success' ? this.$t('updateSoftware.dialogSuccessTitle') : this.$t('updateSoftware.dialogErrorTitle'); this.dialogMessage = this.status === 'success' ? this.$t('updateSoftware.successMessage') - : this.$t('updateSoftware.dialogErrorSimpleMessage'); + : (data.error || this.$t('updateSoftware.dialogErrorSimpleMessage')); this.dialogColor = this.status === 'success' ? 'success' : 'error'; return; } + console.log(`انتظار ${pollInterval}ms برای polling بعدی...`); setTimeout(pollStream, pollInterval); } catch (error) { - console.error('خطا در دریافت جریان داده:', error); + console.error('خطا در polling:', error); + console.log('جزئیات خطای polling:', { + message: error.message, + code: error.code, + response: error.response?.data, + status: error.response?.status, + retryCount: retryCount + }); + + retryCount++; + + // اگر خطای شبکه است و تعداد تلاش‌ها کم است، دوباره تلاش کن + if ((error.code === 'ECONNABORTED' || error.code === 'NETWORK_ERROR' || !error.response) && retryCount < maxRetries) { + console.log(`تلاش مجدد ${retryCount}/${maxRetries}...`); + this.output += `\nتلاش مجدد برای دریافت وضعیت... (${retryCount}/${maxRetries})\n`; + setTimeout(pollStream, pollInterval * 2); // افزایش فاصله برای retry + return; + } + + console.log('تعداد تلاش‌ها تمام شد یا خطای غیرقابل حل'); this.isPolling = false; + this.isStreaming = false; this.output += '\n' + this.$t('updateSoftware.streamError'); this.isUpdating = false; this.buttonColor = 'error'; this.buttonText = this.$t('updateSoftware.failedButton'); + + // نمایش خطا به کاربر + this.showResultDialog = true; + this.dialogTitle = this.$t('updateSoftware.dialogErrorTitle'); + this.dialogMessage = error.response?.data?.message || error.message || this.$t('updateSoftware.dialogErrorSimpleMessage'); + this.dialogColor = 'error'; } }; pollStream(); }, + async checkStatusManually() { + console.log('=== بررسی دستی وضعیت ==='); + console.log('UUID فعلی:', this.updateUuid); + + if (!this.updateUuid) { + console.log('هیچ UUID موجود نیست'); + this.showResultDialog = true; + this.dialogTitle = this.$t('updateSoftware.dialogErrorTitle'); + this.dialogMessage = 'هیچ فرآیند به‌روزرسانی فعال نیست'; + this.dialogColor = 'error'; + return; + } + + this.isCheckingStatus = true; + console.log('ارسال درخواست بررسی وضعیت...'); + + try { + const response = await axios.get(`/api/admin/updatecore/stream`, { + params: { uuid: this.updateUuid }, + timeout: 10000 + }); + + console.log('پاسخ بررسی وضعیت:', response.data); + const data = response.data; + + console.log('وضعیت دریافتی:', data.status); + console.log('خروجی دریافتی:', data.output); + + if (data.output && data.output !== this.output) { + console.log('خروجی به‌روزرسانی شد'); + this.output = data.output; + } else { + console.log('خروجی تغییری نکرده'); + } + + this.status = data.status; + + await nextTick(); + if (this.$refs.outputPre) { + this.$refs.outputPre.scrollTop = this.$refs.outputPre.scrollHeight; + } + + if (this.status === 'success' || this.status === 'error') { + console.log('فرآیند تکمیل شد. وضعیت:', this.status); + this.isPolling = false; + this.isStreaming = false; + this.isUpdating = false; + this.buttonText = this.status === 'success' + ? this.$t('updateSoftware.completedButton') + : this.$t('updateSoftware.failedButton'); + this.buttonColor = this.status === 'success' ? 'success' : 'error'; + + this.showResultDialog = true; + this.dialogTitle = this.status === 'success' + ? this.$t('updateSoftware.dialogSuccessTitle') + : this.$t('updateSoftware.dialogErrorTitle'); + this.dialogMessage = this.status === 'success' + ? this.$t('updateSoftware.successMessage') + : (data.error || this.$t('updateSoftware.dialogErrorSimpleMessage')); + this.dialogColor = this.status === 'success' ? 'success' : 'error'; + } else { + console.log('فرآیند هنوز در حال اجرا است'); + this.showResultDialog = true; + this.dialogTitle = 'وضعیت فعلی'; + this.dialogMessage = 'فرآیند به‌روزرسانی هنوز در حال اجرا است'; + this.dialogColor = 'info'; + } + + } catch (error) { + console.error('خطا در بررسی دستی وضعیت:', error); + console.log('جزئیات خطای بررسی دستی:', { + message: error.message, + code: error.code, + response: error.response?.data, + status: error.response?.status + }); + + // اگر خطای شبکه است، وضعیت فایل state را نمایش بده + if (!error.response) { + console.log('خطای شبکه - نمایش وضعیت محلی'); + this.output += '\nخطا در اتصال به سرور. وضعیت فعلی:\n'; + this.output += `UUID: ${this.updateUuid}\n`; + this.output += 'فرآیند به‌روزرسانی احتمالاً تکمیل شده است.\n'; + this.output += 'لطفاً صفحه را refresh کنید.\n'; + + this.isPolling = false; + this.isStreaming = false; + this.isUpdating = false; + this.buttonText = this.$t('updateSoftware.completedButton'); + this.buttonColor = 'success'; + + this.showResultDialog = true; + this.dialogTitle = 'اطلاعیه'; + this.dialogMessage = 'فرآیند به‌روزرسانی احتمالاً تکمیل شده است. لطفاً صفحه را refresh کنید.'; + this.dialogColor = 'info'; + } else { + console.log('خطای HTTP - نمایش پیام خطا'); + this.showResultDialog = true; + this.dialogTitle = this.$t('updateSoftware.dialogErrorTitle'); + this.dialogMessage = error.response?.data?.message || error.message || 'خطا در بررسی وضعیت'; + this.dialogColor = 'error'; + } + } finally { + this.isCheckingStatus = false; + console.log('=== پایان بررسی دستی وضعیت ==='); + } + }, + refreshPage() { + console.log('=== Refresh page ==='); + console.log('وضعیت فعلی قبل از refresh:', { + isUpdating: this.isUpdating, + isStreaming: this.isStreaming, + isPolling: this.isPolling, + status: this.status, + updateUuid: this.updateUuid + }); + window.location.reload(); + }, + async runUpdateManually() { + console.log('=== اجرای دستی به‌روزرسانی ==='); + + if (!this.updateUuid) { + this.showResultDialog = true; + this.dialogTitle = this.$t('updateSoftware.dialogErrorTitle'); + this.dialogMessage = 'هیچ فرآیند به‌روزرسانی فعال نیست'; + this.dialogColor = 'error'; + return; + } + + this.isRunningManually = true; + this.output += '\n=== اجرای دستی به‌روزرسانی ===\n'; + + try { + // اجرای command به صورت دستی + const response = await axios.post('/api/admin/updatecore/run-manual', { + uuid: this.updateUuid + }, { + headers: { 'X-Requested-With': 'XMLHttpRequest' }, + timeout: 60000 + }); + + console.log('پاسخ اجرای دستی:', response.data); + this.output += response.data.output || response.data.message + '\n'; + + if (response.data.status === 'success') { + this.status = 'success'; + this.isPolling = false; + this.isStreaming = false; + this.isUpdating = false; + this.buttonText = this.$t('updateSoftware.completedButton'); + this.buttonColor = 'success'; + + // بررسی اینکه آیا نرم‌افزار به‌روز است + if (response.data.output && response.data.output.includes('Software is already up to date')) { + this.showResultDialog = true; + this.dialogTitle = 'اطلاعیه'; + this.dialogMessage = 'نرم‌افزار در حال حاضر به‌روز است. هیچ به‌روزرسانی جدیدی موجود نیست.'; + this.dialogColor = 'info'; + } else { + this.showResultDialog = true; + this.dialogTitle = this.$t('updateSoftware.dialogSuccessTitle'); + this.dialogMessage = response.data.message || 'به‌روزرسانی با موفقیت انجام شد'; + this.dialogColor = 'success'; + } + } else { + this.showResultDialog = true; + this.dialogTitle = this.$t('updateSoftware.dialogErrorTitle'); + this.dialogMessage = response.data.message || 'خطا در اجرای دستی'; + this.dialogColor = 'error'; + } + + } catch (error) { + console.error('خطا در اجرای دستی:', 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 || error.message || 'خطا در اجرای دستی'; + this.dialogColor = 'error'; + } finally { + this.isRunningManually = false; + } + }, async clearCache() { this.isClearingCache = true; this.showOutput = true; @@ -439,7 +766,7 @@ export default { try { const response = await axios.post('/api/admin/updatecore/clear-cache', {}, { headers: { 'X-Requested-With': 'XMLHttpRequest' }, - timeout: 7200000 // تایم‌اوت 2 ساعته + timeout: 60000 // کاهش timeout به 1 دقیقه }); this.output += response.data.output || this.$t('updateSoftware.cacheClearedMessage') + '\n'; this.showResultDialog = true; @@ -478,7 +805,7 @@ export default { try { const response = await axios.post('/api/admin/updatecore/change-env', { env: this.tempSelectedEnv }, { headers: { 'X-Requested-With': 'XMLHttpRequest' }, - timeout: 7200000 // تایم‌اوت 2 ساعته + timeout: 300000 // کاهش timeout به 5 دقیقه }); this.output += response.data.output || response.data.message + '\n'; this.selectedEnv = this.tempSelectedEnv; @@ -490,7 +817,7 @@ export default { this.output += 'خطا: ' + (error.response?.data?.message || error.message) + '\n'; this.showResultDialog = true; this.dialogTitle = 'خطا'; - this.dialogMessage = 'خطایی در تغییر حالت رخ داد'; + this.dialogMessage = error.response?.data?.message || error.message || 'خطایی در تغییر حالت رخ داد'; this.dialogColor = 'error'; } finally { this.isChangingEnv = false; @@ -501,7 +828,7 @@ export default { try { const response = await axios.get('/api/admin/updatecore/commits', { headers: { 'X-Requested-With': 'XMLHttpRequest' }, - timeout: 7200000 // تایم‌اوت 2 ساعته + timeout: 30000 // کاهش timeout به 30 ثانیه }); this.currentCommit = response.data.currentCommit || 'unknown'; this.targetCommit = response.data.targetCommit || 'unknown'; @@ -515,7 +842,7 @@ export default { try { const response = await axios.get('/api/admin/updatecore/system-info', { headers: { 'X-Requested-With': 'XMLHttpRequest' }, - timeout: 7200000 // تایم‌اوت 2 ساعته + timeout: 30000 // کاهش timeout به 30 ثانیه }); this.systemInfo = { osName: response.data.osName || 'unknown', @@ -549,7 +876,7 @@ export default { try { const response = await axios.get('/api/admin/updatecore/current-source', { headers: { 'X-Requested-With': 'XMLHttpRequest' }, - timeout: 7200000 // تایم‌اوت 2 ساعته + timeout: 30000 // کاهش timeout به 30 ثانیه }); if (response.data.status === 'success') { @@ -575,7 +902,7 @@ export default { try { const response = await axios.get('/api/admin/updatecore/current-env', { headers: { 'X-Requested-With': 'XMLHttpRequest' }, - timeout: 7200000 // تایم‌اوت 2 ساعته + timeout: 30000 // کاهش timeout به 30 ثانیه }); this.selectedEnv = response.data.env; this.tempSelectedEnv = response.data.env; @@ -596,7 +923,7 @@ export default { try { const response = await axios.get('/api/admin/updatecore/system-logs', { headers: { 'X-Requested-With': 'XMLHttpRequest' }, - timeout: 7200000 // تایم‌اوت 2 ساعته + timeout: 30000 // کاهش timeout به 30 ثانیه }); this.systemLogs = response.data.logs || response.data.message; } catch (error) { @@ -611,7 +938,7 @@ export default { try { const response = await axios.post('/api/admin/updatecore/clear-logs', {}, { headers: { 'X-Requested-With': 'XMLHttpRequest' }, - timeout: 7200000 // تایم‌اوت 2 ساعته + timeout: 30000 // کاهش timeout به 30 ثانیه }); if (response.data.status === 'success') { this.systemLogs = 'لاگ‌ها پاک شدند'; @@ -651,7 +978,7 @@ export default { sourceUrl: this.updateSourceUrl.trim() }, { headers: { 'X-Requested-With': 'XMLHttpRequest' }, - timeout: 7200000 // تایم‌اوت 2 ساعته + timeout: 120000 // کاهش timeout به 2 دقیقه }); this.output += response.data.output || response.data.message + '\n'; @@ -689,15 +1016,25 @@ export default { }, }, mounted() { + console.log('=== Component mounted ==='); + console.log('شروع بارگذاری اطلاعات اولیه...'); + this.fetchCommits(); this.fetchSystemInfo(); this.fetchCurrentEnv(); this.fetchCurrentSource(); this.buttonText = this.$t('updateSoftware.startButton'); this.refreshLogs(); + + console.log('بارگذاری اطلاعات اولیه تکمیل شد'); }, beforeUnmount() { + console.log('=== Component unmounting ==='); + console.log('متوقف کردن polling و streaming...'); this.isPolling = false; + this.isStreaming = false; + this.isUpdating = false; + console.log('Component cleanup completed'); }, };