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');
},
};