Files

542 lines
26 KiB
PHP

<x-app-layout>
@push('styles')
<link href="https://unpkg.com/filepond/dist/filepond.css" rel="stylesheet" />
<link href="https://unpkg.com/filepond-plugin-image-preview/dist/filepond-plugin-image-preview.css"
rel="stylesheet" />
<style>
/* Shared Styles from Backup & Storage */
.status-widget-dark {
border-radius: 24px;
background: linear-gradient(135deg, #1e1e1e 0%, #2d2d2d 100%);
padding: 1.5rem;
color: white;
margin-bottom: 1.5rem;
box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.2);
}
/* Attached Design Preview Styles */
.mockup-container {
position: sticky;
top: 100px;
}
.browser-mockup-frame {
background: #e2e8f0;
border-radius: 12px;
overflow: hidden;
box-shadow: 0 30px 60px -12px rgba(0, 0, 0, 0.15);
border: 1px solid rgba(255, 255, 255, 0.4);
}
.browser-top-bar {
background: #f8fafc;
padding: 10px 18px;
border-bottom: 1px solid #e2e8f0;
display: flex;
align-items: center;
gap: 12px;
}
.browser-dots {
display: flex;
gap: 6px;
}
.browser-dot {
width: 10px;
height: 10px;
border-radius: 50%;
}
.browser-url-field {
background: #ffffff;
border-radius: 6px;
padding: 3px 15px;
font-size: 11px;
color: #94a3b8;
flex-grow: 1;
max-width: 450px;
text-align: center;
border: 1px solid #f1f5f9;
}
/* VISITOR VIEW BACKGROUND (STRICT AS PER IMAGE) */
.viewport-preview {
height: 700px;
background: #f4f4f4;
/* Base light gray */
background-image: repeating-linear-gradient(45deg, #f0f2f5, #f0f2f5 10px, #ffffff 10px, #ffffff 20px);
display: flex;
align-items: center;
justify-content: center;
padding: 3rem;
position: relative;
}
/* WHITE CARD (STRICT AS PER IMAGE) */
.visitor-card-premium {
background: #ffffff;
border-radius: 42px;
padding: 4rem 3rem;
text-align: center;
width: 100%;
max-width: 440px;
box-shadow: 0 15px 35px -5px rgba(0, 0, 0, 0.04);
z-index: 2;
}
.under-maintenance-pill {
background: #ffffff;
border: 1px solid #eef0f3;
border-radius: 100px;
padding: 8px 18px;
display: inline-flex;
align-items: center;
gap: 10px;
margin-bottom: 2.5rem;
font-size: 11px;
font-weight: 800;
color: #1a1c1e;
text-transform: uppercase;
letter-spacing: 0.5px;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.02);
}
.pulse-dot-red {
width: 8px;
height: 8px;
background-color: #ef4444;
border-radius: 50%;
animation: pulse-red 2s infinite;
}
@keyframes pulse-red {
0% {
box-shadow: 0 0 0 0 rgba(239, 68, 68, 0.4);
}
70% {
box-shadow: 0 0 0 8px rgba(239, 68, 68, 0);
}
100% {
box-shadow: 0 0 0 0 rgba(239, 68, 68, 0);
}
}
/* COUNTDOWN SQUARES (STRICT AS PER IMAGE) */
.countdown-grid {
display: grid;
grid-template-columns: repeat(4, 1fr);
gap: 10px;
margin-top: 2rem;
}
.countdown-square {
background: #1a1c1e;
border-radius: 20px;
aspect-ratio: 1/1;
display: flex;
align-items: center;
justify-content: center;
flex-direction: column;
color: white;
transition: all 0.3s ease;
}
.countdown-square:hover {
transform: scale(1.05);
}
.countdown-number {
font-size: 1.25rem;
font-weight: 800;
line-height: 1;
margin-bottom: 2px;
}
.countdown-label {
font-size: 7px;
text-transform: uppercase;
opacity: 0.5;
letter-spacing: 1px;
font-weight: 700;
}
.custom-switch-premium .form-check-input {
width: 3rem;
height: 1.5rem;
cursor: pointer;
}
.filepond--root {
margin-bottom: 0;
}
.filepond--panel-root {
background-color: #f8fafc;
border: 2px dashed #e2e8f0;
border-radius: 16px;
}
</style>
@endpush
<div class="container-fluid pb-5">
{{-- Page Header --}}
<div class="d-flex align-items-center justify-content-between mb-4 animate__animated animate__fadeIn">
<div>
<h4 class="fw-bold mb-1" style="font-family: 'Outfit', sans-serif; letter-spacing: -0.5px;">
{{ __('Maintenance Mode') }}
</h4>
<p class="text-muted small mb-0">
{{ __('Take your application offline for scheduled updates and optimization.') }}
</p>
</div>
</div>
<form id="maintenanceConfigForm" action="{{ route('system-config.update') }}" method="POST"
enctype="multipart/form-data" autocomplete="off" class="ajax-form" data-reset="false">
@csrf
@method('PUT')
<div class="row gx-4">
{{-- Left: Controls (Col-4) --}}
<div class="col-lg-4 animate__animated animate__fadeIn">
{{-- Status Card (Backup Styling) --}}
<div class="status-widget-dark">
<div class="d-flex justify-content-between align-items-center mb-4">
<div class="d-flex align-items-center gap-2">
<div class="bg-white bg-opacity-10 rounded-circle p-2">
<i class="bi bi-broadcast text-info"></i>
</div>
<span class="small fw-bold opacity-75 text-uppercase tracking-wider">Storage
Health</span>
</div>
@if($is_down)
<span class="badge rounded-pill bg-danger text-white fw-bold px-3 py-2"
style="font-size: 10px;">Maintenance</span>
@else
<span class="badge rounded-pill bg-info text-dark fw-bold px-3 py-2"
style="font-size: 10px;">Operational</span>
@endif
</div>
<div class="mb-4">
<h2 class="mb-1 fw-bold" style="font-family: 'Outfit', sans-serif;">
@if($is_down)
System Offline
@else
Systems Ready
@endif
</h2>
<p class="small opacity-50 mb-0">Public access control center.</p>
</div>
<div
class="form-check form-switch custom-switch-premium p-0 d-flex align-items-center justify-content-between bg-white bg-opacity-5 p-3 rounded-4 border border-white border-opacity-10">
<label class="form-check-label fw-semibold small text-white"
for="maintenance_mode_enabled">{{ __('ENABLE MODE') }}</label>
<input class="form-check-input ms-0" type="checkbox" role="switch"
id="maintenance_mode_enabled" name="maintenance_mode_enabled" value="1"
@checked(old('maintenance_mode_enabled', $settings['maintenance_mode_enabled'] ?? false))>
</div>
</div>
{{-- Config Sections --}}
<div class="card adminuiux-card mb-4">
<div class="card-body">
<h6 class="fw-bold mb-4 d-flex align-items-center gap-2">
<i class="bi bi-palette-fill text-primary"></i>
{{ __('Visual & Branding') }}
</h6>
<div class="mb-3">
<label class="form-label fw-semibold">{{ __('Main Headline') }}</label>
<input type="text" class="form-control" name="maintenance_mode_title"
value="{{ $settings['maintenance_mode_title'] ?? 'biiproject.com' }}">
</div>
<div class="mb-3">
<label class="form-label fw-semibold">{{ __('Description') }}</label>
<textarea class="form-control" name="maintenance_mode_message"
rows="3">{{ $settings['maintenance_mode_message'] ?? 'We are currently performing scheduled maintenance. We will be back shortly!' }}</textarea>
</div>
<div class="mb-0">
<label class="form-label fw-semibold mb-2">{{ __('Illustration / Logo') }}</label>
<input type="file" id="maintenance_mode_image" name="maintenance_mode_image"
accept="image/png,image/jpeg,image/svg+xml">
</div>
</div>
</div>
<div class="card adminuiux-card mb-4">
<div class="card-body">
<h6 class="fw-bold mb-4 d-flex align-items-center gap-2">
<i class="bi bi-clock-fill text-primary"></i>
{{ __('Time & Access') }}
</h6>
<div class="mb-3">
<label class="form-label fw-semibold">{{ __('Secret Bypass Key') }}</label>
<input type="text" class="form-control" name="maintenance_mode_secret"
placeholder="e.g. admin-only"
value="{{ $settings['maintenance_mode_secret'] ?? '' }}">
</div>
<div class="row g-2">
<div class="col-12 mb-2">
<label class="form-label fw-semibold">{{ __('End Time') }}</label>
<input type="datetime-local" class="form-control" name="maintenance_mode_end_at"
value="{{ !empty($settings['maintenance_mode_end_at']) ? date('Y-m-d\TH:i', strtotime($settings['maintenance_mode_end_at'])) : '' }}">
</div>
<div class="col-12">
<label class="form-label fw-semibold">{{ __('Retry Interval (Seconds)') }}</label>
<input type="number" class="form-control" name="maintenance_mode_retry"
value="{{ $settings['maintenance_mode_retry'] ?? 3600 }}">
</div>
</div>
</div>
</div>
<div class="card adminuiux-card mb-4 border-warning border-opacity-25 bg-warning bg-opacity-5">
<div class="card-body">
<h6 class="fw-bold mb-3 d-flex align-items-center gap-2 text-warning-emphasis">
<i class="bi bi-megaphone-fill"></i>
{{ __('Broadcast Warning') }}
</h6>
<p class="extra-small text-muted mb-4">
{{ __('Alert all active users before shutting down the system. They will receive a real-time notification.') }}
</p>
<div class="input-group">
<select class="form-select" id="broadcast_minutes">
<option value="1">1 {{ __('Minute') }}</option>
<option value="5" selected>5 {{ __('Minutes') }}</option>
<option value="10">10 {{ __('Minutes') }}</option>
<option value="30">30 {{ __('Minutes') }}</option>
</select>
<button type="button" class="btn btn-warning fw-bold px-3" id="btn-broadcast-warning">
<i class="bi bi-send-fill me-1"></i> {{ __('Send Alert') }}
</button>
</div>
</div>
</div>
@can('manage maintenance mode')
<div class="d-flex justify-content-end">
<button type="submit" class="btn btn-primary rounded-pill px-4 shadow-sm">
{{ __('Apply Configuration') }}
</button>
</div>
@endcan
</div>
{{-- Right: Live Preview (Col-8) --}}
<div class="col-lg-8 animate__animated animate__fadeIn">
<div class="mockup-container">
<div class="browser-mockup-frame">
{{-- Toolbar --}}
<div class="browser-top-bar">
<div class="browser-dots">
<div class="browser-dot" style="background: #ff5f56;"></div>
<div class="browser-dot" style="background: #ffbd2e;"></div>
<div class="browser-dot" style="background: #27c93f;"></div>
</div>
<div class="browser-url-field">
{{ url('/') }}
</div>
</div>
{{-- Viewport (Design from Image) --}}
<div class="viewport-preview">
<div class="visitor-card-premium">
{{-- Maintenance Pill --}}
<div class="under-maintenance-pill">
<span class="pulse-dot-red"></span>
{{ __('UNDER MAINTENANCE') }}
</div>
{{-- Logo Container --}}
<div class="mb-4 mx-auto" id="preview-mnt-image-container"
style="max-width: 140px; min-height: 80px; display: flex; align-items: center; justify-content: center;">
@php
$mnt_img = $settings['maintenance_mode_image'] ?? '';
$display_img = null;
if (!empty($mnt_img)) {
$display_img = str_starts_with($mnt_img, 'assets/') ? asset($mnt_img) : asset('storage/' . $mnt_img);
} elseif (!empty($settings['app_logo'])) {
$display_img = str_starts_with($settings['app_logo'], 'assets/') ? asset($settings['app_logo']) : asset('storage/' . $settings['app_logo']);
}
@endphp
@if($display_img)
<img src="{{ $display_img }}" class="img-fluid" id="mnt-preview-img" alt="Logo">
@else
<i class="bi bi-gear-wide-connected fs-1 text-secondary opacity-25"
style="font-size: 3.5rem !important;"></i>
@endif
</div>
{{-- Content --}}
<h2 class="fw-black mb-3 text-dark" id="preview-mnt-title"
style="font-family: 'Outfit', sans-serif;">
{{ $settings['maintenance_mode_title'] ?? 'biiproject.com' }}
</h2>
<p class="text-secondary small mb-5" id="preview-mnt-message"
style="line-height: 1.6; opacity: 0.8; font-weight: 500;">
{{ $settings['maintenance_mode_message'] ?? 'We are currently performing scheduled maintenance. We will be back shortly!' }}
</p>
{{-- Countdown Grid (Image Style) --}}
<div class="countdown-grid" id="preview-mnt-countdown">
@foreach(['Days', 'Hours', 'Mins', 'Secs'] as $label)
<div class="countdown-square shadow-lg">
<div class="countdown-number" id="cd-{{ strtolower($label) }}">00</div>
<div class="countdown-label">{{ $label }}</div>
</div>
@endforeach
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</form>
</div>
@push('scripts')
<script src="https://unpkg.com/filepond-plugin-image-preview/dist/filepond-plugin-image-preview.js" crossorigin="anonymous"></script>
<script src="https://unpkg.com/filepond/dist/filepond.js" crossorigin="anonymous"></script>
<script>
$(document).ready(function () {
// Initialize FilePond
FilePond.registerPlugin(FilePondPluginImagePreview);
const pond = FilePond.create(document.querySelector('#maintenance_mode_image'), {
name: 'maintenance_mode_image',
labelIdle: '<span class="text-muted small">Drop Logo or <span class="text-primary fw-bold">Browse</span></span>',
imagePreviewHeight: 120,
stylePanelLayout: 'compact',
});
// Form Handling
$('#maintenanceConfigForm').on('ajaxForm:beforeSend', function (e, formData) {
const files = pond.getFiles();
if (files.length > 0) {
formData.set('maintenance_mode_image', files[0].file);
}
});
$('#maintenanceConfigForm').on('ajaxForm:success', function (e, response) {
StandardSwal.fire({ title: 'Success!', text: response.message, icon: 'success', timer: 1500, showConfirmButton: false });
if (response.settings && response.settings.maintenance_mode_image) {
const path = response.settings.maintenance_mode_image;
const newUrl = path.startsWith('assets/') ? `/${path}` : `/storage/${path}`;
originalImgSrc = `${newUrl}?v=${new Date().getTime()}`;
}
if (response.hasOwnProperty('is_down')) {
setTimeout(() => location.reload(), 1000);
}
pond.removeFiles();
});
// Live Preview Sync
$('input[name="maintenance_mode_title"]').on('input', function () {
$('#preview-mnt-title').text($(this).val() || 'biiproject.com');
});
$('textarea[name="maintenance_mode_message"]').on('input', function () {
$('#preview-mnt-message').text($(this).val() || 'Maintenance in progress...');
});
// Countdown Logic
let countdownInterval;
function updateCountdown() {
const val = $('input[name="maintenance_mode_end_at"]').val();
if (!val) {
$('#preview-mnt-countdown').addClass('opacity-25');
return;
}
$('#preview-mnt-countdown').removeClass('opacity-25');
if (countdownInterval) clearInterval(countdownInterval);
const target = new Date(val).getTime();
countdownInterval = setInterval(() => {
const now = new Date().getTime();
const diff = target - now;
if (diff < 0) {
clearInterval(countdownInterval);
$('#cd-days, #cd-hours, #cd-mins, #cd-secs').text('00');
return;
}
const d = Math.floor(diff / (1000 * 60 * 60 * 24));
const h = Math.floor((diff % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
const m = Math.floor((diff % (1000 * 60 * 60)) / (1000 * 60));
const s = Math.floor((diff % (1000 * 60)) / 1000);
$('#cd-days').text(d.toString().padStart(2, '0'));
$('#cd-hours').text(h.toString().padStart(2, '0'));
$('#cd-mins').text(m.toString().padStart(2, '0'));
$('#cd-secs').text(s.toString().padStart(2, '0'));
}, 1000);
}
$('input[name="maintenance_mode_end_at"]').on('change', updateCountdown);
updateCountdown();
// Image Swap
let originalImgSrc = $('#mnt-preview-img').attr('src');
pond.on('addfile', (error, file) => {
if (!error) {
const url = URL.createObjectURL(file.file);
$('#preview-mnt-image-container').html(`<img src="${url}" class="img-fluid" id="mnt-preview-img" alt="Logo">`);
}
});
pond.on('removefile', () => {
if (originalImgSrc) {
$('#preview-mnt-image-container').html(`<img src="${originalImgSrc}" class="img-fluid" id="mnt-preview-img" alt="Logo">`);
} else {
$('#preview-mnt-image-container').html(`<i class="bi bi-gear-wide-connected fs-1 text-secondary opacity-25" style="font-size: 3.5rem !important;"></i>`);
}
});
// Broadcast Warning
$('#btn-broadcast-warning').on('click', function () {
const minutes = $('#broadcast_minutes').val();
const $btn = $(this);
StandardSwal.fire({
title: "{{ __('Send Broadcast Alert?') }}",
text: "{{ __('All active users will receive a warning about the upcoming maintenance.') }}",
icon: 'warning',
showCancelButton: true,
confirmButtonText: "{{ __('Send Alert') }}"
}).then((result) => {
if (result.isConfirmed) {
$btn.prop('disabled', true).html('<span class="spinner-border spinner-border-sm me-1"></span>');
$.post("{{ route('maintenance-mode.broadcast') }}", {
_token: "{{ csrf_token() }}",
minutes: minutes
}, function (response) {
StandardSwal.fire({ title: "{{ __('Success') }}", text: response.message, icon: 'success' });
}).always(() => $btn.prop('disabled', false).html('<i class="bi bi-send-fill me-1"></i> {{ __("Send Alert") }}'));
}
});
});
});
</script>
<style>
.x-small {
font-size: 11px;
}
</style>
@endpush
</x-app-layout>