Add all files
This commit is contained in:
182
extensions/Servers/Proxmox/Livewire/Backups/Create.php
Normal file
182
extensions/Servers/Proxmox/Livewire/Backups/Create.php
Normal file
@@ -0,0 +1,182 @@
|
||||
<?php
|
||||
|
||||
namespace Paymenter\Extensions\Servers\Proxmox\Livewire\Backups;
|
||||
|
||||
use Illuminate\Support\Facades\Cache;
|
||||
use Paymenter\Extensions\Servers\Proxmox\Livewire\Component;
|
||||
use Paymenter\Extensions\Servers\Proxmox\Proxmox;
|
||||
|
||||
class Create extends Component
|
||||
{
|
||||
private const BACKUP_RATE_LIMIT_MINUTES = 10;
|
||||
public $visible;
|
||||
|
||||
public $name;
|
||||
|
||||
public $type = 'snapshot';
|
||||
|
||||
public $compression = 'zstd';
|
||||
|
||||
public function getCanCreateBackupProperty()
|
||||
{
|
||||
if ($this->isBackupRateLimited()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$backupSettings = $this->server->service->product->settings()->where('key', 'backups')->first();
|
||||
$backupLimit = $backupSettings?->value ?? 0;
|
||||
|
||||
// If limit is 0 or not set, allow unlimited backups
|
||||
if ($backupLimit <= 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$proxmox = new Proxmox;
|
||||
$currentBackups = $proxmox->request('/nodes/' . $this->server->node->name . '/storage/' . $this->server->node->backup_location . '/content', data: [
|
||||
'content' => 'backup',
|
||||
'vmid' => $this->server->vm_id,
|
||||
])->json()['data'] ?? [];
|
||||
|
||||
$currentBackupCount = count($currentBackups);
|
||||
|
||||
return $currentBackupCount < $backupLimit;
|
||||
}
|
||||
|
||||
public function backup()
|
||||
{
|
||||
$this->validate([
|
||||
'name' => 'required|string|max:255',
|
||||
'type' => 'required|in:snapshot,suspend,stop',
|
||||
'compression' => 'required|in:gzip,lzo,zstd,none',
|
||||
]);
|
||||
|
||||
if ($this->isBackupRateLimited()) {
|
||||
$remainingSeconds = $this->secondsUntilRateLimitExpires();
|
||||
$this->notify(
|
||||
sprintf(
|
||||
'You must wait %s before creating another backup.',
|
||||
$this->formatDurationForHumans($remainingSeconds)
|
||||
),
|
||||
'error'
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check backup limit
|
||||
$backupSettings = $this->server->service->product->settings()->where('key', 'backups')->first();
|
||||
$backupLimit = $backupSettings?->value ?? 0;
|
||||
|
||||
if ($backupLimit > 0) {
|
||||
$proxmox = new Proxmox;
|
||||
$currentBackups = $proxmox->request('/nodes/' . $this->server->node->name . '/storage/' . $this->server->node->backup_location . '/content', data: [
|
||||
'content' => 'backup',
|
||||
'vmid' => $this->server->vm_id,
|
||||
])->json()['data'] ?? [];
|
||||
|
||||
$currentBackupCount = count($currentBackups);
|
||||
|
||||
if ($currentBackupCount >= $backupLimit) {
|
||||
$this->notify("Backup limit reached. You can only create {$backupLimit} backup(s). Please delete an existing backup before creating a new one.", 'error');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
$proxmox = new Proxmox;
|
||||
|
||||
try {
|
||||
$proxmox->request('/nodes/' . $this->server->node->name . '/vzdump', 'post', data: [
|
||||
'vmid' => $this->server->vm_id,
|
||||
'storage' => $this->server->node->backup_location,
|
||||
'mode' => $this->type,
|
||||
'compress' => $this->compression == 'none' ? (int) false : $this->compression,
|
||||
'notes-template' => $this->name,
|
||||
]);
|
||||
} catch (\Throwable $e) {
|
||||
$this->notify('Failed to start backup: ' . $e->getMessage(), 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
Cache::put(
|
||||
$this->getBackupRateLimitCacheKey(),
|
||||
now()->timestamp,
|
||||
now()->addMinutes(self::BACKUP_RATE_LIMIT_MINUTES + 1)
|
||||
);
|
||||
|
||||
$this->notify('Backup started', 'success');
|
||||
|
||||
$this->visible = false;
|
||||
|
||||
$this->reset([
|
||||
'name',
|
||||
'type',
|
||||
'compression',
|
||||
]);
|
||||
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('proxmox::backups.create');
|
||||
}
|
||||
|
||||
public function pollEligibility(): void
|
||||
{
|
||||
// Intentionally empty. Polling triggers Livewire to re-render,
|
||||
// which re-evaluates canCreateBackup (rate limit + plan limits).
|
||||
}
|
||||
|
||||
private function isBackupRateLimited(): bool
|
||||
{
|
||||
$lastTimestamp = Cache::get($this->getBackupRateLimitCacheKey());
|
||||
|
||||
if (!$lastTimestamp) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$elapsed = now()->timestamp - (int) $lastTimestamp;
|
||||
|
||||
if ($elapsed >= self::BACKUP_RATE_LIMIT_MINUTES * 60) {
|
||||
Cache::forget($this->getBackupRateLimitCacheKey());
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private function secondsUntilRateLimitExpires(): int
|
||||
{
|
||||
if (!$this->isBackupRateLimited()) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
$lastTimestamp = (int) Cache::get($this->getBackupRateLimitCacheKey());
|
||||
$elapsed = now()->timestamp - $lastTimestamp;
|
||||
|
||||
return max(0, (self::BACKUP_RATE_LIMIT_MINUTES * 60) - $elapsed);
|
||||
}
|
||||
|
||||
private function getBackupRateLimitCacheKey(): string
|
||||
{
|
||||
return 'proxmox-backup-last-create-' . $this->server->id;
|
||||
}
|
||||
|
||||
private function formatDurationForHumans(int $seconds): string
|
||||
{
|
||||
if ($seconds <= 0) {
|
||||
return 'a moment';
|
||||
}
|
||||
|
||||
$minutes = intdiv($seconds, 60);
|
||||
$remainingSeconds = $seconds % 60;
|
||||
|
||||
if ($minutes > 0 && $remainingSeconds > 0) {
|
||||
return sprintf('%d minute(s) and %d second(s)', $minutes, $remainingSeconds);
|
||||
}
|
||||
|
||||
if ($minutes > 0) {
|
||||
return sprintf('%d minute(s)', $minutes);
|
||||
}
|
||||
|
||||
return sprintf('%d second(s)', $remainingSeconds);
|
||||
}
|
||||
}
|
||||
133
extensions/Servers/Proxmox/Livewire/Backups/Index.php
Normal file
133
extensions/Servers/Proxmox/Livewire/Backups/Index.php
Normal file
@@ -0,0 +1,133 @@
|
||||
<?php
|
||||
|
||||
namespace Paymenter\Extensions\Servers\Proxmox\Livewire\Backups;
|
||||
|
||||
use Livewire\Attributes\Locked;
|
||||
use Paymenter\Extensions\Servers\Proxmox\Livewire\Component;
|
||||
use Paymenter\Extensions\Servers\Proxmox\Models\Server;
|
||||
use Paymenter\Extensions\Servers\Proxmox\Proxmox;
|
||||
|
||||
class Index extends Component
|
||||
{
|
||||
#[Locked]
|
||||
public $backups = [];
|
||||
|
||||
public $showRestore = false;
|
||||
|
||||
public $confirmRestore = true;
|
||||
|
||||
public bool $showDelete = false;
|
||||
|
||||
public bool $confirmDelete = false;
|
||||
|
||||
public $volid;
|
||||
|
||||
public function mount(Server $server): void
|
||||
{
|
||||
parent::mount($server);
|
||||
|
||||
$proxmox = new Proxmox;
|
||||
$settings = $this->server->service->product->settings()->where('key', 'backups')->first();
|
||||
if ($settings?->value <= 0) {
|
||||
$this->redirect(route('proxmox.server.overview', $this->server->id), true);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$this->backups = $proxmox->request('/nodes/' . $this->server->node->name . '/storage/' . $this->server->node->backup_location . '/content', data: [
|
||||
'content' => 'backup',
|
||||
'vmid' => $this->server->vm_id,
|
||||
])->json()['data'];
|
||||
}
|
||||
|
||||
public function restoreBackup()
|
||||
{
|
||||
$this->validate([
|
||||
'volid' => 'required',
|
||||
]);
|
||||
|
||||
if (!$this->confirmRestore) {
|
||||
$this->notify('Please confirm the restore action', 'error');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if backup exists
|
||||
$backup = collect($this->backups)->firstWhere('volid', $this->volid);
|
||||
if (!$backup) {
|
||||
$this->notify('Backup not found', 'error');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$proxmox = new Proxmox;
|
||||
$proxmox->request('/nodes/' . $this->server->node->name . '/qemu', 'post', data: [
|
||||
'vmid' => $this->server->vm_id,
|
||||
'force' => true,
|
||||
'archive' => $this->volid,
|
||||
]);
|
||||
|
||||
$this->notify('Backup restoration in progress', 'success');
|
||||
|
||||
$this->showRestore = false;
|
||||
|
||||
// Redirect to the server overview page
|
||||
$this->redirect(route('proxmox.server.overview', $this->server->id), true);
|
||||
}
|
||||
|
||||
public function promptDelete(string $volid): void
|
||||
{
|
||||
$this->volid = $volid;
|
||||
$this->confirmDelete = false;
|
||||
$this->showDelete = true;
|
||||
}
|
||||
|
||||
public function deleteBackup(?string $volid = null)
|
||||
{
|
||||
$volid = $volid ?? $this->volid;
|
||||
|
||||
if (!$volid) {
|
||||
$this->notify('Select a backup to delete.', 'error');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (!$this->confirmDelete) {
|
||||
$this->notify('Please confirm the delete action', 'error');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if backup exists
|
||||
$backup = collect($this->backups)->firstWhere('volid', $volid);
|
||||
if (!$backup) {
|
||||
$this->notify('Backup not found', 'error');
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$proxmox = new Proxmox;
|
||||
$proxmox->requestAndWait('/nodes/' . $this->server->node->name . '/qemu/' . $this->server->vm_id . '/status/stop', 'post', [
|
||||
'vmid' => $this->server->vm_id,
|
||||
], $this->server->node->name);
|
||||
|
||||
$response = $proxmox->request('/nodes/' . $this->server->node->name . '/storage/' . $this->server->node->backup_location . '/content/' . $volid, 'delete');
|
||||
|
||||
if ($response->status() == 200) {
|
||||
$this->notify('Backup deleted successfully', 'success');
|
||||
$this->backups = array_filter($this->backups, fn ($backup) => $backup['volid'] !== $volid);
|
||||
$this->showDelete = false;
|
||||
$this->confirmDelete = false;
|
||||
$this->volid = null;
|
||||
} else {
|
||||
$this->notify('Failed to delete backup: ' . $response->json()['errors'][0]['message'], 'error');
|
||||
}
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
return view('proxmox::backups')->layoutData([
|
||||
'sidebar' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user