|
|
|
@@ -1,4 +1,18 @@
|
|
|
|
|
<div class="flex items-center gap-1">
|
|
|
|
|
<div
|
|
|
|
|
class="flex items-center gap-1"
|
|
|
|
|
x-init="if (['/admin', '/admin/'].includes(window.location.pathname)) $wire.checkPeerStatus()"
|
|
|
|
|
>
|
|
|
|
|
|
|
|
|
|
@can('View:SyncPage')
|
|
|
|
|
@if($peerChecked)
|
|
|
|
|
<span class="text-xs font-medium px-1 whitespace-nowrap"
|
|
|
|
|
style="{{ $peerConnected ? 'color: #15803d' : 'color: #dc2626' }}"
|
|
|
|
|
x-bind:style="{{ json_encode($peerConnected) }} ? ($store.theme === 'dark' ? 'color: #4ade80' : 'color: #15803d') : ($store.theme === 'dark' ? 'color: #f87171' : 'color: #dc2626')"
|
|
|
|
|
@if($peerPath) title="{{ $peerPath }}" @endif>
|
|
|
|
|
{{ $peerConnected ? __('navigation.sync.peer_connected') : __('navigation.sync.peer_disconnected') }}
|
|
|
|
|
</span>
|
|
|
|
|
@endif
|
|
|
|
|
@endcan
|
|
|
|
|
|
|
|
|
|
<button
|
|
|
|
|
x-data
|
|
|
|
@@ -26,9 +40,17 @@ class="relative"
|
|
|
|
|
failed: false,
|
|
|
|
|
interval: null,
|
|
|
|
|
|
|
|
|
|
showBackupProgress: false,
|
|
|
|
|
backupProgress: 0,
|
|
|
|
|
backupStatusText: '',
|
|
|
|
|
backupDone: false,
|
|
|
|
|
backupInterval: null,
|
|
|
|
|
backupUrl: null,
|
|
|
|
|
|
|
|
|
|
confirmSync() {
|
|
|
|
|
this.open = false;
|
|
|
|
|
this.showConfirm = true;
|
|
|
|
|
$wire.checkPeerStatus();
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
startSync() {
|
|
|
|
@@ -60,6 +82,33 @@ class="relative"
|
|
|
|
|
if (this.done) {
|
|
|
|
|
window.location.reload();
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
startBackup() {
|
|
|
|
|
this.open = false;
|
|
|
|
|
this.showBackupProgress = true;
|
|
|
|
|
this.backupProgress = 5;
|
|
|
|
|
this.backupDone = false;
|
|
|
|
|
this.backupUrl = null;
|
|
|
|
|
this.backupStatusText = '{{ __('navigation.backup.status_preparing') }}';
|
|
|
|
|
|
|
|
|
|
this.backupInterval = setInterval(() => {
|
|
|
|
|
if (this.backupProgress < 80) {
|
|
|
|
|
this.backupProgress += Math.random() * 8;
|
|
|
|
|
if (this.backupProgress > 30) {
|
|
|
|
|
this.backupStatusText = '{{ __('navigation.backup.status_compressing') }}';
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}, 400);
|
|
|
|
|
|
|
|
|
|
$wire.runBackup();
|
|
|
|
|
},
|
|
|
|
|
|
|
|
|
|
closeBackupModal() {
|
|
|
|
|
this.showBackupProgress = false;
|
|
|
|
|
if (this.backupUrl) {
|
|
|
|
|
window.location.href = this.backupUrl;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}"
|
|
|
|
|
x-on:sync-finished.window="
|
|
|
|
@@ -73,6 +122,13 @@ class="relative"
|
|
|
|
|
failed = true;
|
|
|
|
|
}
|
|
|
|
|
"
|
|
|
|
|
x-on:backup-ready.window="
|
|
|
|
|
clearInterval(backupInterval);
|
|
|
|
|
backupProgress = 100;
|
|
|
|
|
backupStatusText = '{{ __('navigation.backup.status_ready') }}';
|
|
|
|
|
backupDone = true;
|
|
|
|
|
backupUrl = $event.detail.url;
|
|
|
|
|
"
|
|
|
|
|
@click.outside="open = false"
|
|
|
|
|
>
|
|
|
|
|
<button
|
|
|
|
@@ -114,8 +170,7 @@ class="flex items-center gap-3 px-4 py-2.5 text-sm text-gray-700 dark:text-gray-
|
|
|
|
|
|
|
|
|
|
<button
|
|
|
|
|
type="button"
|
|
|
|
|
@click="open = false"
|
|
|
|
|
wire:click="runBackup"
|
|
|
|
|
@click="startBackup()"
|
|
|
|
|
class="flex items-center gap-3 px-4 py-2.5 text-sm text-gray-700 dark:text-gray-300 hover:bg-gray-50 dark:hover:bg-gray-700 transition w-full"
|
|
|
|
|
>
|
|
|
|
|
<x-heroicon-o-arrow-down-tray class="w-4 h-4 text-success-500 shrink-0" />
|
|
|
|
@@ -222,6 +277,61 @@ class="px-4 py-2 text-sm font-medium text-white rounded-lg transition"
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
<template x-teleport="body">
|
|
|
|
|
<div
|
|
|
|
|
x-show="showBackupProgress"
|
|
|
|
|
x-transition.opacity
|
|
|
|
|
dir="rtl"
|
|
|
|
|
class="fixed inset-0 z-999 flex items-center justify-center bg-black/50 backdrop-blur-sm"
|
|
|
|
|
style="display:none"
|
|
|
|
|
>
|
|
|
|
|
<div style="position:relative; background:white; border-radius:1rem; box-shadow:0 20px 60px rgba(0,0,0,.2); width:100%; max-width:28rem; margin:0 1rem; overflow:hidden" class="dark:bg-gray-800">
|
|
|
|
|
{{-- Close button: top-left, completely independent of layout --}}
|
|
|
|
|
<button
|
|
|
|
|
@click="showBackupProgress = false; clearInterval(backupInterval)"
|
|
|
|
|
style="position:absolute; left:12px; top:12px; z-index:10"
|
|
|
|
|
class="flex items-center justify-center w-9 h-9 rounded-lg text-gray-400 hover:text-gray-600 hover:bg-gray-100 dark:hover:text-gray-200 dark:hover:bg-gray-700 transition"
|
|
|
|
|
>
|
|
|
|
|
<x-heroicon-o-x-mark class="w-4 h-4" />
|
|
|
|
|
</button>
|
|
|
|
|
|
|
|
|
|
<div class="px-6 pt-6 pb-4">
|
|
|
|
|
<div class="flex items-center gap-3 mb-5">
|
|
|
|
|
<div class="flex items-center justify-center w-10 h-10 rounded-full bg-success-50 dark:bg-success-900/30">
|
|
|
|
|
<template x-if="!backupDone">
|
|
|
|
|
<svg class="w-5 h-5 text-success-500 animate-spin" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24">
|
|
|
|
|
<circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4"></circle>
|
|
|
|
|
<path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4z"></path>
|
|
|
|
|
</svg>
|
|
|
|
|
</template>
|
|
|
|
|
<template x-if="backupDone">
|
|
|
|
|
<x-heroicon-o-check-circle class="w-5 h-5 text-success-500" />
|
|
|
|
|
</template>
|
|
|
|
|
</div>
|
|
|
|
|
<h3 class="text-lg font-semibold text-gray-900 dark:text-white">{{ __('navigation.backup.label') }}</h3>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="w-full bg-gray-200 dark:bg-gray-700 rounded-full h-3 mb-3 overflow-hidden">
|
|
|
|
|
<div
|
|
|
|
|
class="h-3 rounded-full transition-all duration-500 ease-out bg-success-500"
|
|
|
|
|
:style="'width: ' + Math.min(backupProgress, 100) + '%'"
|
|
|
|
|
></div>
|
|
|
|
|
</div>
|
|
|
|
|
<p class="text-sm text-gray-600 dark:text-gray-400 mb-1" x-text="backupStatusText"></p>
|
|
|
|
|
<p class="text-xs text-gray-400 dark:text-gray-500" x-text="Math.round(Math.min(backupProgress, 100)) + '%'"></p>
|
|
|
|
|
</div>
|
|
|
|
|
<div class="flex items-center justify-end px-6 py-4 bg-gray-50 dark:bg-gray-900/50">
|
|
|
|
|
<button
|
|
|
|
|
x-show="backupDone"
|
|
|
|
|
@click="showBackupProgress = false; window.location.href = backupUrl"
|
|
|
|
|
class="px-4 py-2 text-sm font-medium text-white bg-success-600 hover:bg-success-700 rounded-lg transition"
|
|
|
|
|
>
|
|
|
|
|
{{ __('navigation.backup.download_button') }}
|
|
|
|
|
</button>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</div>
|
|
|
|
|
</template>
|
|
|
|
|
|
|
|
|
|
</div>
|
|
|
|
|
@endcan
|
|
|
|
|
|
|
|
|
|