Skip to content

Commit 136673d

Browse files
author
David Courtey
committed
feat: add application configuration for Adminer integration and implement access control
1 parent f1cf9eb commit 136673d

9 files changed

Lines changed: 107 additions & 8 deletions

File tree

app/Http/Controllers/Web/AdminerController.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
namespace App\Http\Controllers\Web;
44

5+
use App\Facades\AppConfig;
56
use App\Http\Controllers\Controller;
67
use App\Services\AdminerService;
78
use Illuminate\Http\Request;
@@ -10,6 +11,8 @@ class AdminerController extends Controller
1011
{
1112
public function __invoke(Request $request, AdminerService $adminer): void
1213
{
14+
abort_unless((bool) AppConfig::get('app.adminer_enabled'), 404);
15+
1316
$credentials = session('adminer_credentials');
1417

1518
// Auto-login: simulate form submission only on the initial load

app/Livewire/Configuration/Index.php

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -140,6 +140,15 @@ public function getSsoConfig(): array
140140
];
141141
}
142142

143+
public function saveApplicationConfig(): void
144+
{
145+
abort_unless(auth()->user()->isAdmin(), Response::HTTP_FORBIDDEN);
146+
147+
$this->form->saveApplication();
148+
149+
$this->success(__('Application configuration saved.'));
150+
}
151+
143152
public function saveBackupConfig(): void
144153
{
145154
abort_unless(auth()->user()->isAdmin(), Response::HTTP_FORBIDDEN);

app/Livewire/DatabaseServer/Index.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
namespace App\Livewire\DatabaseServer;
44

55
use App\Enums\DatabaseType;
6+
use App\Facades\AppConfig;
67
use App\Models\Backup;
78
use App\Models\DatabaseServer;
89
use App\Models\NotificationChannel;
@@ -133,6 +134,10 @@ public function confirmRestore(string $id): void
133134

134135
public function openAdminer(string $id): void
135136
{
137+
if (! AppConfig::get('app.adminer_enabled')) {
138+
return;
139+
}
140+
136141
$server = DatabaseServer::findOrFail($id);
137142

138143
$this->authorize('view', $server);

app/Livewire/Forms/ConfigurationForm.php

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,9 @@
88

99
class ConfigurationForm extends Form
1010
{
11+
// Application settings
12+
public bool $adminer_enabled = true;
13+
1114
// Backup settings
1215
public string $working_directory = '';
1316

@@ -34,6 +37,7 @@ class ConfigurationForm extends Form
3437

3538
public function loadFromConfig(): void
3639
{
40+
$this->adminer_enabled = (bool) AppConfig::get('app.adminer_enabled');
3741
$this->working_directory = (string) AppConfig::get('backup.working_directory');
3842
$this->compression = (string) AppConfig::get('backup.compression');
3943
$this->compression_level = (int) AppConfig::get('backup.compression_level');
@@ -45,6 +49,29 @@ public function loadFromConfig(): void
4549
$this->verify_files_cron = (string) AppConfig::get('backup.verify_files_cron');
4650
}
4751

52+
/**
53+
* @return array<string, mixed>
54+
*/
55+
private function applicationRules(): array
56+
{
57+
return [
58+
'adminer_enabled' => ['boolean'],
59+
];
60+
}
61+
62+
public function saveApplication(): void
63+
{
64+
$this->validate($this->applicationRules());
65+
66+
$appKeyMap = [
67+
'adminer_enabled' => 'app.adminer_enabled',
68+
];
69+
70+
foreach ($appKeyMap as $property => $configKey) {
71+
AppConfig::set($configKey, $this->{$property});
72+
}
73+
}
74+
4875
/**
4976
* @return array<string, mixed>
5077
*/

app/Services/AppConfigService.php

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ class AppConfigService
1414
* @var array<string, array{type: string, is_sensitive: bool, default: mixed}>
1515
*/
1616
private const array CONFIG = [
17+
'app.adminer_enabled' => ['type' => 'boolean', 'is_sensitive' => false, 'default' => true],
1718
'backup.working_directory' => ['type' => 'string', 'is_sensitive' => false, 'default' => '/tmp/backups'],
1819
'backup.compression' => ['type' => 'string', 'is_sensitive' => false, 'default' => 'gzip'],
1920
'backup.compression_level' => ['type' => 'integer', 'is_sensitive' => false, 'default' => 6],

resources/views/livewire/configuration/index.blade.php

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ class="btn-ghost btn-sm"
2828

2929
<div class="grid gap-6">
3030
<!-- Application Configuration (read-only) -->
31-
<x-card title="{{ __('Application') }}" subtitle="{{ __('General application settings (read-only).') }}" shadow class="min-w-0">
31+
<x-card :title="__('Application')" :subtitle="__('General application settings.')" shadow class="min-w-0">
3232
<x-slot:menu>
3333
<x-button
3434
label="{{ __('Documentation') }}"
@@ -39,6 +39,25 @@ class="btn-ghost btn-sm"
3939
/>
4040
</x-slot:menu>
4141
@include('livewire.configuration._config-table', ['rows' => $appConfig])
42+
43+
<form wire:submit="saveApplicationConfig" class="mt-4 border-t border-base-200/60 pt-4">
44+
<div class="divide-y divide-base-200/80">
45+
<x-config-row :label="__('Database Browser')" :description="__('Enable the built-in Adminer database browser for viewing and managing database contents.')">
46+
<x-toggle wire:model="form.adminer_enabled" :disabled="!$this->isAdmin" />
47+
</x-config-row>
48+
</div>
49+
50+
@if ($this->isAdmin)
51+
<div class="flex items-center justify-end border-t border-base-200/60 pt-6">
52+
<x-button
53+
type="submit"
54+
class="btn-primary"
55+
:label="__('Save Application Settings')"
56+
spinner="saveApplicationConfig"
57+
/>
58+
</div>
59+
@endif
60+
</form>
4261
</x-card>
4362

4463
<!-- Backup Schedules -->

resources/views/livewire/database-server/index.blade.php

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -121,12 +121,14 @@ class="flex items-center gap-1 hover:text-success transition-colors tooltip @if
121121

122122
@scope('cell_actions', $server)
123123
<div>
124-
@can('view', $server)
125-
@if(! in_array($server->database_type, [\App\Enums\DatabaseType::REDIS, \App\Enums\DatabaseType::MONGODB]))
126-
<x-button icon="o-table-cells" wire:click="openAdminer('{{ $server->id }}')" spinner
127-
tooltip="{{ __('Browse') }}" class="btn-ghost btn-sm text-accent" />
128-
@endif
129-
@endcan
124+
@if(\App\Facades\AppConfig::get('app.adminer_enabled'))
125+
@can('view', $server)
126+
@if(! in_array($server->database_type, [\App\Enums\DatabaseType::REDIS, \App\Enums\DatabaseType::MONGODB]))
127+
<x-button icon="o-table-cells" wire:click="openAdminer('{{ $server->id }}')" spinner
128+
tooltip="{{ __('Browse') }}" class="btn-ghost btn-sm text-accent" />
129+
@endif
130+
@endcan
131+
@endif
130132
@can('backup', $server)
131133
<x-button icon="o-arrow-down-tray" wire:click="runBackupAll('{{ $server->id }}')" spinner
132134
tooltip="{{ __('Backup now') }}" class="btn-ghost btn-sm text-info" />
@@ -158,7 +160,9 @@ class="flex items-center gap-1 hover:text-success transition-colors tooltip @if
158160
<livewire:database-server.restore-modal />
159161

160162
<!-- ADMINER MODAL -->
161-
<livewire:database-server.adminer-modal />
163+
@if(\App\Facades\AppConfig::get('app.adminer_enabled'))
164+
<livewire:database-server.adminer-modal />
165+
@endif
162166

163167
<!-- REDIS RESTORE INFO MODAL -->
164168
<x-modal wire:model="showRedisRestoreModal" :title="__('Restore Redis / Valkey Snapshot')" class="backdrop-blur">

tests/Feature/ConfigurationTest.php

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929
->test(Index::class)
3030
->assertSee('Configuration')
3131
->assertSee('Save Backup Settings')
32+
->assertSet('form.adminer_enabled', true)
3233
->assertSet('form.compression', 'gzip')
3334
->assertSet('form.compression_level', 6)
3435
->assertSet('form.verify_files', true);
@@ -57,6 +58,23 @@
5758
->assertForbidden();
5859
});
5960

61+
test('saving application config persists adminer enabled', function () {
62+
Livewire::actingAs(User::factory()->create(['role' => 'admin']))
63+
->test(Index::class)
64+
->set('form.adminer_enabled', false)
65+
->call('saveApplicationConfig')
66+
->assertHasNoErrors();
67+
68+
expect(AppConfig::get('app.adminer_enabled'))->toBe(false);
69+
});
70+
71+
test('non-admin cannot save application config', function () {
72+
Livewire::actingAs(User::factory()->create(['role' => 'member']))
73+
->test(Index::class)
74+
->call('saveApplicationConfig')
75+
->assertForbidden();
76+
});
77+
6078
test('saving backup config persists values', function () {
6179
Livewire::actingAs(User::factory()->create(['role' => 'admin']))
6280
->test(Index::class)

tests/Feature/Livewire/DatabaseServer/IndexTest.php

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
<?php
22

3+
use App\Facades\AppConfig;
34
use App\Jobs\ProcessBackupJob;
45
use App\Livewire\DatabaseServer\Index;
56
use App\Models\Backup;
@@ -51,6 +52,18 @@
5152

5253
// --- openAdminer ---
5354

55+
test('openAdminer does nothing when adminer is disabled', function () {
56+
AppConfig::set('app.adminer_enabled', false);
57+
58+
$user = User::factory()->create(['role' => User::ROLE_ADMIN]);
59+
$server = DatabaseServer::factory()->withoutBackups()->create(['database_type' => 'mysql']);
60+
61+
Livewire::actingAs($user)
62+
->test(Index::class)
63+
->call('openAdminer', $server->id)
64+
->assertNotDispatched('open-adminer-modal');
65+
});
66+
5467
test('openAdminer rejects unsupported database types', function (string $factoryState) {
5568
$user = User::factory()->create(['role' => User::ROLE_ADMIN]);
5669
$server = DatabaseServer::factory()->{$factoryState}()->withoutBackups()->create();

0 commit comments

Comments
 (0)