support for change repositories

This commit is contained in:
Hesabix 2025-07-12 11:19:24 +00:00
parent 0d18762f1e
commit b1f8af83f4
3 changed files with 293 additions and 8 deletions

View file

@ -23,8 +23,9 @@ final class UpdateCoreController extends AbstractController
public function api_admin_updatecore_run(): JsonResponse
{
$projectDir = $this->getParameter('kernel.project_dir');
$gitRoot = dirname($projectDir); // رفتن به ریشه پروژه
$uuid = uniqid();
$stateFile = $projectDir . '/../hesabixBackup/update_state_' . $uuid . '.json';
$stateFile = $gitRoot . '/hesabixBackup/update_state_' . $uuid . '.json';
if (!file_exists(dirname($stateFile))) {
mkdir(dirname($stateFile), 0755, true);
@ -41,7 +42,7 @@ final class UpdateCoreController extends AbstractController
'COMPOSER_HOME' => '/var/www/.composer',
]);
$process = new Process(['php', 'bin/console', 'hesabix:update', $stateFile], $projectDir, $env);
$process = new Process(['php', 'hesabixCore/bin/console', 'hesabix:update', $stateFile], $gitRoot, $env);
$process->setTimeout(7200); // افزایش تایم‌اوت به 2 ساعت
$process->start(function ($type, $buffer) use ($stateFile) {
$state = json_decode(file_get_contents($stateFile), true) ?? ['uuid' => uniqid(), 'log' => ''];
@ -70,7 +71,9 @@ final class UpdateCoreController extends AbstractController
], 400);
}
$stateFile = $this->getParameter('kernel.project_dir') . '/../hesabixBackup/update_state_' . $uuid . '.json';
$projectDir = $this->getParameter('kernel.project_dir');
$gitRoot = dirname($projectDir);
$stateFile = $gitRoot . '/hesabixBackup/update_state_' . $uuid . '.json';
if (!file_exists($stateFile)) {
return new JsonResponse([
@ -97,7 +100,7 @@ final class UpdateCoreController extends AbstractController
}
if (!$isRunning) {
$backupDir = $this->getParameter('kernel.project_dir') . '/../hesabixBackup';
$backupDir = $gitRoot . '/hesabixBackup';
$stateFiles = glob($backupDir . '/update_state_*.json');
foreach ($stateFiles as $file) {
if (is_file($file)) {
@ -128,7 +131,9 @@ final class UpdateCoreController extends AbstractController
return new JsonResponse(['status' => 'error', 'message' => 'UUID is required'], 400);
}
$stateFile = $this->getParameter('kernel.project_dir') . '/../hesabixBackup/update_state_' . $uuid . '.json';
$projectDir = $this->getParameter('kernel.project_dir');
$gitRoot = dirname($projectDir);
$stateFile = $gitRoot . '/hesabixBackup/update_state_' . $uuid . '.json';
return new StreamedResponse(function () use ($stateFile) {
header('Content-Type: text/event-stream');
@ -167,13 +172,14 @@ final class UpdateCoreController extends AbstractController
public function api_admin_updatecore_commits(): JsonResponse
{
$projectDir = $this->getParameter('kernel.project_dir');
$gitRoot = dirname($projectDir); // رفتن به ریشه پروژه
$currentProcess = new Process(['git', 'rev-parse', 'HEAD'], $projectDir);
$currentProcess = new Process(['git', 'rev-parse', 'HEAD'], $gitRoot);
$currentProcess->setTimeout(7200); // افزایش تایم‌اوت
$currentProcess->run();
$currentCommit = $currentProcess->isSuccessful() ? trim($currentProcess->getOutput()) : 'unknown';
$targetProcess = new Process(['git', 'ls-remote', 'origin', 'HEAD'], $projectDir);
$targetProcess = new Process(['git', 'ls-remote', 'origin', 'HEAD'], $gitRoot);
$targetProcess->setTimeout(7200); // افزایش تایم‌اوت
$targetProcess->run();
$targetOutput = $targetProcess->isSuccessful() ? explode("\t", trim($targetProcess->getOutput()))[0] : 'unknown';
@ -428,4 +434,170 @@ final class UpdateCoreController extends AbstractController
], 500);
}
}
#[Route('/api/admin/updatecore/current-source', name: 'api_admin_updatecore_current_source', methods: ['GET'])]
public function api_admin_updatecore_current_source(): JsonResponse
{
$projectDir = $this->getParameter('kernel.project_dir');
$gitRoot = dirname($projectDir); // رفتن به ریشه پروژه
$output = '';
try {
// بررسی اینکه آیا پروژه یک مخزن Git است
if (!is_dir($gitRoot . '/.git')) {
return new JsonResponse([
'status' => 'error',
'message' => 'این پروژه یک مخزن Git نیست',
'sourceUrl' => '',
], 400);
}
// دریافت آدرس مخزن origin فعلی
$process = new Process(['git', 'remote', 'get-url', 'origin'], $gitRoot);
$process->setTimeout(7200); // افزایش تایم‌اوت
$process->run();
if (!$process->isSuccessful()) {
return new JsonResponse([
'status' => 'error',
'message' => 'خطا در دریافت آدرس مخزن: ' . $process->getErrorOutput(),
'sourceUrl' => '',
], 500);
}
$sourceUrl = trim($process->getOutput());
$output .= "آدرس مخزن فعلی: $sourceUrl\n";
return new JsonResponse([
'status' => 'success',
'sourceUrl' => $sourceUrl,
'output' => $output,
]);
} catch (\Exception $e) {
return new JsonResponse([
'status' => 'error',
'message' => 'خطا در بررسی مخزن: ' . $e->getMessage(),
'sourceUrl' => '',
], 500);
}
}
#[Route('/api/admin/updatecore/change-source', name: 'api_admin_updatecore_change_source', methods: ['POST'])]
public function api_admin_updatecore_change_source(Request $request): JsonResponse
{
$sourceUrl = $request->getPayload()->get('sourceUrl');
$output = '';
if (!$sourceUrl || !filter_var($sourceUrl, FILTER_VALIDATE_URL) && !preg_match('/^git@[^:]+:[^\/]+\/[^\/]+\.git$/', $sourceUrl)) {
return new JsonResponse([
'status' => 'error',
'message' => 'آدرس مخزن نامعتبر است. لطفاً یک آدرس HTTP یا SSH معتبر وارد کنید.',
'output' => $output,
], 400);
}
$projectDir = $this->getParameter('kernel.project_dir');
$gitRoot = dirname($projectDir); // رفتن به ریشه پروژه
try {
// بررسی اینکه آیا پروژه یک مخزن Git است
if (!is_dir($gitRoot . '/.git')) {
return new JsonResponse([
'status' => 'error',
'message' => 'این پروژه یک مخزن Git نیست',
'output' => $output,
], 400);
}
$output .= "شروع تغییر آدرس مخزن...\n";
// دریافت آدرس مخزن فعلی
$currentProcess = new Process(['git', 'remote', 'get-url', 'origin'], $gitRoot);
$currentProcess->setTimeout(7200);
$currentProcess->run();
$currentUrl = $currentProcess->isSuccessful() ? trim($currentProcess->getOutput()) : '';
if ($currentUrl) {
$output .= "آدرس مخزن فعلی: $currentUrl\n";
}
// تغییر آدرس مخزن origin
$changeProcess = new Process(['git', 'remote', 'set-url', 'origin', $sourceUrl], $gitRoot);
$changeProcess->setTimeout(7200);
$changeProcess->run();
if (!$changeProcess->isSuccessful()) {
return new JsonResponse([
'status' => 'error',
'message' => 'خطا در تغییر آدرس مخزن: ' . $changeProcess->getErrorOutput(),
'output' => $output,
], 500);
}
$output .= "آدرس مخزن به $sourceUrl تغییر یافت\n";
// بررسی اتصال به مخزن جدید
$testProcess = new Process(['git', 'remote', 'show', 'origin'], $gitRoot);
$testProcess->setTimeout(7200);
$testProcess->run();
if (!$testProcess->isSuccessful()) {
return new JsonResponse([
'status' => 'error',
'message' => 'خطا در اتصال به مخزن جدید: ' . $testProcess->getErrorOutput(),
'output' => $output,
], 500);
}
$output .= "اتصال به مخزن جدید با موفقیت برقرار شد\n";
// دریافت اطلاعات مخزن جدید
$fetchProcess = new Process(['git', 'fetch', 'origin'], $gitRoot);
$fetchProcess->setTimeout(7200);
$fetchProcess->run();
if (!$fetchProcess->isSuccessful()) {
$output .= "هشدار: خطا در دریافت اطلاعات از مخزن جدید: " . $fetchProcess->getErrorOutput() . "\n";
} else {
$output .= "اطلاعات مخزن جدید با موفقیت دریافت شد\n";
}
// بررسی branch های موجود
$branchProcess = new Process(['git', 'branch', '-r'], $gitRoot);
$branchProcess->setTimeout(7200);
$branchProcess->run();
if ($branchProcess->isSuccessful()) {
$branches = trim($branchProcess->getOutput());
if ($branches) {
$output .= "شاخه‌های موجود در مخزن جدید:\n$branches\n";
} else {
$output .= "هیچ شاخه‌ای در مخزن جدید یافت نشد\n";
}
}
// پاک کردن کش Git
$cleanProcess = new Process(['git', 'gc', '--prune=now'], $gitRoot);
$cleanProcess->setTimeout(7200);
$cleanProcess->run();
if ($cleanProcess->isSuccessful()) {
$output .= "کش Git پاک شد\n";
}
return new JsonResponse([
'status' => 'success',
'message' => 'آدرس مخزن با موفقیت تغییر یافت و اتصال برقرار شد',
'output' => $output,
]);
} catch (\Exception $e) {
return new JsonResponse([
'status' => 'error',
'message' => 'خطا در تغییر آدرس مخزن: ' . $e->getMessage(),
'output' => $output,
], 500);
}
}
}

View file

@ -268,6 +268,19 @@ const fa_lang = {
"fetchError": "خطا در دریافت",
"cancel": "لغو",
"confirm": "تأیید",
"updateSourceTitle": "منبع به‌روزرسانی",
"updateSourceLabel": "آدرس منبع به‌روزرسانی",
"changeSourceButton": "تغییر منبع",
"sourceUrlRequired": "لطفاً آدرس منبع به‌روزرسانی را وارد کنید",
"changingSourceMessage": "در حال تغییر منبع به‌روزرسانی...",
"sourceChangeSuccess": "منبع به‌روزرسانی با موفقیت تغییر یافت",
"sourceChangeError": "خطایی در تغییر منبع به‌روزرسانی رخ داد",
"notGitRepository": "این پروژه یک مخزن Git نیست",
"invalidRepositoryUrl": "آدرس مخزن نامعتبر است. لطفاً یک آدرس HTTP یا SSH معتبر وارد کنید.",
"repositoryUrlError": "خطا در دریافت آدرس مخزن",
"repositoryChangeError": "خطا در تغییر آدرس مخزن",
"repositoryConnectionError": "خطا در اتصال به مخزن جدید",
"repositoryChangeSuccess": "آدرس مخزن با موفقیت تغییر یافت و اتصال برقرار شد",
},
static: {
not_found: "صفحه مورد نظر یافت نشد",

View file

@ -31,7 +31,7 @@
<p><span class="font-weight-bold primary--text">{{ $t('updateSoftware.distroVersion') }}:</span> {{ systemInfo.distroVersion }}</p>
<p><span class="font-weight-bold primary--text">{{ $t('updateSoftware.webServer') }}:</span> {{ systemInfo.webServer }}</p>
<p><span class="font-weight-bold primary--text">{{ $t('updateSoftware.dbName') }}:</span> {{ systemInfo.dbName }}</p>
<p><span class="font-weight-bold primary--useStateFiletext">{{ $t('updateSoftware.dbVersion') }}:</span> {{ systemInfo.dbVersion }}</p>
<p><span class="font-weight-bold primary--text">{{ $t('updateSoftware.dbVersion') }}:</span> {{ systemInfo.dbVersion }}</p>
<p><span class="font-weight-bold primary--text">{{ $t('updateSoftware.currentEnv') }}:</span> {{ selectedEnv }}</p>
</v-card-text>
</v-card>
@ -76,6 +76,39 @@
<v-window-item>
<v-card flat>
<v-card-text>
<!-- بخش تنظیمات منبع بهروزرسانی -->
<v-card class="mb-4" variant="outlined">
<v-card-title class="text-subtitle-1">
{{ $t('updateSoftware.updateSourceTitle') }}
</v-card-title>
<v-card-text>
<v-row>
<v-col cols="12" md="8">
<v-text-field
v-model="updateSourceUrl"
:label="$t('updateSoftware.updateSourceLabel')"
placeholder="https://github.com/username/repository.git"
outlined
dense
:disabled="isUpdating || isChangingSource"
:loading="isChangingSource"
></v-text-field>
</v-col>
<v-col cols="12" md="4">
<v-btn
color="secondary"
:loading="isChangingSource"
:disabled="isUpdating || isChangingSource || !updateSourceUrl.trim()"
@click="changeUpdateSource"
block
>
{{ $t('updateSoftware.changeSourceButton') }}
</v-btn>
</v-col>
</v-row>
</v-card-text>
</v-card>
<v-row justify="end" class="mb-4">
<v-col cols="auto">
<v-btn-group size="small">
@ -201,6 +234,8 @@ export default {
const isLoadingLogs = ref(false);
const isClearingLogs = ref(false);
const isPolling = ref(false);
const updateSourceUrl = ref('');
const isChangingSource = ref(false);
return {
isUpdating,
@ -229,6 +264,8 @@ export default {
isLoadingLogs,
isClearingLogs,
isPolling,
updateSourceUrl,
isChangingSource,
};
},
computed: {
@ -508,6 +545,32 @@ export default {
};
}
},
async fetchCurrentSource() {
try {
const response = await axios.get('/api/admin/updatecore/current-source', {
headers: { 'X-Requested-With': 'XMLHttpRequest' },
timeout: 7200000 // تایماوت 2 ساعته
});
if (response.data.status === 'success') {
this.updateSourceUrl = response.data.sourceUrl || '';
} else {
console.error('Failed to fetch current source:', response.data.message);
this.updateSourceUrl = '';
}
} catch (error) {
console.error('Failed to fetch current source:', error);
this.updateSourceUrl = '';
// نمایش پیام خطا به کاربر
if (error.response?.data?.message) {
this.showResultDialog = true;
this.dialogTitle = this.$t('updateSoftware.dialogErrorTitle');
this.dialogMessage = error.response.data.message;
this.dialogColor = 'error';
}
}
},
async fetchCurrentEnv() {
try {
const response = await axios.get('/api/admin/updatecore/current-env', {
@ -570,6 +633,42 @@ export default {
this.isClearingLogs = false;
}
},
async changeUpdateSource() {
if (!this.updateSourceUrl.trim()) {
this.showResultDialog = true;
this.dialogTitle = this.$t('updateSoftware.dialogErrorTitle');
this.dialogMessage = this.$t('updateSoftware.sourceUrlRequired');
this.dialogColor = 'error';
return;
}
this.isChangingSource = true;
this.showOutput = true;
this.output = this.$t('updateSoftware.changingSourceMessage') + '\n';
try {
const response = await axios.post('/api/admin/updatecore/change-source', {
sourceUrl: this.updateSourceUrl.trim()
}, {
headers: { 'X-Requested-With': 'XMLHttpRequest' },
timeout: 7200000 // تایماوت 2 ساعته
});
this.output += response.data.output || response.data.message + '\n';
this.showResultDialog = true;
this.dialogTitle = this.$t('updateSoftware.dialogSuccessTitle');
this.dialogMessage = response.data.message || this.$t('updateSoftware.repositoryChangeSuccess');
this.dialogColor = 'success';
} catch (error) {
this.output += 'خطا: ' + (error.response?.data?.message || error.message) + '\n';
this.showResultDialog = true;
this.dialogTitle = this.$t('updateSoftware.dialogErrorTitle');
this.dialogMessage = error.response?.data?.message || this.$t('updateSoftware.sourceChangeError');
this.dialogColor = 'error';
} finally {
this.isChangingSource = false;
}
},
copyLogsToClipboard() {
const plainLogs = this.systemLogs.replace(/\[\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d+\+\d{2}:\d{2}\]/g, '\n[$&]')
.replace(/\s+\[\]/g, ' []')
@ -593,6 +692,7 @@ export default {
this.fetchCommits();
this.fetchSystemInfo();
this.fetchCurrentEnv();
this.fetchCurrentSource();
this.buttonText = this.$t('updateSoftware.startButton');
this.refreshLogs();
},