mirror of
https://git.imnavajas.es/jjimenez/safekat.git
synced 2025-07-25 22:52:08 +00:00
Avances
This commit is contained in:
@ -22,7 +22,8 @@ $routes->group('sistema', ['namespace' => 'App\Controllers\Sistema'], function (
|
||||
* Tool
|
||||
*========================**/
|
||||
$routes->get('', 'Backups::index', ['as' => 'backupsList']);
|
||||
$routes->get('/create', 'Backups::create', ['as' => 'backupsCreate']);
|
||||
$routes->get('create', 'Backups::create', ['as' => 'backupsCreate']);
|
||||
$routes->get('delete-local/(:num)', 'Backups::deleteLocal/$1', ['as' => 'backupsDeleteLocal']);
|
||||
$routes->get('restore/(:segment)', 'Backups::restore/$1', ['as' => 'backupsRestore']);
|
||||
|
||||
});
|
||||
|
||||
@ -1,46 +1,122 @@
|
||||
<?php
|
||||
|
||||
namespace App\Controllers\Sistema;
|
||||
|
||||
use App\Controllers\BaseController;
|
||||
use App\Models\Sistema\BackupModel;
|
||||
use phpseclib3\Net\SFTP;
|
||||
use ZipArchive;
|
||||
|
||||
class Backups extends BaseController
|
||||
{
|
||||
protected $backupModel;
|
||||
|
||||
public function __construct()
|
||||
{
|
||||
$this->backupModel = new BackupModel();
|
||||
}
|
||||
|
||||
public function index()
|
||||
{
|
||||
helper('filesystem');
|
||||
// Muestra la vista con la lista de backups
|
||||
$files = directory_map(WRITEPATH . 'backups/', 1);
|
||||
return view('themes/vuexy/form/backups/backupList', ['files' => $files]);
|
||||
|
||||
$entries = $this->backupModel->orderBy('created_at', 'DESC')->findAll();
|
||||
|
||||
$backups = [];
|
||||
foreach ($entries as $entry) {
|
||||
$file = $entry['filename'];
|
||||
$localPath = $entry['path_local'];
|
||||
$remotePath = $entry['path_remote'];
|
||||
|
||||
$isLocal = $localPath && file_exists($localPath);
|
||||
$isRemote = !empty($remotePath);
|
||||
|
||||
if ($isLocal) {
|
||||
$fecha = date('Y-m-d H:i', filemtime($localPath));
|
||||
$tamano = filesize($localPath);
|
||||
$tamanoFmt = number_format($tamano / 1024, 2) . ' KB';
|
||||
|
||||
// Actualizar la BD si ha cambiado
|
||||
if ($entry['size'] != $tamano) {
|
||||
$this->backupModel->update($entry['id'], [
|
||||
'size' => $tamano,
|
||||
]);
|
||||
}
|
||||
|
||||
} else {
|
||||
$fecha = $entry['created_at'] ?? '-';
|
||||
$tamano = $entry['size'] ?? null;
|
||||
$tamanoFmt = $tamano ? number_format($tamano / 1024, 2) . ' KB' : '-';
|
||||
}
|
||||
|
||||
$backups[] = [
|
||||
'id' => $entry['id'],
|
||||
'filename' => $file,
|
||||
'fecha' => $fecha,
|
||||
'tamano' => $tamanoFmt,
|
||||
'local' => $isLocal,
|
||||
'remoto' => $isRemote,
|
||||
];
|
||||
}
|
||||
|
||||
return view('themes/vuexy/form/backups/backupList', ['backups' => $backups]);
|
||||
}
|
||||
|
||||
|
||||
public function create()
|
||||
{
|
||||
helper('filesystem');
|
||||
|
||||
$filename = 'backup_' . date('Ymd_His') . '.sql';
|
||||
$path = WRITEPATH . 'backups/' . $filename;
|
||||
$timestamp = date('Ymd_His');
|
||||
$sqlFilename = "backup_{$timestamp}.sql";
|
||||
$zipFilename = "backup_{$timestamp}.zip";
|
||||
$sqlPath = WRITEPATH . 'backups/' . $sqlFilename;
|
||||
$zipPath = WRITEPATH . 'backups/' . $zipFilename;
|
||||
|
||||
$dbConfig = config('Database')->default;
|
||||
|
||||
$host = $dbConfig['hostname'];
|
||||
$username = $dbConfig['username'];
|
||||
$password = $dbConfig['password'];
|
||||
$database = $dbConfig['database'];
|
||||
|
||||
$command = "mysqldump -h {$host} -u{$username} -p'{$password}' {$database} > {$path}";
|
||||
|
||||
$command = "mysqldump -h {$host} -u{$username} -p'{$password}' {$database} > {$sqlPath}";
|
||||
system($command, $retval);
|
||||
|
||||
if ($retval !== 0) {
|
||||
throw new \RuntimeException("Error al crear el backup.");
|
||||
}
|
||||
|
||||
// Enviar a SFTP
|
||||
$this->sendToSFTP($path, $filename);
|
||||
// Crear el zip
|
||||
$zip = new ZipArchive();
|
||||
if ($zip->open($zipPath, ZipArchive::CREATE) === TRUE) {
|
||||
$zip->addFile($sqlPath, $sqlFilename);
|
||||
$zip->close();
|
||||
unlink($sqlPath); // eliminar el .sql original
|
||||
} else {
|
||||
throw new \RuntimeException("Error al comprimir el backup.");
|
||||
}
|
||||
|
||||
return redirect()->to(route_to('backupsList'))->with('message', 'Backup creado y enviado.');
|
||||
// Insertar en BD
|
||||
$backupId = $this->backupModel->insert([
|
||||
'filename' => $zipFilename,
|
||||
'type' => 'manual',
|
||||
'path_local' => $zipPath,
|
||||
'path_remote' => null,
|
||||
'size' => filesize($zipPath),
|
||||
'status' => 'pendiente',
|
||||
'created_at' => date('Y-m-d H:i:s')
|
||||
], true);
|
||||
|
||||
// Enviar a SFTP
|
||||
$this->sendToSFTP($zipPath, $zipFilename);
|
||||
|
||||
// Actualizar BD
|
||||
$remotePath = "/users/erp2019/backups_erp/" . $zipFilename;
|
||||
$this->backupModel->update($backupId, [
|
||||
'path_remote' => $remotePath,
|
||||
'status' => 'subido'
|
||||
]);
|
||||
|
||||
return redirect()->to(route_to('backupsList'))->with('message', 'Backup creado, comprimido y enviado.');
|
||||
}
|
||||
|
||||
public function restore($file)
|
||||
@ -50,18 +126,49 @@ class Backups extends BaseController
|
||||
throw new \CodeIgniter\Exceptions\PageNotFoundException("Backup no encontrado.");
|
||||
}
|
||||
|
||||
$db = \Config\Database::connect();
|
||||
$sql = file_get_contents($path);
|
||||
$db->query('SET FOREIGN_KEY_CHECKS=0;');
|
||||
$db->query($sql);
|
||||
$db->query('SET FOREIGN_KEY_CHECKS=1;');
|
||||
$zip = new ZipArchive();
|
||||
if ($zip->open($path) === TRUE) {
|
||||
$extractPath = WRITEPATH . 'backups/tmp_restore/';
|
||||
if (!is_dir($extractPath)) {
|
||||
mkdir($extractPath, 0775, true);
|
||||
}
|
||||
$zip->extractTo($extractPath);
|
||||
$zip->close();
|
||||
|
||||
return redirect()->to('/backups')->with('message', 'Backup restaurado.');
|
||||
$sqlFiles = glob($extractPath . '*.sql');
|
||||
if (count($sqlFiles) === 0) {
|
||||
throw new \RuntimeException("No se encontró ningún archivo .sql en el ZIP");
|
||||
}
|
||||
|
||||
$db = \Config\Database::connect();
|
||||
$sql = file_get_contents($sqlFiles[0]);
|
||||
$db->query('SET FOREIGN_KEY_CHECKS=0;');
|
||||
$db->query($sql);
|
||||
$db->query('SET FOREIGN_KEY_CHECKS=1;');
|
||||
|
||||
array_map('unlink', glob($extractPath . '*'));
|
||||
rmdir($extractPath);
|
||||
|
||||
return redirect()->to('/backups')->with('message', 'Backup restaurado correctamente.');
|
||||
} else {
|
||||
throw new \RuntimeException("No se pudo abrir el archivo ZIP");
|
||||
}
|
||||
}
|
||||
|
||||
public function deleteLocal($id)
|
||||
{
|
||||
$backup = $this->backupModel->find($id);
|
||||
if ($backup && $backup['path_local'] && file_exists($backup['path_local'])) {
|
||||
unlink($backup['path_local']);
|
||||
$this->backupModel->update($id, ['path_local' => null]);
|
||||
return redirect()->to(route_to('backupsList'))->with('message', 'Backup local eliminado.');
|
||||
}
|
||||
return redirect()->to(route_to('backupsList'))->with('error', 'Archivo no encontrado.');
|
||||
}
|
||||
|
||||
|
||||
private function sendToSFTP($localPath, $remoteFilename)
|
||||
{
|
||||
|
||||
$sftpHost = 'sftp.hidrive.ionos.com';
|
||||
$sftpUser = 'erp2019';
|
||||
$sftpPass = 'Z2CjX7kd2h';
|
||||
@ -78,6 +185,5 @@ class Backups extends BaseController
|
||||
if (!$sftp->put($remotePath, $fileContents)) {
|
||||
throw new \RuntimeException("No se pudo subir el backup al servidor SFTP.");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@ -0,0 +1,33 @@
|
||||
<?php
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
class CreateBackupsTable extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
$this->forge->addField([
|
||||
'id' => [
|
||||
'type' => 'INT',
|
||||
'unsigned' => true,
|
||||
'auto_increment' => true
|
||||
],
|
||||
'filename' => ['type' => 'VARCHAR', 'constraint' => 255],
|
||||
'type' => ['type' => 'ENUM', 'constraint' => ['manual', 'cron']],
|
||||
'path_local' => ['type' => 'VARCHAR', 'constraint' => 255, 'null' => true],
|
||||
'path_remote' => ['type' => 'VARCHAR', 'constraint' => 255, 'null' => true],
|
||||
'size' => ['type' => 'INT', 'unsigned' => true, 'null' => true],
|
||||
'status' => ['type' => 'ENUM', 'constraint' => ['pendiente', 'subido', 'error'], 'default' => 'pendiente'],
|
||||
'created_at' => ['type' => 'DATETIME', 'null' => false],
|
||||
'updated_at' => ['type' => 'DATETIME', 'null' => true],
|
||||
]);
|
||||
$this->forge->addKey('id', true);
|
||||
$this->forge->createTable('backups');
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
$this->forge->dropTable('backups');
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,26 @@
|
||||
<?php
|
||||
namespace App\Database\Migrations;
|
||||
|
||||
use CodeIgniter\Database\Migration;
|
||||
|
||||
class CreateRestoresTable extends Migration
|
||||
{
|
||||
public function up()
|
||||
{
|
||||
$this->forge->addField([
|
||||
'id' => ['type' => 'INT', 'unsigned' => true, 'auto_increment' => true],
|
||||
'backup_id' => ['type' => 'INT', 'unsigned' => true],
|
||||
'restored_by' => ['type' => 'VARCHAR', 'constraint' => 100],
|
||||
'source' => ['type' => 'ENUM', 'constraint' => ['local', 'remote']],
|
||||
'restored_at' => ['type' => 'DATETIME'],
|
||||
]);
|
||||
$this->forge->addKey('id', true);
|
||||
$this->forge->addForeignKey('backup_id', 'backups', 'id', 'CASCADE', 'CASCADE');
|
||||
$this->forge->createTable('restores');
|
||||
}
|
||||
|
||||
public function down()
|
||||
{
|
||||
$this->forge->dropTable('restores');
|
||||
}
|
||||
}
|
||||
15
ci4/app/Models/Sistema/BackupModel.php
Normal file
15
ci4/app/Models/Sistema/BackupModel.php
Normal file
@ -0,0 +1,15 @@
|
||||
<?php
|
||||
|
||||
namespace App\Models\Sistema;
|
||||
|
||||
use App\Models\BaseModel;
|
||||
|
||||
class BackupModel extends BaseModel
|
||||
{
|
||||
protected $table = 'backups';
|
||||
protected $primaryKey = 'id';
|
||||
protected $allowedFields = [
|
||||
'filename', 'type', 'path_local', 'path_remote', 'size', 'status', 'created_at', 'updated_at'
|
||||
];
|
||||
protected $useTimestamps = true;
|
||||
}
|
||||
@ -6,23 +6,67 @@
|
||||
<!--Content Body-->
|
||||
<div class="row mt-4">
|
||||
<div class="col-md-12">
|
||||
<h4 class="mb-4">Backups disponibles</h4>
|
||||
<a href="<?= route_to('backupsCreate') ?>" class="btn btn-primary mb-3">Crear backup</a>
|
||||
|
||||
<div class="table-responsive">
|
||||
<table id="tablaBackups" class="table table-striped table-hover w-100">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Archivo</th>
|
||||
<th>Fecha</th>
|
||||
<th>Tamaño</th>
|
||||
<th>Local</th>
|
||||
<th>Remoto</th>
|
||||
<th>Acciones</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<?php foreach ($backups as $b): ?>
|
||||
<tr>
|
||||
<td><?= esc($b['filename']) ?></td>
|
||||
<td><?= esc($b['fecha']) ?></td>
|
||||
<td><?= esc($b['tamano']) ?></td>
|
||||
<td><span class="badge bg-<?= $b['local'] ? 'success' : 'secondary' ?>">
|
||||
<?= $b['local'] ? 'Sí' : 'No' ?>
|
||||
</span></td>
|
||||
<td><span class="badge bg-<?= $b['remoto'] ? 'info' : 'secondary' ?>">
|
||||
<?= $b['remoto'] ? 'Sí' : 'No' ?>
|
||||
</span></td>
|
||||
<td class="text-nowrap">
|
||||
<?php if ($b['local']): ?>
|
||||
<a href="<?= base_url('backups/restore/' . $b['filename']) ?>"
|
||||
class="btn btn-sm btn-warning">Restaurar</a>
|
||||
<a href="<?= base_url('backups/delete-local/' . $b['id']) ?>"
|
||||
class="btn btn-sm btn-danger">Eliminar local</a>
|
||||
<?php elseif ($b['remoto']): ?>
|
||||
<a href="<?= base_url('backups/restore/' . $b['filename'] . '/remote') ?>"
|
||||
class="btn btn-sm btn-warning">Restaurar Remoto</a>
|
||||
<?php else: ?>
|
||||
<span class="text-muted">No disponible</span>
|
||||
<?php endif; ?>
|
||||
</td>
|
||||
</tr>
|
||||
<?php endforeach; ?>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
</div>
|
||||
|
||||
<h2>Backups disponibles</h2>
|
||||
<a href="<?= route_to('backupsCreate') ?>">Crear backup</a>
|
||||
<ul>
|
||||
<?php foreach ($files as $file): ?>
|
||||
<li>
|
||||
<?= $file ?>
|
||||
<a href="<?= base_url('backups/restore/' . $file) ?>">[Restaurar]</a>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
<?= session('message') ?>
|
||||
</div>
|
||||
</div>
|
||||
<?= $this->endSection() ?>
|
||||
|
||||
<?= $this->section('additionalInlineJs') ?>
|
||||
|
||||
|
||||
<script>
|
||||
$(document).ready(function () {
|
||||
$('#tablaBackups').DataTable({
|
||||
order: [[1, 'desc']],
|
||||
language: {
|
||||
url: '/assets/vendor/datatables/i18n/es-ES.json' // ajusta si usas idioma español
|
||||
}
|
||||
});
|
||||
});
|
||||
</script>
|
||||
<?= $this->endSection() ?>
|
||||
Reference in New Issue
Block a user