Files
CABM-IT/resources/views/components/settings/⚡env.blade.php
T
mrKamooandClaude Sonnet 5 c35833ed0b Remplace l'édition directe du .env par des réglages en base de données
L'édition de fichier nécessitait que le process web ait un accès en
écriture sur .env — problématique en prod (permissions, plusieurs
instances). À la place :

- Table `settings` (clé/valeur) + SettingsRegistry, une liste blanche
  explicite des réglages modifiables (mail, OnlyOffice, GLPI, nom/URL de
  l'app) mappés vers leur chemin de config Laravel.
- Volontairement exclu : tout ce qui touche à l'infrastructure (APP_KEY,
  DB_*, SESSION_DRIVER, CACHE_STORE, QUEUE_CONNECTION...) — ces valeurs
  sont nécessaires avant même qu'une connexion BDD existe, donc une
  surcharge en base ne serait pas fiable et reste réservée au .env.
- AppServiceProvider applique les surcharges au boot (cache 5 min,
  invalidé immédiatement à l'enregistrement) ; le reste du code continue
  de lire config() normalement, sans changement ailleurs.
- Tant qu'aucun réglage n'est enregistré, le comportement reste identique
  au .env actuel — transition sans rupture.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
2026-08-20 09:21:15 +02:00

139 lines
6.0 KiB
PHP

<?php
use App\Models\Setting;
use App\Services\SettingsRegistry;
use Illuminate\Support\Facades\Cache;
use Livewire\Component;
new class extends Component
{
/** @var array<string, string> */
public array $values = [];
public function mount(): void
{
$this->loadValues();
}
public function save(): void
{
foreach ($this->values as $key => $value) {
if (str_contains($value, "\n") || str_contains($value, "\r")) {
$this->addError('values', "La valeur de {$key} ne peut pas contenir de retour à la ligne.");
return;
}
}
foreach (SettingsRegistry::configMap() as $envKey => $path) {
$value = trim($this->values[$envKey] ?? '');
Setting::updateOrCreate(['key' => $envKey], ['value' => $value === '' ? null : $value]);
}
Cache::forget(SettingsRegistry::CACHE_KEY);
session()->flash('status', 'Configuration enregistrée. Les nouvelles valeurs sont actives immédiatement.');
$this->loadValues();
}
public function resetToDefault(string $envKey): void
{
if (! array_key_exists($envKey, SettingsRegistry::configMap())) {
return;
}
Setting::where('key', $envKey)->delete();
Cache::forget(SettingsRegistry::CACHE_KEY);
session()->flash('status', "Valeur par défaut restaurée pour {$envKey}.");
$this->loadValues();
}
protected function loadValues(): void
{
$stored = Setting::query()->pluck('value', 'key');
foreach (SettingsRegistry::configMap() as $envKey => $path) {
$this->values[$envKey] = $stored[$envKey] ?? (string) (config($path) ?? '');
}
}
};
?>
<div class="max-w-3xl">
<h1 class="mb-2 text-2xl font-semibold text-slate-900">Configuration</h1>
<p class="mb-6 text-sm text-slate-500">
Ces réglages sont stockés en base de données et prennent effet immédiatement, sans
redémarrage. Les paramètres d'infrastructure (base de données, clé de chiffrement,
pilote de session…) ne sont pas modifiables ici — ils doivent être changés directement
dans le fichier <code class="rounded bg-slate-100 px-1 py-0.5">.env</code> du serveur.
</p>
@if (session('status'))
<div class="mb-4 rounded border border-emerald-200 bg-emerald-50 px-4 py-2 text-sm text-emerald-700">
{{ session('status') }}
</div>
@endif
@error('values')
<div class="mb-4 rounded border border-red-200 bg-red-50 px-4 py-2 text-sm text-red-700">
{{ $message }}
</div>
@enderror
<form wire:submit="save" class="space-y-8 rounded border border-slate-200 bg-white p-6">
@foreach (\App\Services\SettingsRegistry::SETTINGS as $section => $keys)
<div>
<p class="mb-3 text-xs font-semibold uppercase tracking-wide text-slate-400">{{ $section }}</p>
<div class="space-y-1.5">
@foreach ($keys as $envKey => $meta)
<div class="flex items-center gap-3 py-1">
<label for="setting-{{ $envKey }}" class="w-48 shrink-0 text-sm text-slate-700">
{{ $meta['label'] }}
</label>
@if (\App\Services\SettingsRegistry::isSensitive($envKey))
<div x-data="{ show: false }" class="flex flex-1 items-center gap-2">
<input :type="show ? 'text' : 'password'" id="setting-{{ $envKey }}"
wire:model="values.{{ $envKey }}" autocomplete="off"
class="w-full rounded border-slate-300 font-mono text-sm shadow-sm focus:border-slate-500 focus:ring-slate-500">
<button type="button" x-on:click="show = !show"
class="shrink-0 text-xs text-slate-500 hover:text-slate-900">
<span x-text="show ? 'Masquer' : 'Révéler'"></span>
</button>
</div>
@else
<input type="text" id="setting-{{ $envKey }}"
wire:model="values.{{ $envKey }}"
class="flex-1 rounded border-slate-300 font-mono text-sm shadow-sm focus:border-slate-500 focus:ring-slate-500">
@endif
<button type="button" wire:click="resetToDefault('{{ $envKey }}')"
wire:confirm="Revenir à la valeur par défaut de {{ $envKey }} (celle du .env) ?"
class="shrink-0 text-xs text-slate-400 hover:text-slate-700">
Réinitialiser
</button>
</div>
@endforeach
</div>
</div>
@endforeach
<div class="flex items-center gap-3 border-t border-slate-100 pt-6">
<button type="submit" wire:loading.attr="disabled" wire:target="save"
class="inline-flex items-center gap-2 rounded bg-slate-900 px-4 py-2 text-sm font-medium text-white hover:bg-slate-700 disabled:cursor-not-allowed disabled:opacity-60">
<svg wire:loading wire:target="save" class="h-4 w-4 animate-spin" viewBox="0 0 24 24" fill="none">
<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>
<span wire:loading.remove wire:target="save">Enregistrer</span>
<span wire:loading wire:target="save">Enregistrement…</span>
</button>
</div>
</form>
</div>