billingerp/admin/update/index.php

384 lines
22 KiB
PHP

<?php
session_start();
require_once __DIR__ . '/../includes/db.php';
require_once __DIR__ . '/UpdateManager.php';
// ავტორიზაციის შემოწმება
if (!isset($_SESSION['user_id'])) {
header('Location: ../login.php');
exit;
}
$updateManager = new UpdateManager($pdo);
$message = '';
$messageType = '';
// განახლების გაშვება
if ($_POST['action'] ?? '' === 'run_update') {
$version = $_POST['version'] ?? '';
try {
$result = $updateManager->runUpdate($version);
$message = $result['message'];
$messageType = 'success';
} catch (Exception $e) {
$message = $e->getMessage();
$messageType = 'danger';
}
}
// ყველა განახლების გაშვება
if ($_POST['action'] ?? '' === 'run_all_updates') {
try {
$results = $updateManager->runAllUpdates();
$messages = [];
foreach ($results as $result) {
$messages[] = $result['message'];
}
$message = implode('<br>', $messages);
$messageType = 'success';
} catch (Exception $e) {
$message = $e->getMessage();
$messageType = 'danger';
}
}
// ახალი მიგრაციის შექმნა
if ($_POST['action'] ?? '' === 'create_migration') {
$version = $_POST['new_version'] ?? '';
$description = $_POST['description'] ?? '';
$upSql = $_POST['up_sql'] ?? '';
$downSql = $_POST['down_sql'] ?? '';
// ვალიდაცია
if (empty($version) || empty($description) || empty($upSql)) {
$message = "გთხოვთ შეავსოთ ყველა სავალდებულო ველი.";
$messageType = 'danger';
} elseif (!preg_match('/^\d+\.\d+\.\d+$/', $version)) {
$message = "ვერსია უნდა იყოს X.Y.Z ფორმატში (მაგ: 1.0.3)";
$messageType = 'danger';
} else {
try {
// შევამოწმოთ არ არსებობს თუ არა უკვე ასეთი ვერსია
$existingUpdates = $updateManager->getAvailableUpdates();
foreach ($existingUpdates as $update) {
if ($update['version'] === $version) {
throw new Exception("ვერსია $version უკვე არსებობს");
}
}
// შევამოწმოთ ვერსიების ისტორიაში
$history = $updateManager->getUpdateHistory();
foreach ($history as $record) {
if ($record['version'] === $version) {
throw new Exception("ვერსია $version უკვე გამოყენებულია");
}
}
$result = $updateManager->createMigration($version, $description, $upSql, $downSql);
$message = $result['message'];
$messageType = 'success';
} catch (Exception $e) {
$message = "შეცდომა: " . $e->getMessage();
$messageType = 'danger';
}
}
}
$currentVersion = $updateManager->getCurrentVersion();
$availableUpdates = $updateManager->getAvailableUpdates();
$updateHistory = $updateManager->getUpdateHistory();
?>
<!DOCTYPE html>
<html lang="ka">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>STACK - განახლებების მართვა</title>
<link href="../dist/css/tabler.min.css" rel="stylesheet" />
<style>
.code-block { background: #f8f9fa; padding: 15px; border-radius: 5px; font-family: monospace; margin: 10px 0; }
.version-badge { font-size: 0.875em; }
</style>
</head>
<body>
<div class="page">
<div class="page-wrapper">
<div class="container-xl">
<!-- Header -->
<div class="page-header d-print-none">
<div class="row align-items-center">
<div class="col">
<h2 class="page-title">STACK - განახლებების მართვა</h2>
<div class="text-muted mt-1">
მიმდინარე ვერსია: <span class="badge bg-dark"><?= htmlspecialchars($currentVersion) ?></span>
</div>
</div>
<div class="col-auto">
<a href="../dashboard.php" class="btn btn-outline-primary">
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon icon-tabler icons-tabler-outline icon-tabler-arrow-left">
<path stroke="none" d="M0 0h24v24H0z" fill="none"/>
<path d="M5 12l14 0"/>
<path d="M5 12l6 6"/>
<path d="M5 12l6 -6"/>
</svg>
უკან დაბრუნება
</a>
</div>
</div>
</div>
<!-- Messages -->
<?php if ($message): ?>
<div class="alert alert-<?= $messageType ?> alert-dismissible">
<div class="d-flex">
<div><?= $message ?></div>
</div>
<a class="btn-close" data-bs-dismiss="alert" aria-label="close"></a>
</div>
<?php endif; ?>
<div class="row">
<!-- ხელმისაწვდომი განახლებები -->
<div class="col-md-8">
<div class="card">
<div class="card-header">
<h3 class="card-title">ხელმისაწვდომი განახლებები</h3>
<?php if (count($availableUpdates) > 1): ?>
<div class="card-actions">
<form method="post" style="display: inline;">
<input type="hidden" name="action" value="run_all_updates">
<button type="submit" class="btn btn-success btn-sm" onclick="return confirm('ყველა განახლების გაშვება?')">
ყველას გაშვება
</button>
</form>
</div>
<?php endif; ?>
</div>
<div class="card-body">
<?php if (empty($availableUpdates)): ?>
<div class="text-center text-muted py-4">
<svg xmlns="http://www.w3.org/2000/svg" width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon mb-3 text-green">
<path d="M22 11.08V12a10 10 0 1 1-5.93-9.14"/>
<polyline points="22,4 12,14.01 9,11.01"/>
</svg>
<h3>ყველაფერი განახლებულია!</h3>
<p class="text-muted">ახალი განახლებები ხელმისაწვდომი არ არის.</p>
</div>
<?php else: ?>
<div class="list-group list-group-flush">
<?php foreach ($availableUpdates as $update): ?>
<div class="list-group-item">
<div class="row align-items-center">
<div class="col">
<strong>ვერსია <?= htmlspecialchars($update['version']) ?></strong>
<div class="text-muted"><?= htmlspecialchars($update['description']) ?></div>
<small class="text-muted">ფაილი: <?= htmlspecialchars($update['filename']) ?></small>
</div>
<div class="col-auto">
<form method="post" style="display: inline;">
<input type="hidden" name="action" value="run_update">
<input type="hidden" name="version" value="<?= htmlspecialchars($update['version']) ?>">
<button type="submit" class="btn btn-primary btn-sm" onclick="return confirm('განახლების გაშვება?')">
გაშვება
</button>
</form>
</div>
</div>
</div>
<?php endforeach; ?>
</div>
<?php endif; ?>
</div>
</div>
<!-- განახლებების ისტორია -->
<div class="card mt-4">
<div class="card-header">
<h3 class="card-title">განახლებების ისტორია</h3>
</div>
<div class="card-body">
<?php if (empty($updateHistory)): ?>
<p class="text-muted">განახლებების ისტორია ცარიელია.</p>
<?php else: ?>
<div class="table-responsive">
<table class="table">
<thead>
<tr>
<th>ვერსია</th>
<th>აღწერა</th>
<th>სტატუსი</th>
<th>თარიღი</th>
</tr>
</thead>
<tbody>
<?php foreach ($updateHistory as $history): ?>
<tr>
<td><span class="badge bg-secondary version-badge"><?= htmlspecialchars($history['version']) ?></span></td>
<td><?= htmlspecialchars($history['description']) ?></td>
<td>
<?php
$statusClass = [
'completed' => 'success',
'failed' => 'danger',
'pending' => 'warning'
];
$statusText = [
'completed' => 'დასრულებული',
'failed' => 'ვერ შესრულდა',
'pending' => 'მუშავდება'
];
?>
<span class="badge bg-<?= $statusClass[$history['status']] ?>">
<?= $statusText[$history['status']] ?>
</span>
</td>
<td><?= date('Y-m-d H:i', strtotime($history['executed_at'])) ?></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
</div>
<?php endif; ?>
</div>
</div>
</div>
<!-- ახალი მიგრაციის შექმნა -->
<div class="col-md-4">
<div class="card">
<div class="card-header">
<h3 class="card-title">ახალი მიგრაციის შექმნა</h3>
</div>
<div class="card-body">
<form method="post">
<input type="hidden" name="action" value="create_migration">
<div class="mb-3">
<label class="form-label">ვერსია</label>
<?php
// შემოთავაზებული შემდეგი ვერსია
$parts = explode('.', $currentVersion);
$nextMinor = $parts[0] . '.' . $parts[1] . '.' . ((int)$parts[2] + 1);
$nextMajor = $parts[0] . '.' . ((int)$parts[1] + 1) . '.0';
?>
<input type="text" name="new_version" class="form-control" placeholder="<?= $nextMinor ?>" required>
<small class="form-hint">
ფორმატი: X.Y.Z | შემოთავაზებული:
<a href="#" onclick="document.querySelector('[name=new_version]').value='<?= $nextMinor ?>'; return false;"><?= $nextMinor ?></a> |
<a href="#" onclick="document.querySelector('[name=new_version]').value='<?= $nextMajor ?>'; return false;"><?= $nextMajor ?></a>
</small>
</div>
<div class="mb-3">
<label class="form-label">აღწერა</label>
<input type="text" name="description" class="form-control" placeholder="მაგ: Add new column to users table" required>
</div>
<div class="mb-3">
<label class="form-label">SQL (UP)</label>
<textarea name="up_sql" class="form-control" rows="4" placeholder="ALTER TABLE users ADD COLUMN phone VARCHAR(20);" required></textarea>
<small class="form-hint">განახლების SQL</small>
</div>
<div class="mb-3">
<label class="form-label">SQL (DOWN) - არასავალდებულო</label>
<textarea name="down_sql" class="form-control" rows="4" placeholder="ALTER TABLE users DROP COLUMN phone;"></textarea>
<small class="form-hint">Rollback SQL</small>
</div>
<button type="submit" class="btn btn-primary w-100">მიგრაციის შექმნა</button>
</form>
</div>
</div>
<!-- სასარგებლო ინფორმაცია -->
<div class="card mt-4">
<div class="card-header">
<h3 class="card-title">სასარგებლო ინფორმაცია</h3>
</div>
<div class="card-body">
<div class="mb-3">
<strong>მიგრაციის ფაილების ლოკაცია:</strong>
<div class="code-block">/admin/update/migrations/</div>
</div>
<div class="mb-3">
<strong>Backup ფაილები:</strong>
<div class="code-block">/admin/update/backups/</div>
</div>
<div class="alert alert-info">
<h4>მნიშვნელოვანი!</h4>
<ul class="mb-0">
<li>ყოველი განახლების წინ ავტომატურად იქმნება backup</li>
<li>მიგრაციის ფაილები უნდა იყოს ვერსია_აღწერა.php ფორმატში</li>
<li>ვერსიები ეშვება თანმიმდევრულად</li>
</ul>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script src="../dist/js/tabler.min.js"></script>
<script>
// მიგრაციის ფორმის validation
document.addEventListener('DOMContentLoaded', function() {
const migrationForm = document.querySelector('form[method="post"]');
const versionInput = document.querySelector('input[name="new_version"]');
const descriptionInput = document.querySelector('input[name="description"]');
const upSqlInput = document.querySelector('textarea[name="up_sql"]');
if (migrationForm) {
migrationForm.addEventListener('submit', function(e) {
let errors = [];
// ვერსიის ვალიდაცია
const version = versionInput.value.trim();
if (!version) {
errors.push('ვერსია სავალდებულოა');
} else if (!/^\d+\.\d+\.\d+$/.test(version)) {
errors.push('ვერსია უნდა იყოს X.Y.Z ფორმატში (მაგ: 1.0.3)');
}
// აღწერის ვალიდაცია
if (!descriptionInput.value.trim()) {
errors.push('აღწერა სავალდებულოა');
}
// SQL-ის ვალიდაცია
if (!upSqlInput.value.trim()) {
errors.push('SQL (UP) სავალდებულოა');
}
if (errors.length > 0) {
alert('შეცდომები:\n' + errors.join('\n'));
e.preventDefault();
return false;
}
return confirm('დარწმუნებული ხართ რომ გსურთ მიგრაციის შექმნა?');
});
}
// ვერსიის real-time validation
if (versionInput) {
versionInput.addEventListener('input', function() {
const value = this.value;
if (value && !/^\d+\.\d+\.\d+$/.test(value)) {
this.style.borderColor = '#dc3545';
this.title = 'ვერსია უნდა იყოს X.Y.Z ფორმატში';
} else {
this.style.borderColor = '';
this.title = '';
}
});
}
});
</script>
</body>
</html>