bug fix in automatic update

This commit is contained in:
Hesabix 2025-08-17 11:26:24 +00:00
parent 789618927d
commit 2f144c0d9d
3 changed files with 665 additions and 103 deletions

View file

@ -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, "<comment>Attempt $attempt failed: $errorMessage</comment>");
// If the command failed with "dubious ownership" error, try to fix it
if (str_contains($errorMessage, 'dubious ownership')) {
$this->writeOutput($output, "<comment>Detected Git ownership issue, attempting to fix...</comment>");
if ($this->fixGitOwnershipIssue($workingDir)) {
$this->writeOutput($output, "<info>Git ownership issue fixed, retrying command...</info>");
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, '<info>Git ownership issue detected and fixed.</info>');
} else {
$this->writeOutput($output, '<info>Git repository ownership is correct.</info>');
}
}
$this->writeOutput($output, 'Pre-update checks completed successfully.');
}

View file

@ -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');
$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);
}
});
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";

View file

@ -112,17 +112,17 @@
<v-row justify="end" class="mb-4">
<v-col cols="auto">
<v-btn-group size="small">
<v-btn :color="buttonColor" :loading="isUpdating"
:disabled="isUpdating || status === 'running'"
<v-btn :color="buttonColor" :loading="isUpdating || isStreaming"
:disabled="isUpdating || isStreaming || status === 'running'"
@click="startUpdate">
{{ buttonText }}
</v-btn>
<v-btn color="warning" :loading="isClearingCache"
:disabled="isUpdating || isClearingCache"
:disabled="isUpdating || isStreaming || isClearingCache"
@click="clearCache">
{{ $t('updateSoftware.clearCacheButton') }}
</v-btn>
<v-btn color="info" :disabled="isUpdating || isChangingEnv"
<v-btn color="info" :loading="isChangingEnv" :disabled="isUpdating || isStreaming || isChangingEnv"
@click="openEnvDialog">
{{ $t('updateSoftware.changeEnvButton') }}
</v-btn>
@ -134,11 +134,28 @@
<v-toolbar flat color="white" density="compact">
<v-toolbar-title>{{ $t('updateSoftware.progressTitle') }}</v-toolbar-title>
<v-spacer></v-spacer>
<v-chip v-if="isStreaming" color="primary" size="small" class="mr-2">
<v-icon left size="small">mdi-sync</v-icon>
در حال بهروزرسانی...
</v-chip>
<v-btn icon size="small" @click="checkStatusManually" class="mr-1" :loading="isCheckingStatus">
<v-icon>mdi-refresh</v-icon>
</v-btn>
<v-btn icon size="small" @click="refreshPage" class="mr-1" title="Refresh صفحه">
<v-icon>mdi-reload</v-icon>
</v-btn>
<v-btn icon size="small" @click="runUpdateManually" class="mr-1" title="اجرای دستی به‌روزرسانی" :loading="isRunningManually">
<v-icon>mdi-play</v-icon>
</v-btn>
<v-btn icon size="small" @click="copyToClipboard">
<v-icon>mdi-content-copy</v-icon>
</v-btn>
</v-toolbar>
<v-card-text>
<v-alert v-if="isStreaming && !isPolling" type="info" dense class="mb-2">
<v-icon left size="small">mdi-information</v-icon>
اگر وضعیت بهروزرسانی نمایش داده نمیشود، روی دکمه refresh کلیک کنید
</v-alert>
<pre class="output-pre" ref="outputPre" v-html="formattedOutput"></pre>
</v-card-text>
</v-card>
@ -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);
retryCount = 0; // reset retry count on success
if (parsedData.output && parsedData.output !== this.output) {
this.output = parsedData.output;
}
console.log('وضعیت دریافتی:', data.status);
console.log('خروجی قبلی:', this.output);
console.log('خروجی جدید:', data.output);
this.status = parsedData.status;
} catch (parseError) {
console.error('خطا در پردازش پاسخ:', parseError);
}
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');
},
};
</script>