mirror of
https://github.com/Part-DB/Part-DB-server.git
synced 2026-02-11 12:09:36 +00:00
Only use the simple maintenance page, and made this a bit more generic
This commit is contained in:
parent
1adfec16e2
commit
58d574a33a
3 changed files with 10 additions and 346 deletions
|
|
@ -37,8 +37,7 @@ use Twig\Environment;
|
||||||
*/
|
*/
|
||||||
readonly class MaintenanceModeSubscriber implements EventSubscriberInterface
|
readonly class MaintenanceModeSubscriber implements EventSubscriberInterface
|
||||||
{
|
{
|
||||||
public function __construct(private UpdateExecutor $updateExecutor,
|
public function __construct(private UpdateExecutor $updateExecutor)
|
||||||
private Environment $twig)
|
|
||||||
{
|
{
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
@ -48,7 +47,6 @@ readonly class MaintenanceModeSubscriber implements EventSubscriberInterface
|
||||||
return [
|
return [
|
||||||
// High priority to run before other listeners
|
// High priority to run before other listeners
|
||||||
KernelEvents::REQUEST => ['onKernelRequest', 512], //High priority to run before other listeners
|
KernelEvents::REQUEST => ['onKernelRequest', 512], //High priority to run before other listeners
|
||||||
KernelEvents::RESPONSE => ['onKernelResponse', -512] // Low priority to run after other listeners
|
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -71,13 +69,12 @@ readonly class MaintenanceModeSubscriber implements EventSubscriberInterface
|
||||||
|
|
||||||
// Get maintenance info
|
// Get maintenance info
|
||||||
$maintenanceInfo = $this->updateExecutor->getMaintenanceInfo();
|
$maintenanceInfo = $this->updateExecutor->getMaintenanceInfo();
|
||||||
$lockInfo = $this->updateExecutor->getLockInfo();
|
|
||||||
|
|
||||||
// Calculate how long the update has been running
|
// Calculate how long the update has been running
|
||||||
$duration = null;
|
$duration = null;
|
||||||
if ($lockInfo && isset($lockInfo['started_at'])) {
|
if ($maintenanceInfo && isset($maintenanceInfo['enabled_at'])) {
|
||||||
try {
|
try {
|
||||||
$startedAt = new \DateTime($lockInfo['started_at']);
|
$startedAt = new \DateTime($maintenanceInfo['enabled_at']);
|
||||||
$now = new \DateTime();
|
$now = new \DateTime();
|
||||||
$duration = $now->getTimestamp() - $startedAt->getTimestamp();
|
$duration = $now->getTimestamp() - $startedAt->getTimestamp();
|
||||||
} catch (\Exception) {
|
} catch (\Exception) {
|
||||||
|
|
@ -85,17 +82,7 @@ readonly class MaintenanceModeSubscriber implements EventSubscriberInterface
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Try to render the Twig template, fall back to simple HTML
|
$content = $this->getSimpleMaintenanceHtml($maintenanceInfo, $duration);
|
||||||
try {
|
|
||||||
$content = $this->twig->render('maintenance/maintenance.html.twig', [
|
|
||||||
'reason' => $maintenanceInfo['reason'] ?? 'Maintenance in progress',
|
|
||||||
'started_at' => $maintenanceInfo['enabled_at'] ?? null,
|
|
||||||
'duration' => $duration,
|
|
||||||
]);
|
|
||||||
} catch (\Exception) {
|
|
||||||
// Fallback to simple HTML if Twig fails
|
|
||||||
$content = $this->getSimpleMaintenanceHtml($maintenanceInfo, $duration);
|
|
||||||
}
|
|
||||||
|
|
||||||
$response = new Response($content, Response::HTTP_SERVICE_UNAVAILABLE);
|
$response = new Response($content, Response::HTTP_SERVICE_UNAVAILABLE);
|
||||||
$response->headers->set('Retry-After', '30');
|
$response->headers->set('Retry-After', '30');
|
||||||
|
|
@ -104,28 +91,6 @@ readonly class MaintenanceModeSubscriber implements EventSubscriberInterface
|
||||||
$event->setResponse($response);
|
$event->setResponse($response);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function onKernelResponse(ResponseEvent $event)
|
|
||||||
{
|
|
||||||
// Only handle main requests
|
|
||||||
if (!$event->isMainRequest()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Skip if not in maintenance mode
|
|
||||||
if (!$this->updateExecutor->isMaintenanceMode()) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Allow CLI requests
|
|
||||||
if (PHP_SAPI === 'cli') {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Remove all Content-Security-Policy headers to allow loading resources during maintenance
|
|
||||||
$response = $event->getResponse();
|
|
||||||
$response->headers->remove('Content-Security-Policy');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Generate a simple maintenance page HTML without Twig.
|
* Generate a simple maintenance page HTML without Twig.
|
||||||
*/
|
*/
|
||||||
|
|
@ -134,6 +99,8 @@ readonly class MaintenanceModeSubscriber implements EventSubscriberInterface
|
||||||
$reason = htmlspecialchars($maintenanceInfo['reason'] ?? 'Update in progress');
|
$reason = htmlspecialchars($maintenanceInfo['reason'] ?? 'Update in progress');
|
||||||
$durationText = $duration !== null ? sprintf('%d seconds', $duration) : 'a moment';
|
$durationText = $duration !== null ? sprintf('%d seconds', $duration) : 'a moment';
|
||||||
|
|
||||||
|
$startDateStr = $maintenanceInfo['enabled_at'] ?? 'unknown time';
|
||||||
|
|
||||||
return <<<HTML
|
return <<<HTML
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html lang="en">
|
<html lang="en">
|
||||||
|
|
@ -233,7 +200,7 @@ readonly class MaintenanceModeSubscriber implements EventSubscriberInterface
|
||||||
<div class="icon">
|
<div class="icon">
|
||||||
<span class="spinner">⚙️</span>
|
<span class="spinner">⚙️</span>
|
||||||
</div>
|
</div>
|
||||||
<h1>Part-DB is Updating</h1>
|
<h1>Part-DB is under maintenance</h1>
|
||||||
<p>We're making things better. This should only take a moment.</p>
|
<p>We're making things better. This should only take a moment.</p>
|
||||||
|
|
||||||
<div class="reason">
|
<div class="reason">
|
||||||
|
|
@ -245,7 +212,9 @@ readonly class MaintenanceModeSubscriber implements EventSubscriberInterface
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p class="info">
|
<p class="info">
|
||||||
Update running for <span class="duration">{$durationText}</span><br>
|
Maintenance mode active since <span class="duration">{$startDateStr}</span><br>
|
||||||
|
<br>
|
||||||
|
Started <span class="duration">{$durationText}</span> ago<br>
|
||||||
<small>This page will automatically refresh every 15 seconds.</small>
|
<small>This page will automatically refresh every 15 seconds.</small>
|
||||||
</p>
|
</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -1,251 +0,0 @@
|
||||||
<!DOCTYPE html>
|
|
||||||
<html lang="en" data-bs-theme="dark">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8">
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
||||||
<meta http-equiv="refresh" content="15">
|
|
||||||
<title>Part-DB - {% trans %}update_manager.maintenance.title{% endtrans %}</title>
|
|
||||||
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.2/dist/css/bootstrap.min.css" rel="stylesheet">
|
|
||||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css">
|
|
||||||
<style>
|
|
||||||
body {
|
|
||||||
background: linear-gradient(135deg, #1a1a2e 0%, #16213e 50%, #0f3460 100%);
|
|
||||||
min-height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.maintenance-card {
|
|
||||||
background: rgba(255, 255, 255, 0.05);
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
||||||
border-radius: 20px;
|
|
||||||
padding: 50px;
|
|
||||||
max-width: 550px;
|
|
||||||
text-align: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-container {
|
|
||||||
width: 120px;
|
|
||||||
height: 120px;
|
|
||||||
margin: 0 auto 30px;
|
|
||||||
background: linear-gradient(135deg, #00d4ff 0%, #00ff88 100%);
|
|
||||||
border-radius: 50%;
|
|
||||||
display: flex;
|
|
||||||
justify-content: center;
|
|
||||||
align-items: center;
|
|
||||||
animation: pulse 2s infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
.icon-container i {
|
|
||||||
font-size: 50px;
|
|
||||||
color: #1a1a2e;
|
|
||||||
animation: spin 3s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes pulse {
|
|
||||||
0%, 100% { transform: scale(1); box-shadow: 0 0 0 0 rgba(0, 212, 255, 0.4); }
|
|
||||||
50% { transform: scale(1.05); box-shadow: 0 0 30px 10px rgba(0, 212, 255, 0.2); }
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
from { transform: rotate(0deg); }
|
|
||||||
to { transform: rotate(360deg); }
|
|
||||||
}
|
|
||||||
|
|
||||||
h1 {
|
|
||||||
color: #00d4ff;
|
|
||||||
font-weight: 600;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.reason-badge {
|
|
||||||
background: rgba(0, 212, 255, 0.15);
|
|
||||||
border: 1px solid rgba(0, 212, 255, 0.3);
|
|
||||||
padding: 12px 24px;
|
|
||||||
border-radius: 30px;
|
|
||||||
display: inline-block;
|
|
||||||
margin: 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-container {
|
|
||||||
margin: 30px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress {
|
|
||||||
height: 8px;
|
|
||||||
background: rgba(255, 255, 255, 0.1);
|
|
||||||
border-radius: 4px;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.progress-bar {
|
|
||||||
background: linear-gradient(90deg, #00d4ff, #00ff88);
|
|
||||||
animation: progressAnim 2.5s ease-in-out infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes progressAnim {
|
|
||||||
0% { width: 0%; margin-left: 0%; }
|
|
||||||
50% { width: 40%; margin-left: 30%; }
|
|
||||||
100% { width: 0%; margin-left: 100%; }
|
|
||||||
}
|
|
||||||
|
|
||||||
.timer {
|
|
||||||
font-family: 'SF Mono', 'Consolas', monospace;
|
|
||||||
font-size: 2rem;
|
|
||||||
color: #00ff88;
|
|
||||||
margin: 20px 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-steps {
|
|
||||||
text-align: left;
|
|
||||||
margin: 30px 0;
|
|
||||||
padding: 20px;
|
|
||||||
background: rgba(0, 0, 0, 0.2);
|
|
||||||
border-radius: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-step {
|
|
||||||
padding: 8px 0;
|
|
||||||
color: rgba(255, 255, 255, 0.6);
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-step i {
|
|
||||||
width: 24px;
|
|
||||||
margin-right: 12px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-step.active {
|
|
||||||
color: #00d4ff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.status-step.completed {
|
|
||||||
color: #00ff88;
|
|
||||||
}
|
|
||||||
|
|
||||||
.refresh-info {
|
|
||||||
color: rgba(255, 255, 255, 0.5);
|
|
||||||
font-size: 0.85rem;
|
|
||||||
}
|
|
||||||
|
|
||||||
.refresh-info i {
|
|
||||||
animation: spin 2s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
.logo {
|
|
||||||
opacity: 0.3;
|
|
||||||
margin-top: 30px;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<div class="maintenance-card">
|
|
||||||
<div class="icon-container">
|
|
||||||
<i class="fas fa-cog"></i>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<h1>{% trans %}update_manager.maintenance.heading{% endtrans %}</h1>
|
|
||||||
|
|
||||||
<p class="text-secondary fs-5">
|
|
||||||
{% trans %}update_manager.maintenance.description{% endtrans %}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div class="reason-badge">
|
|
||||||
<i class="fas fa-arrow-up me-2"></i>
|
|
||||||
{{ reason }}
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="progress-container">
|
|
||||||
<div class="progress">
|
|
||||||
<div class="progress-bar" role="progressbar"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="status-steps">
|
|
||||||
<div class="status-step" id="step-backup">
|
|
||||||
<i class="fas fa-database"></i>
|
|
||||||
<span>{% trans %}update_manager.maintenance.step_backup{% endtrans %}</span>
|
|
||||||
</div>
|
|
||||||
<div class="status-step" id="step-download">
|
|
||||||
<i class="fas fa-download"></i>
|
|
||||||
<span>{% trans %}update_manager.maintenance.step_download{% endtrans %}</span>
|
|
||||||
</div>
|
|
||||||
<div class="status-step" id="step-install">
|
|
||||||
<i class="fas fa-box-open"></i>
|
|
||||||
<span>{% trans %}update_manager.maintenance.step_install{% endtrans %}</span>
|
|
||||||
</div>
|
|
||||||
<div class="status-step" id="step-migrate">
|
|
||||||
<i class="fas fa-database"></i>
|
|
||||||
<span>{% trans %}update_manager.maintenance.step_migrate{% endtrans %}</span>
|
|
||||||
</div>
|
|
||||||
<div class="status-step" id="step-cache">
|
|
||||||
<i class="fas fa-sync"></i>
|
|
||||||
<span>{% trans %}update_manager.maintenance.step_cache{% endtrans %}</span>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
{% if duration is not null %}
|
|
||||||
<div class="timer" id="timer">
|
|
||||||
{{ duration // 60 }}:{{ '%02d'|format(duration % 60) }}
|
|
||||||
</div>
|
|
||||||
{% endif %}
|
|
||||||
|
|
||||||
<p class="refresh-info">
|
|
||||||
<i class="fas fa-sync-alt me-1"></i>
|
|
||||||
{% trans %}update_manager.maintenance.auto_refresh{% endtrans %}
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<div class="logo">
|
|
||||||
<i class="fa fa-microchip me-2"></i> Part-DB
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<script>
|
|
||||||
// Simulate step progression
|
|
||||||
const steps = ['step-backup', 'step-download', 'step-install', 'step-migrate', 'step-cache'];
|
|
||||||
let currentStep = 0;
|
|
||||||
|
|
||||||
function updateSteps() {
|
|
||||||
steps.forEach((stepId, index) => {
|
|
||||||
const step = document.getElementById(stepId);
|
|
||||||
if (index < currentStep) {
|
|
||||||
step.classList.add('completed');
|
|
||||||
step.classList.remove('active');
|
|
||||||
step.querySelector('i').className = 'fas fa-check-circle';
|
|
||||||
} else if (index === currentStep) {
|
|
||||||
step.classList.add('active');
|
|
||||||
step.querySelector('i').className = 'fas fa-spinner fa-spin';
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
currentStep = (currentStep + 1) % (steps.length + 1);
|
|
||||||
if (currentStep === 0) {
|
|
||||||
steps.forEach(stepId => {
|
|
||||||
const step = document.getElementById(stepId);
|
|
||||||
step.classList.remove('completed', 'active');
|
|
||||||
step.querySelector('i').className = step.querySelector('i').className.replace('fa-check-circle', 'fas');
|
|
||||||
step.querySelector('i').className = step.querySelector('i').className.replace('fa-spinner fa-spin', 'fas');
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setInterval(updateSteps, 3000);
|
|
||||||
|
|
||||||
// Update timer
|
|
||||||
{% if duration is not null %}
|
|
||||||
let seconds = {{ duration }};
|
|
||||||
const timerEl = document.getElementById('timer');
|
|
||||||
|
|
||||||
setInterval(() => {
|
|
||||||
seconds++;
|
|
||||||
const mins = Math.floor(seconds / 60);
|
|
||||||
const secs = seconds % 60;
|
|
||||||
timerEl.textContent = `${mins}:${secs.toString().padStart(2, '0')}`;
|
|
||||||
}, 1000);
|
|
||||||
{% endif %}
|
|
||||||
</script>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
@ -14718,60 +14718,6 @@ Buerklin-API Authentication server:
|
||||||
<target>bytes</target>
|
<target>bytes</target>
|
||||||
</segment>
|
</segment>
|
||||||
</unit>
|
</unit>
|
||||||
<unit id="kVfrQQ_" name="update_manager.maintenance.title">
|
|
||||||
<segment state="translated">
|
|
||||||
<source>update_manager.maintenance.title</source>
|
|
||||||
<target>Maintenance</target>
|
|
||||||
</segment>
|
|
||||||
</unit>
|
|
||||||
<unit id="DJPbvVc" name="update_manager.maintenance.heading">
|
|
||||||
<segment state="translated">
|
|
||||||
<source>update_manager.maintenance.heading</source>
|
|
||||||
<target>Part-DB is Updating</target>
|
|
||||||
</segment>
|
|
||||||
</unit>
|
|
||||||
<unit id="x2kQd7v" name="update_manager.maintenance.description">
|
|
||||||
<segment state="translated">
|
|
||||||
<source>update_manager.maintenance.description</source>
|
|
||||||
<target>We're installing updates to make Part-DB even better. This should only take a moment.</target>
|
|
||||||
</segment>
|
|
||||||
</unit>
|
|
||||||
<unit id="LF4Ebik" name="update_manager.maintenance.step_backup">
|
|
||||||
<segment state="translated">
|
|
||||||
<source>update_manager.maintenance.step_backup</source>
|
|
||||||
<target>Creating backup</target>
|
|
||||||
</segment>
|
|
||||||
</unit>
|
|
||||||
<unit id="uioqI08" name="update_manager.maintenance.step_download">
|
|
||||||
<segment state="translated">
|
|
||||||
<source>update_manager.maintenance.step_download</source>
|
|
||||||
<target>Downloading updates</target>
|
|
||||||
</segment>
|
|
||||||
</unit>
|
|
||||||
<unit id="lNjhpKA" name="update_manager.maintenance.step_install">
|
|
||||||
<segment state="translated">
|
|
||||||
<source>update_manager.maintenance.step_install</source>
|
|
||||||
<target>Installing files</target>
|
|
||||||
</segment>
|
|
||||||
</unit>
|
|
||||||
<unit id="SfP.cFa" name="update_manager.maintenance.step_migrate">
|
|
||||||
<segment state="translated">
|
|
||||||
<source>update_manager.maintenance.step_migrate</source>
|
|
||||||
<target>Running migrations</target>
|
|
||||||
</segment>
|
|
||||||
</unit>
|
|
||||||
<unit id="u_8lAEw" name="update_manager.maintenance.step_cache">
|
|
||||||
<segment state="translated">
|
|
||||||
<source>update_manager.maintenance.step_cache</source>
|
|
||||||
<target>Clearing cache</target>
|
|
||||||
</segment>
|
|
||||||
</unit>
|
|
||||||
<unit id="ys_sMwM" name="update_manager.maintenance.auto_refresh">
|
|
||||||
<segment state="translated">
|
|
||||||
<source>update_manager.maintenance.auto_refresh</source>
|
|
||||||
<target>This page will refresh automatically when the update is complete.</target>
|
|
||||||
</segment>
|
|
||||||
</unit>
|
|
||||||
<unit id="Gt.91s_" name="perm.system.manage_updates">
|
<unit id="Gt.91s_" name="perm.system.manage_updates">
|
||||||
<segment state="translated">
|
<segment state="translated">
|
||||||
<source>perm.system.manage_updates</source>
|
<source>perm.system.manage_updates</source>
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue