Ajoute les mises à jour autonomes
Les installations hors YunoHost peuvent désormais vérifier et installer une release Forgejo avec sauvegarde et retour arrière complet.
This commit is contained in:
parent
f41504c852
commit
53e79a72d7
25 changed files with 1146 additions and 61 deletions
|
|
@ -89,7 +89,7 @@
|
|||
class="btn <?= $accountingActive ? 'btn-teal' : 'btn-outline-teal' ?> btn-sm me-2"
|
||||
href="/accounting?year=0">🧾 Comptabilité</a><?php endif; ?>
|
||||
|
||||
<div class="dropdown settings-dropdown"><button class="btn btn-dark btn-sm me-2 settings-trigger"
|
||||
<div class="dropdown settings-dropdown"><button class="btn btn-dark btn-sm me-2 settings-trigger position-relative"
|
||||
data-bs-toggle="dropdown" aria-expanded="false" title="<?= h(t('account.settings')) ?>"
|
||||
aria-label="<?= h(t('account.settings')) ?>"><svg xmlns="http://www.w3.org/2000/svg"
|
||||
class="icon m-0" width="22" height="22" viewBox="0 0 24 24" stroke-width="2"
|
||||
|
|
@ -98,7 +98,7 @@
|
|||
<path d="M12 15.5a3.5 3.5 0 1 0 0 -7a3.5 3.5 0 0 0 0 7z" />
|
||||
<path
|
||||
d="M19.4 15a1.65 1.65 0 0 0 .33 1.82l.06 .06a2 2 0 1 1 -2.83 2.83l-.06 -.06a1.65 1.65 0 0 0 -1.82 -.33a1.65 1.65 0 0 0 -1 1.51v.17a2 2 0 1 1 -4 0v-.09a1.65 1.65 0 0 0 -1.08 -1.51a1.65 1.65 0 0 0 -1.82 .33l-.06 .06a2 2 0 1 1 -2.83 -2.83l.06 -.06a1.65 1.65 0 0 0 .33 -1.82a1.65 1.65 0 0 0 -1.51 -1h-.17a2 2 0 1 1 0 -4h.09a1.65 1.65 0 0 0 1.51 -1.08a1.65 1.65 0 0 0 -.33 -1.82l-.06 -.06a2 2 0 1 1 2.83 -2.83l.06 .06a1.65 1.65 0 0 0 1.82 .33h.08a1.65 1.65 0 0 0 1 -1.51v-.17a2 2 0 1 1 4 0v.09a1.65 1.65 0 0 0 1 1.51a1.65 1.65 0 0 0 1.82 -.33l.06 -.06a2 2 0 1 1 2.83 2.83l-.06 .06a1.65 1.65 0 0 0 -.33 1.82v.08a1.65 1.65 0 0 0 1.51 1h.17a2 2 0 1 1 0 4h-.09a1.65 1.65 0 0 0 -1.51 1z" />
|
||||
</svg></button>
|
||||
</svg><?php if ($updateAvailable): ?><span class="position-absolute top-0 start-100 translate-middle p-1 bg-orange border border-light rounded-circle"><span class="visually-hidden"><?= h(t('updates.available_short')) ?></span></span><?php endif; ?></button>
|
||||
<div class="dropdown-menu dropdown-menu-end">
|
||||
<div class="dropdown-item-text">
|
||||
<div class="fw-bold">👤 <?= h($currentUser['display_name'] ?: $currentUser['username']) ?>
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ if (!function_exists('settingsNavigationLinks')) {
|
|||
'permissions' => ['/settings/permissions','🔐',t('settings.permissions'),'access'],
|
||||
'privacy' => ['/settings/privacy','🔏',t('settings.privacy'),'access'],
|
||||
'backups' => ['/settings/backups','💾',t('settings.backups'),'maintenance'],
|
||||
'updates' => ['/settings/updates','⬆️',t('settings.updates'),'maintenance'],
|
||||
'health' => ['/settings/health','🩺',t('settings.health'),'maintenance'],
|
||||
'notifications' => ['/settings/notifications','🔔',t('settings.notifications'),'maintenance'],
|
||||
'data' => ['/settings/data','🧪',t('settings.data'),'maintenance'],
|
||||
|
|
|
|||
|
|
@ -31,9 +31,9 @@
|
|||
<div class="col-lg-6">
|
||||
<section class="card h-100">
|
||||
<div class="card-body">
|
||||
<span class="badge bg-blue-lt mb-2">1.0.0-rc.15</span>
|
||||
<h3>Catalogue vétérinaire modifiable</h3>
|
||||
<p class="text-muted">Tarifs modifiables directement depuis leur ligne, tout en préservant les dépenses historiques.</p>
|
||||
<span class="badge bg-blue-lt mb-2">1.0.0-rc.16</span>
|
||||
<h3>Mises à jour autonomes</h3>
|
||||
<p class="text-muted">Installation vérifiée depuis Forgejo, sauvegarde obligatoire et retour automatique du code et des données en cas d’échec.</p>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
|
|
|
|||
|
|
@ -18,6 +18,11 @@ $accountingActive = str_starts_with($currentPath, '/accounting');
|
|||
$settingsActive = str_starts_with($currentPath, '/settings');
|
||||
$settingsMenuLinks = settingsNavigationLinks();
|
||||
$currentUser = Auth::user();
|
||||
$updateStatus = ['available' => false];
|
||||
if (Auth::is('admin') && class_exists(UpdateService::class)) {
|
||||
$updateStatus = UpdateService::refreshIfDue();
|
||||
}
|
||||
$updateAvailable = (bool) ($updateStatus['available'] ?? false);
|
||||
$pageName = trim((string)($title ?? ''));
|
||||
$documentTitle = $pageName !== '' ? $pageName . ' — Globinours' : 'Globinours';
|
||||
$pageDescription = trim((string)($pageDescription ?? ''));
|
||||
|
|
|
|||
|
|
@ -110,7 +110,7 @@ $formatSize = static function (int $bytes): string {
|
|||
<?php foreach ($backups as $backup): ?>
|
||||
<tr>
|
||||
<td class="text-nowrap"><?= h($backup['date']) ?></td>
|
||||
<td><strong><?= h($backup['name']) ?></strong><?php if (str_contains($backup['name'], 'avant-restauration')): ?>
|
||||
<td><strong><?= h($backup['name']) ?></strong><?php if (str_contains($backup['name'], '-avant-')): ?>
|
||||
<span class="badge bg-orange-lt"><?= h(t('backups.emergency')) ?></span><?php endif; ?></td>
|
||||
<td><?= h($formatSize((int)$backup['size'])) ?></td>
|
||||
<td>
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ $settingsSection = match(true) {
|
|||
str_starts_with($settingsPath, '/settings/lab-references') => 'lab-references',
|
||||
str_starts_with($settingsPath, '/settings/public-site') => 'public-site',
|
||||
str_starts_with($settingsPath, '/settings/backups') => 'backups',
|
||||
str_starts_with($settingsPath, '/settings/updates') => 'updates',
|
||||
str_starts_with($settingsPath, '/settings/health') => 'health',
|
||||
str_starts_with($settingsPath, '/settings/notifications') => 'notifications',
|
||||
str_starts_with($settingsPath, '/settings/audit') => 'audit',
|
||||
|
|
|
|||
92
app/Views/settings_updates.php
Normal file
92
app/Views/settings_updates.php
Normal file
|
|
@ -0,0 +1,92 @@
|
|||
<?php require __DIR__ . '/settings_nav.php'; ?>
|
||||
<div class="mb-3">
|
||||
<h2 class="mb-1"><?= h(t('updates.heading')) ?></h2>
|
||||
<div class="text-muted"><?= h(t('updates.intro')) ?></div>
|
||||
</div>
|
||||
|
||||
<?php if ($status === 'checked'): ?><div class="alert alert-success">✅ <?= h(t('updates.checked')) ?></div><?php endif; ?>
|
||||
<?php if ($status === 'installed'): ?><div class="alert alert-success">✅ <?= h(t('updates.installed', ['version' => (string) ($_GET['version'] ?? '')])) ?></div><?php endif; ?>
|
||||
<?php if ($error !== ''): ?><div class="alert alert-danger"><strong><?= h(t('updates.failed')) ?></strong> <?= h($error) ?></div><?php endif; ?>
|
||||
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-lg-7">
|
||||
<section class="card h-100">
|
||||
<div class="card-header"><h3 class="card-title"><?= h(t('updates.version_status')) ?></h3></div>
|
||||
<div class="card-body">
|
||||
<dl class="row mb-0">
|
||||
<dt class="col-sm-5"><?= h(t('updates.installed_version')) ?></dt><dd class="col-sm-7"><code><?= h($updateStatus['current']) ?></code></dd>
|
||||
<dt class="col-sm-5"><?= h(t('updates.available_version')) ?></dt><dd class="col-sm-7"><?= $updateStatus['latest'] !== '' ? '<code>' . h($updateStatus['latest']) . '</code>' : '—' ?></dd>
|
||||
<dt class="col-sm-5"><?= h(t('updates.last_check')) ?></dt><dd class="col-sm-7"><?= h($updateStatus['checked_at'] ?: '—') ?></dd>
|
||||
</dl>
|
||||
<?php if ($updateStatus['available']): ?><div class="alert alert-warning mt-3 mb-0">⬆️ <?= h(t('updates.available', ['version' => $updateStatus['latest']])) ?></div>
|
||||
<?php elseif ($updateStatus['latest'] !== ''): ?><div class="alert alert-success mt-3 mb-0">✅ <?= h(t('updates.current')) ?></div><?php endif; ?>
|
||||
<?php if ($updateStatus['error'] !== ''): ?><div class="alert alert-secondary mt-3 mb-0"><?= h(t('updates.check_error', ['error' => $updateStatus['error']])) ?></div><?php endif; ?>
|
||||
</div>
|
||||
<div class="card-footer d-flex flex-wrap gap-2 justify-content-between">
|
||||
<form method="post" action="/settings/updates/check"><button class="btn btn-outline-primary">🔄 <?= h(t('updates.check_now')) ?></button></form>
|
||||
<?php if ($updateStatus['notes_url'] !== ''): ?><a class="btn btn-link" href="<?= h($updateStatus['notes_url']) ?>" target="_blank" rel="noopener noreferrer"><?= h(t('updates.release_notes')) ?></a><?php endif; ?>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
<div class="col-lg-5">
|
||||
<section class="card h-100">
|
||||
<div class="card-header"><h3 class="card-title"><?= h(t('updates.preflight')) ?></h3></div>
|
||||
<div class="list-group list-group-flush">
|
||||
<?php $ready = true; foreach ($checks as $check): $ready = $ready && $check['ok']; ?><div class="list-group-item d-flex justify-content-between gap-3">
|
||||
<span><?= $check['ok'] ? '✅' : '❌' ?> <?= h($check['label']) ?></span><small class="text-muted text-end"><?= h($check['detail']) ?></small>
|
||||
</div><?php endforeach; ?>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-3 mb-3">
|
||||
<div class="col-lg-6">
|
||||
<section class="card h-100 <?= $updateStatus['available'] ? 'border-primary' : '' ?>">
|
||||
<div class="card-header"><h3 class="card-title"><?= h(t('updates.automatic')) ?></h3></div>
|
||||
<form method="post" action="/settings/updates/install" onsubmit="return confirm(<?= h(json_encode(t('updates.install_confirm'), JSON_HEX_APOS | JSON_HEX_QUOT)) ?>)">
|
||||
<div class="card-body">
|
||||
<p><?= h(t('updates.automatic_help')) ?></p>
|
||||
<label class="form-label" for="updatePassword"><?= h(t('updates.password')) ?></label>
|
||||
<input class="form-control" id="updatePassword" type="password" name="password" autocomplete="current-password" required>
|
||||
</div>
|
||||
<div class="card-footer text-end"><button class="btn btn-primary" <?= !$ready || !$updateStatus['available'] ? 'disabled' : '' ?>><?= h(t('updates.backup_and_install')) ?></button></div>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
<div class="col-lg-6">
|
||||
<section class="card h-100">
|
||||
<div class="card-header"><h3 class="card-title"><?= h(t('updates.manual_archive')) ?></h3></div>
|
||||
<form method="post" action="/settings/updates/upload" enctype="multipart/form-data" onsubmit="return confirm(<?= h(json_encode(t('updates.install_confirm'), JSON_HEX_APOS | JSON_HEX_QUOT)) ?>)">
|
||||
<div class="card-body">
|
||||
<p><?= h(t('updates.manual_help')) ?></p>
|
||||
<label class="form-label" for="updateRelease"><?= h(t('updates.archive')) ?></label>
|
||||
<input class="form-control" id="updateRelease" type="file" name="release" accept=".tar.gz,.tgz,application/gzip" required>
|
||||
<label class="form-label mt-3" for="updateHash"><?= h(t('updates.sha256')) ?></label>
|
||||
<input class="form-control font-monospace" id="updateHash" name="sha256" minlength="64" maxlength="64" pattern="[a-fA-F0-9]{64}" required>
|
||||
<label class="form-label mt-3" for="uploadPassword"><?= h(t('updates.password')) ?></label>
|
||||
<input class="form-control" id="uploadPassword" type="password" name="password" autocomplete="current-password" required>
|
||||
</div>
|
||||
<div class="card-footer text-end"><button class="btn btn-outline-primary" <?= !$ready ? 'disabled' : '' ?>><?= h(t('updates.install_archive')) ?></button></div>
|
||||
</form>
|
||||
</section>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<?php if ($lastUpdate): ?><section class="card border-warning">
|
||||
<div class="card-header"><h3 class="card-title"><?= h(t('updates.rollback')) ?></h3></div>
|
||||
<form method="post" action="/settings/updates/rollback" onsubmit="return confirm(<?= h(json_encode(t('updates.rollback_confirm'), JSON_HEX_APOS | JSON_HEX_QUOT)) ?>)">
|
||||
<div class="card-body">
|
||||
<p><?= h(t('updates.rollback_help', ['version' => (string) $lastUpdate['from'], 'date' => (string) $lastUpdate['installed_at']])) ?></p>
|
||||
<div class="alert alert-warning"><?= h(t('updates.rollback_data_warning')) ?></div>
|
||||
<label class="form-label" for="rollbackPassword"><?= h(t('updates.password')) ?></label>
|
||||
<input class="form-control" id="rollbackPassword" type="password" name="password" autocomplete="current-password" required>
|
||||
</div>
|
||||
<div class="card-footer text-end"><button class="btn btn-warning"><?= h(t('updates.rollback_button', ['version' => (string) $lastUpdate['from']])) ?></button></div>
|
||||
</form>
|
||||
</section><?php endif; ?>
|
||||
|
||||
<div class="d-flex flex-wrap justify-content-between gap-2 mt-3">
|
||||
<div class="text-muted small"><?= h(t('updates.privacy')) ?><br><code><?= h(UpdateService::repositoryUrl()) ?></code></div>
|
||||
<?php if (UpdateService::logPath()): ?><a class="btn btn-sm btn-outline-secondary" href="/settings/updates/log">⬇ <?= h(t('updates.download_log')) ?></a><?php endif; ?>
|
||||
</div>
|
||||
Loading…
Reference in a new issue