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>
This commit is contained in:
co-authored by
Claude Sonnet 5
parent
da589d0627
commit
c35833ed0b
@@ -0,0 +1,15 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Attributes\Fillable;
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A key/value override for a whitelisted config path — see SettingsRegistry.
|
||||||
|
* Absence of a row (or a null value) means "use the .env/config default".
|
||||||
|
*/
|
||||||
|
#[Fillable(['key', 'value'])]
|
||||||
|
class Setting extends Model
|
||||||
|
{
|
||||||
|
}
|
||||||
@@ -2,7 +2,11 @@
|
|||||||
|
|
||||||
namespace App\Providers;
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use App\Models\Setting;
|
||||||
|
use App\Services\SettingsRegistry;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
use Throwable;
|
||||||
|
|
||||||
class AppServiceProvider extends ServiceProvider
|
class AppServiceProvider extends ServiceProvider
|
||||||
{
|
{
|
||||||
@@ -11,7 +15,35 @@ class AppServiceProvider extends ServiceProvider
|
|||||||
*/
|
*/
|
||||||
public function register(): void
|
public function register(): void
|
||||||
{
|
{
|
||||||
//
|
$this->applySettingOverrides();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Overlay database-stored settings (see SettingsRegistry) onto config,
|
||||||
|
* so the rest of the app keeps reading config('mail.from.address') etc.
|
||||||
|
* without knowing whether the value came from .env or the settings UI.
|
||||||
|
* Silently does nothing if the settings table doesn't exist yet (e.g.
|
||||||
|
* during the very first `migrate` on a fresh install).
|
||||||
|
*/
|
||||||
|
protected function applySettingOverrides(): void
|
||||||
|
{
|
||||||
|
try {
|
||||||
|
$overrides = Cache::remember(
|
||||||
|
SettingsRegistry::CACHE_KEY,
|
||||||
|
300,
|
||||||
|
fn () => Setting::query()->whereNotNull('value')->pluck('value', 'key')->all(),
|
||||||
|
);
|
||||||
|
} catch (Throwable) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
$map = SettingsRegistry::configMap();
|
||||||
|
|
||||||
|
foreach ($overrides as $key => $value) {
|
||||||
|
if (isset($map[$key]) && $value !== '') {
|
||||||
|
config([$map[$key] => $value]);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,160 +0,0 @@
|
|||||||
<?php
|
|
||||||
|
|
||||||
namespace App\Services;
|
|
||||||
|
|
||||||
use RuntimeException;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Reads and rewrites the application's .env file line-by-line, preserving
|
|
||||||
* comments, blank lines, and formatting for anything it doesn't touch.
|
|
||||||
*/
|
|
||||||
class EnvFileManager
|
|
||||||
{
|
|
||||||
/**
|
|
||||||
* Never editable from the admin UI: getting any of these wrong can break
|
|
||||||
* the database connection, invalidate encrypted sessions/cookies, or
|
|
||||||
* otherwise lock an administrator out of the app entirely. Change these
|
|
||||||
* via direct file/shell access instead.
|
|
||||||
*/
|
|
||||||
public const PROTECTED_KEYS = [
|
|
||||||
'APP_KEY',
|
|
||||||
'DB_CONNECTION',
|
|
||||||
'DB_HOST',
|
|
||||||
'DB_PORT',
|
|
||||||
'DB_DATABASE',
|
|
||||||
'DB_USERNAME',
|
|
||||||
'DB_PASSWORD',
|
|
||||||
'SESSION_DRIVER',
|
|
||||||
];
|
|
||||||
|
|
||||||
protected string $path;
|
|
||||||
|
|
||||||
public function __construct(?string $path = null)
|
|
||||||
{
|
|
||||||
$this->path = $path ?: base_path('.env');
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @return array<int, array{type: 'comment', text: string}|array{type: 'blank'}|array{type: 'raw', raw: string}|array{type: 'var', key: string, value: string, protected: bool, sensitive: bool}>
|
|
||||||
*/
|
|
||||||
public function read(): array
|
|
||||||
{
|
|
||||||
if (! is_file($this->path)) {
|
|
||||||
throw new RuntimeException('Le fichier .env est introuvable.');
|
|
||||||
}
|
|
||||||
|
|
||||||
$lines = preg_split('/\r\n|\r|\n/', file_get_contents($this->path));
|
|
||||||
$parsed = [];
|
|
||||||
|
|
||||||
foreach ($lines as $line) {
|
|
||||||
$trimmed = trim($line);
|
|
||||||
|
|
||||||
if ($trimmed === '') {
|
|
||||||
$parsed[] = ['type' => 'blank'];
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (str_starts_with($trimmed, '#')) {
|
|
||||||
$parsed[] = ['type' => 'comment', 'text' => ltrim(substr($trimmed, 1))];
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! str_contains($trimmed, '=')) {
|
|
||||||
$parsed[] = ['type' => 'raw', 'raw' => $line];
|
|
||||||
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
[$key, $rawValue] = explode('=', $trimmed, 2);
|
|
||||||
$key = trim($key);
|
|
||||||
|
|
||||||
$parsed[] = [
|
|
||||||
'type' => 'var',
|
|
||||||
'key' => $key,
|
|
||||||
'value' => $this->unquote(trim($rawValue)),
|
|
||||||
'protected' => in_array($key, self::PROTECTED_KEYS, true),
|
|
||||||
'sensitive' => $this->isSensitive($key),
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
return $parsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function isSensitive(string $key): bool
|
|
||||||
{
|
|
||||||
return (bool) preg_match('/PASSWORD|SECRET|TOKEN|KEY/i', $key);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Rewrites only the given keys' values, leaving every other line (order,
|
|
||||||
* comments, blanks, untouched keys) exactly as-is. Protected keys are
|
|
||||||
* silently ignored even if present in $updates — never trust the form.
|
|
||||||
*
|
|
||||||
* @param array<string, string> $updates
|
|
||||||
*/
|
|
||||||
public function update(array $updates): void
|
|
||||||
{
|
|
||||||
foreach ($updates as $key => $value) {
|
|
||||||
if (str_contains($value, "\n") || str_contains($value, "\r")) {
|
|
||||||
throw new RuntimeException("La valeur de {$key} ne peut pas contenir de retour à la ligne.");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! is_writable($this->path)) {
|
|
||||||
throw new RuntimeException("Le fichier .env n'est pas accessible en écriture par le serveur web.");
|
|
||||||
}
|
|
||||||
|
|
||||||
$lines = preg_split('/\r\n|\r|\n/', file_get_contents($this->path));
|
|
||||||
$output = [];
|
|
||||||
|
|
||||||
foreach ($lines as $line) {
|
|
||||||
$trimmed = trim($line);
|
|
||||||
$replaced = false;
|
|
||||||
|
|
||||||
if ($trimmed !== '' && ! str_starts_with($trimmed, '#') && str_contains($trimmed, '=')) {
|
|
||||||
[$key] = explode('=', $trimmed, 2);
|
|
||||||
$key = trim($key);
|
|
||||||
|
|
||||||
if (array_key_exists($key, $updates) && ! in_array($key, self::PROTECTED_KEYS, true)) {
|
|
||||||
$output[] = $key.'='.$this->quote($updates[$key]);
|
|
||||||
$replaced = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (! $replaced) {
|
|
||||||
$output[] = $line;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Write to a temp file in the same directory first, then rename —
|
|
||||||
// rename() is atomic, so a request reading .env mid-save never sees
|
|
||||||
// a half-written file.
|
|
||||||
$tmpPath = $this->path.'.tmp-'.bin2hex(random_bytes(4));
|
|
||||||
file_put_contents($tmpPath, implode(PHP_EOL, $output));
|
|
||||||
rename($tmpPath, $this->path);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function unquote(string $value): string
|
|
||||||
{
|
|
||||||
if (strlen($value) >= 2 && $value[0] === '"' && str_ends_with($value, '"')) {
|
|
||||||
return stripcslashes(substr($value, 1, -1));
|
|
||||||
}
|
|
||||||
|
|
||||||
if (strlen($value) >= 2 && $value[0] === "'" && str_ends_with($value, "'")) {
|
|
||||||
return substr($value, 1, -1);
|
|
||||||
}
|
|
||||||
|
|
||||||
return $value;
|
|
||||||
}
|
|
||||||
|
|
||||||
protected function quote(string $value): string
|
|
||||||
{
|
|
||||||
if ($value === '' || preg_match('/[\s#"]/', $value)) {
|
|
||||||
return '"'.addcslashes($value, '"\\').'"';
|
|
||||||
}
|
|
||||||
|
|
||||||
return $value;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,71 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Services;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whitelist of settings administrators can override from the database instead
|
||||||
|
* of editing .env directly. Deliberately excludes anything read at the very
|
||||||
|
* start of the boot process (APP_KEY, DB_*, SESSION_DRIVER, CACHE_STORE,
|
||||||
|
* QUEUE_CONNECTION, ...): those are resolved before a database connection —
|
||||||
|
* or before this override even runs — so changing them here wouldn't
|
||||||
|
* reliably take effect, and getting DB_* wrong here could lock everyone out.
|
||||||
|
* Only settings that are safe to read lazily, at point of use, belong here.
|
||||||
|
*/
|
||||||
|
class SettingsRegistry
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @var array<string, array<string, array{config: string, label: string}>>
|
||||||
|
*/
|
||||||
|
public const SETTINGS = [
|
||||||
|
'Application' => [
|
||||||
|
'APP_NAME' => ['config' => 'app.name', 'label' => "Nom de l'application"],
|
||||||
|
'APP_URL' => ['config' => 'app.url', 'label' => 'URL de base'],
|
||||||
|
],
|
||||||
|
'Mail' => [
|
||||||
|
'MAIL_MAILER' => ['config' => 'mail.default', 'label' => 'Pilote (mailer)'],
|
||||||
|
'MAIL_HOST' => ['config' => 'mail.mailers.smtp.host', 'label' => 'Hôte SMTP'],
|
||||||
|
'MAIL_PORT' => ['config' => 'mail.mailers.smtp.port', 'label' => 'Port SMTP'],
|
||||||
|
'MAIL_SCHEME' => ['config' => 'mail.mailers.smtp.scheme', 'label' => 'Schéma (tls, ssl…)'],
|
||||||
|
'MAIL_USERNAME' => ['config' => 'mail.mailers.smtp.username', 'label' => 'Utilisateur SMTP'],
|
||||||
|
'MAIL_PASSWORD' => ['config' => 'mail.mailers.smtp.password', 'label' => 'Mot de passe SMTP'],
|
||||||
|
'MAIL_FROM_ADDRESS' => ['config' => 'mail.from.address', 'label' => 'Adresse expéditeur'],
|
||||||
|
'MAIL_FROM_NAME' => ['config' => 'mail.from.name', 'label' => 'Nom expéditeur'],
|
||||||
|
],
|
||||||
|
'OnlyOffice' => [
|
||||||
|
'ONLYOFFICE_DS_URL' => ['config' => 'onlyoffice.document_server_url', 'label' => 'URL du serveur de documents'],
|
||||||
|
'ONLYOFFICE_CALLBACK_BASE_URL' => ['config' => 'onlyoffice.callback_base_url', 'label' => 'URL de rappel (callback)'],
|
||||||
|
'ONLYOFFICE_JWT_SECRET' => ['config' => 'onlyoffice.jwt_secret', 'label' => 'Secret JWT'],
|
||||||
|
],
|
||||||
|
'GLPI' => [
|
||||||
|
'GLPI_API_URL' => ['config' => 'glpi.api_url', 'label' => "URL de l'API"],
|
||||||
|
'GLPI_APP_TOKEN' => ['config' => 'glpi.app_token', 'label' => 'Jeton application'],
|
||||||
|
'GLPI_USER_TOKEN' => ['config' => 'glpi.user_token', 'label' => 'Jeton utilisateur'],
|
||||||
|
'GLPI_CACHE_TTL' => ['config' => 'glpi.cache_ttl_seconds', 'label' => 'Durée du cache (secondes)'],
|
||||||
|
],
|
||||||
|
];
|
||||||
|
|
||||||
|
public const CACHE_KEY = 'settings.overrides';
|
||||||
|
|
||||||
|
public static function isSensitive(string $key): bool
|
||||||
|
{
|
||||||
|
return (bool) preg_match('/PASSWORD|SECRET|TOKEN|KEY/i', $key);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flat env-style key => config path map, for applying overrides at boot.
|
||||||
|
*
|
||||||
|
* @return array<string, string>
|
||||||
|
*/
|
||||||
|
public static function configMap(): array
|
||||||
|
{
|
||||||
|
$map = [];
|
||||||
|
|
||||||
|
foreach (self::SETTINGS as $section) {
|
||||||
|
foreach ($section as $envKey => $meta) {
|
||||||
|
$map[$envKey] = $meta['config'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return $map;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,23 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('settings', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('key')->unique();
|
||||||
|
$table->text('value')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('settings');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -133,7 +133,7 @@
|
|||||||
</a>
|
</a>
|
||||||
<a href="{{ route('settings.env') }}"
|
<a href="{{ route('settings.env') }}"
|
||||||
class="block rounded px-3 py-2 hover:bg-slate-800 {{ request()->routeIs('settings.env') ? 'bg-slate-800 text-white' : '' }}">
|
class="block rounded px-3 py-2 hover:bg-slate-800 {{ request()->routeIs('settings.env') ? 'bg-slate-800 text-white' : '' }}">
|
||||||
Configuration (.env)
|
Configuration
|
||||||
</a>
|
</a>
|
||||||
</div>
|
</div>
|
||||||
@endrole
|
@endrole
|
||||||
|
|||||||
@@ -1,19 +1,18 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
use App\Services\EnvFileManager;
|
use App\Models\Setting;
|
||||||
|
use App\Services\SettingsRegistry;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
use Livewire\Component;
|
use Livewire\Component;
|
||||||
|
|
||||||
new class extends Component
|
new class extends Component
|
||||||
{
|
{
|
||||||
/** @var array<int, array<string, mixed>> */
|
|
||||||
public array $lines = [];
|
|
||||||
|
|
||||||
/** @var array<string, string> */
|
/** @var array<string, string> */
|
||||||
public array $values = [];
|
public array $values = [];
|
||||||
|
|
||||||
public function mount(): void
|
public function mount(): void
|
||||||
{
|
{
|
||||||
$this->loadLines();
|
$this->loadValues();
|
||||||
}
|
}
|
||||||
|
|
||||||
public function save(): void
|
public function save(): void
|
||||||
@@ -26,38 +25,51 @@ new class extends Component
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
foreach (SettingsRegistry::configMap() as $envKey => $path) {
|
||||||
app(EnvFileManager::class)->update($this->values);
|
$value = trim($this->values[$envKey] ?? '');
|
||||||
} catch (\Throwable $e) {
|
|
||||||
session()->flash('error', $e->getMessage());
|
|
||||||
|
|
||||||
|
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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
session()->flash('status', 'Configuration enregistrée. Les nouvelles valeurs seront actives dès la prochaine requête.');
|
Setting::where('key', $envKey)->delete();
|
||||||
|
Cache::forget(SettingsRegistry::CACHE_KEY);
|
||||||
|
|
||||||
$this->loadLines();
|
session()->flash('status', "Valeur par défaut restaurée pour {$envKey}.");
|
||||||
|
|
||||||
|
$this->loadValues();
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function loadLines(): void
|
protected function loadValues(): void
|
||||||
{
|
{
|
||||||
$this->lines = app(EnvFileManager::class)->read();
|
$stored = Setting::query()->pluck('value', 'key');
|
||||||
$this->values = [];
|
|
||||||
|
|
||||||
foreach ($this->lines as $line) {
|
foreach (SettingsRegistry::configMap() as $envKey => $path) {
|
||||||
if ($line['type'] === 'var' && ! $line['protected']) {
|
$this->values[$envKey] = $stored[$envKey] ?? (string) (config($path) ?? '');
|
||||||
$this->values[$line['key']] = $line['value'];
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
?>
|
?>
|
||||||
|
|
||||||
<div class="max-w-3xl">
|
<div class="max-w-3xl">
|
||||||
<h1 class="mb-2 text-2xl font-semibold text-slate-900">Configuration (.env)</h1>
|
<h1 class="mb-2 text-2xl font-semibold text-slate-900">Configuration</h1>
|
||||||
<p class="mb-6 text-sm text-slate-500">
|
<p class="mb-6 text-sm text-slate-500">
|
||||||
Modifie directement les variables d'environnement de l'application. Les changements
|
Ces réglages sont stockés en base de données et prennent effet immédiatement, sans
|
||||||
prennent effet à la prochaine requête — aucun redémarrage n'est nécessaire.
|
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>
|
</p>
|
||||||
|
|
||||||
@if (session('status'))
|
@if (session('status'))
|
||||||
@@ -66,44 +78,28 @@ new class extends Component
|
|||||||
</div>
|
</div>
|
||||||
@endif
|
@endif
|
||||||
|
|
||||||
@if (session('error'))
|
|
||||||
<div class="mb-4 rounded border border-red-200 bg-red-50 px-4 py-2 text-sm text-red-700">
|
|
||||||
{{ session('error') }}
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
@error('values')
|
@error('values')
|
||||||
<div class="mb-4 rounded border border-red-200 bg-red-50 px-4 py-2 text-sm text-red-700">
|
<div class="mb-4 rounded border border-red-200 bg-red-50 px-4 py-2 text-sm text-red-700">
|
||||||
{{ $message }}
|
{{ $message }}
|
||||||
</div>
|
</div>
|
||||||
@enderror
|
@enderror
|
||||||
|
|
||||||
<form wire:submit="save" class="space-y-1 rounded border border-slate-200 bg-white p-6">
|
<form wire:submit="save" class="space-y-8 rounded border border-slate-200 bg-white p-6">
|
||||||
@foreach ($lines as $line)
|
@foreach (\App\Services\SettingsRegistry::SETTINGS as $section => $keys)
|
||||||
@if ($line['type'] === 'comment')
|
<div>
|
||||||
@if ($line['text'] !== '')
|
<p class="mb-3 text-xs font-semibold uppercase tracking-wide text-slate-400">{{ $section }}</p>
|
||||||
<p class="mb-1 mt-5 text-xs font-semibold uppercase tracking-wide text-slate-400 first:mt-0">
|
|
||||||
{{ $line['text'] }}
|
<div class="space-y-1.5">
|
||||||
</p>
|
@foreach ($keys as $envKey => $meta)
|
||||||
@endif
|
<div class="flex items-center gap-3 py-1">
|
||||||
@elseif ($line['type'] === 'var')
|
<label for="setting-{{ $envKey }}" class="w-48 shrink-0 text-sm text-slate-700">
|
||||||
<div class="flex items-center gap-3 py-1.5">
|
{{ $meta['label'] }}
|
||||||
<label for="env-{{ $line['key'] }}" class="w-56 shrink-0 truncate font-mono text-xs text-slate-600" title="{{ $line['key'] }}">
|
|
||||||
{{ $line['key'] }}
|
|
||||||
</label>
|
</label>
|
||||||
|
|
||||||
@if ($line['protected'])
|
@if (\App\Services\SettingsRegistry::isSensitive($envKey))
|
||||||
<div class="flex flex-1 items-center gap-2">
|
|
||||||
<input type="text" value="{{ $line['sensitive'] ? '••••••••••••' : $line['value'] }}" disabled
|
|
||||||
class="w-full rounded border-slate-200 bg-slate-50 font-mono text-sm text-slate-400">
|
|
||||||
<span class="shrink-0 rounded-full bg-slate-100 px-2 py-0.5 text-xs font-medium text-slate-500" title="Non modifiable ici — nécessite un accès direct au serveur">
|
|
||||||
🔒 lecture seule
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
@elseif ($line['sensitive'])
|
|
||||||
<div x-data="{ show: false }" class="flex flex-1 items-center gap-2">
|
<div x-data="{ show: false }" class="flex flex-1 items-center gap-2">
|
||||||
<input :type="show ? 'text' : 'password'" id="env-{{ $line['key'] }}"
|
<input :type="show ? 'text' : 'password'" id="setting-{{ $envKey }}"
|
||||||
wire:model="values.{{ $line['key'] }}" autocomplete="off"
|
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">
|
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"
|
<button type="button" x-on:click="show = !show"
|
||||||
class="shrink-0 text-xs text-slate-500 hover:text-slate-900">
|
class="shrink-0 text-xs text-slate-500 hover:text-slate-900">
|
||||||
@@ -111,15 +107,23 @@ new class extends Component
|
|||||||
</button>
|
</button>
|
||||||
</div>
|
</div>
|
||||||
@else
|
@else
|
||||||
<input type="text" id="env-{{ $line['key'] }}"
|
<input type="text" id="setting-{{ $envKey }}"
|
||||||
wire:model="values.{{ $line['key'] }}"
|
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">
|
class="flex-1 rounded border-slate-300 font-mono text-sm shadow-sm focus:border-slate-500 focus:ring-slate-500">
|
||||||
@endif
|
@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>
|
</div>
|
||||||
@endif
|
|
||||||
@endforeach
|
@endforeach
|
||||||
|
|
||||||
<div class="flex items-center gap-3 pt-5">
|
<div class="flex items-center gap-3 border-t border-slate-100 pt-6">
|
||||||
<button type="submit" wire:loading.attr="disabled" wire:target="save"
|
<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">
|
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">
|
<svg wire:loading wire:target="save" class="h-4 w-4 animate-spin" viewBox="0 0 24 24" fill="none">
|
||||||
|
|||||||
@@ -1,3 +1,3 @@
|
|||||||
<x-layouts.app title="Configuration (.env)">
|
<x-layouts.app title="Configuration">
|
||||||
<livewire:settings.env />
|
<livewire:settings.env />
|
||||||
</x-layouts.app>
|
</x-layouts.app>
|
||||||
|
|||||||
Reference in New Issue
Block a user