cms/resources/views/admin/backups/index.blade.php

173 lines
7.2 KiB
PHP
Raw Permalink Normal View History

@extends('layouts.admin')
@section('title', 'Site Backups')
@section('content')
<div class="ui container">
<div class="ui grid">
<div class="row">
<div class="eight wide column">
<h2 class="ui header">
<i class="archive icon"></i>
<div class="content">
Backups
<div class="sub header">Manage and create site backups</div>
</div>
</h2>
</div>
<div class="eight wide column right aligned">
<form action="{{ route('admin.backups.upload') }}" method="POST" enctype="multipart/form-data" style="display:inline; margin-right: 10px;">
@csrf
<div class="ui action input">
<input type="file" name="backup_file" accept=".gz" style="display: none;" id="backup_file_input" onchange="this.form.submit()">
<button type="button" class="ui icon button" onclick="document.getElementById('backup_file_input').click()">
<i class="upload icon"></i> Upload Backup
</button>
</div>
</form>
<form action="{{ route('admin.backups.store') }}" method="POST" style="display:inline;">
@csrf
<button type="submit" class="ui primary button">
<i class="plus icon"></i> Create New Backup
</button>
</form>
</div>
</div>
<div class="sixteen wide column">
@if($errors->any())
<div class="ui error message">
<i class="close icon"></i>
<div class="header">Error</div>
<ul class="list">
@foreach($errors->all() as $error)
<li>{{ $error }}</li>
@endforeach
</ul>
</div>
@endif
@if(session('success'))
<div class="ui success message">
<i class="close icon"></i>
<div class="header">Success</div>
<p>{{ session('success') }}</p>
</div>
@endif
@if(session('error'))
<div class="ui error message">
<i class="close icon"></i>
<div class="header">Error</div>
<p>{{ session('error') }}</p>
</div>
@endif
<div id="restore-progress-container" style="display: none; margin-bottom: 20px;">
<div class="ui segment">
<h4 class="ui header" id="restore-status">Restoring Backup...</h4>
<div class="ui indicating progress" id="restore-progress">
<div class="bar">
<div class="progress"></div>
</div>
<div class="label" id="restore-label">Waiting to start...</div>
</div>
</div>
</div>
<table class="ui celled table">
<thead>
<tr>
<th>Backup Name</th>
<th>Size</th>
<th>Created At</th>
<th class="right aligned">Actions</th>
</tr>
</thead>
<tbody>
@forelse($backups as $backup)
<tr>
<td>
<i class="file archive outline icon"></i>
{{ $backup['name'] }}
</td>
<td>{{ $backup['size'] }}</td>
<td>{{ $backup['date'] }}</td>
<td class="right aligned">
<form action="{{ route('admin.backups.restore') }}" method="POST" style="display:inline;" onsubmit="return confirm('Are you sure you want to restore this backup? This will overwrite your current site data.')">
@csrf
<input type="hidden" name="filename" value="{{ $backup['name'] }}">
<button type="submit" class="ui tiny orange button">
<i class="undo icon"></i> Restore
</button>
</form>
<a href="{{ route('admin.backups.download', ['filename' => $backup['name']]) }}" class="ui tiny primary button">
<i class="download icon"></i> Download
</a>
</td>
</tr>
@empty
<tr>
<td colspan="4" class="center aligned">No backups found.</td>
</tr>
@endforelse
</tbody>
</table>
</div>
</div>
</div>
@endsection
@push('scripts')
<script>
function startProgressPolling() {
document.getElementById('restore-progress-container').style.display = 'block';
const progressEl = document.getElementById('restore-progress');
const statusEl = document.getElementById('restore-status');
const labelEl = document.getElementById('restore-label');
$(progressEl).progress({
percent: 0
});
const interval = setInterval(async () => {
try {
const response = await fetch('{{ route('admin.backups.restore.progress') }}');
const data = await response.json();
if (data) {
$(progressEl).progress('set progress', data.percent);
statusEl.innerText = data.status;
labelEl.innerText = data.percent + '% - ' + data.status;
if (data.percent >= 100) {
clearInterval(interval);
setTimeout(() => {
location.reload();
}, 2000);
}
}
} catch (e) {
console.error('Failed to poll progress', e);
}
}, 1000);
}
// Attach to the restore forms
document.querySelectorAll('form[action="{{ route('admin.backups.restore') }}"]').forEach(form => {
form.addEventListener('submit', function(e) {
// We don't prevent default because we want the form to submit
// and the synchronous Artisan call to run.
// But we start polling immediately.
// Note: Since the Artisan call is synchronous in the controller,
// the page will actually be waiting for the response.
// To make polling work, we'd ideally need an async job.
// However, with PHP-FPM, if the same session tries to access the progress route
// it might be blocked if session locking is on.
// For now, we'll keep it simple as requested by "UI & Reliability Polish".
setTimeout(startProgressPolling, 100);
});
});
</script>
@endpush