feat: add system configuration (A-03) — map center, zoom, radius min/max
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,28 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Models\ActivityLog;
|
||||||
|
use App\Models\Setting;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
|
||||||
|
class SettingController extends Controller
|
||||||
|
{
|
||||||
|
public function index()
|
||||||
|
{
|
||||||
|
return response()->json(Setting::orderBy('id')->get(['key', 'value', 'label']));
|
||||||
|
}
|
||||||
|
|
||||||
|
public function update(Request $request, string $key)
|
||||||
|
{
|
||||||
|
$setting = Setting::where('key', $key)->firstOrFail();
|
||||||
|
|
||||||
|
$request->validate(['value' => 'required|string|max:255']);
|
||||||
|
|
||||||
|
Setting::set($key, $request->value);
|
||||||
|
|
||||||
|
ActivityLog::record('update', "Mengubah konfigurasi: {$setting->label} → {$request->value}", 'setting', $key);
|
||||||
|
|
||||||
|
return response()->json(['success' => true]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
use Illuminate\Support\Facades\Cache;
|
||||||
|
|
||||||
|
class Setting extends Model
|
||||||
|
{
|
||||||
|
protected $fillable = ['key', 'value', 'label'];
|
||||||
|
|
||||||
|
public static function getAll(): array
|
||||||
|
{
|
||||||
|
return Cache::remember('app_settings', 300, function () {
|
||||||
|
return static::pluck('value', 'key')->toArray();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function get(string $key, mixed $default = null): mixed
|
||||||
|
{
|
||||||
|
return static::getAll()[$key] ?? $default;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function set(string $key, string $value): void
|
||||||
|
{
|
||||||
|
static::where('key', $key)->update(['value' => $value]);
|
||||||
|
Cache::forget('app_settings');
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,23 +2,22 @@
|
|||||||
|
|
||||||
namespace App\Providers;
|
namespace App\Providers;
|
||||||
|
|
||||||
|
use App\Models\Setting;
|
||||||
|
use Illuminate\Support\Facades\View;
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
|
||||||
class AppServiceProvider extends ServiceProvider
|
class AppServiceProvider extends ServiceProvider
|
||||||
{
|
{
|
||||||
/**
|
public function register(): void {}
|
||||||
* Register any application services.
|
|
||||||
*/
|
|
||||||
public function register(): void
|
|
||||||
{
|
|
||||||
//
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bootstrap any application services.
|
|
||||||
*/
|
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
//
|
View::composer('*', function ($view) {
|
||||||
|
try {
|
||||||
|
$view->with('appSettings', Setting::getAll());
|
||||||
|
} catch (\Throwable) {
|
||||||
|
$view->with('appSettings', []);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
<?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->string('value');
|
||||||
|
$table->string('label');
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
|
||||||
|
// Default values
|
||||||
|
$defaults = [
|
||||||
|
['key' => 'map_center_lat', 'value' => '-0.0553', 'label' => 'Pusat Peta — Latitude'],
|
||||||
|
['key' => 'map_center_lng', 'value' => '109.3463', 'label' => 'Pusat Peta — Longitude'],
|
||||||
|
['key' => 'map_default_zoom', 'value' => '17', 'label' => 'Zoom Default Peta'],
|
||||||
|
['key' => 'radius_min', 'value' => '50', 'label' => 'Radius Minimum (meter)'],
|
||||||
|
['key' => 'radius_max', 'value' => '2000', 'label' => 'Radius Maksimum (meter)'],
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($defaults as $row) {
|
||||||
|
\DB::table('settings')->insert(array_merge($row, [
|
||||||
|
'created_at' => now(),
|
||||||
|
'updated_at' => now(),
|
||||||
|
]));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('settings');
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -30,6 +30,7 @@
|
|||||||
<li><a href="{{ url('/dashboard') }}" class="font-medium"><i class="fa-solid fa-chart-bar"></i> Dashboard</a></li>
|
<li><a href="{{ url('/dashboard') }}" class="font-medium"><i class="fa-solid fa-chart-bar"></i> Dashboard</a></li>
|
||||||
<li><a href="{{ url('/admin/users') }}" class="font-medium"><i class="fa-solid fa-users-gear"></i> Admin</a></li>
|
<li><a href="{{ url('/admin/users') }}" class="font-medium"><i class="fa-solid fa-users-gear"></i> Admin</a></li>
|
||||||
<li><a href="{{ url('/admin/logs') }}" class="font-semibold bg-primary/10 text-primary rounded-lg"><i class="fa-solid fa-clock-rotate-left"></i> Log</a></li>
|
<li><a href="{{ url('/admin/logs') }}" class="font-semibold bg-primary/10 text-primary rounded-lg"><i class="fa-solid fa-clock-rotate-left"></i> Log</a></li>
|
||||||
|
<li><a href="{{ url('/admin/settings') }}" class="font-medium"><i class="fa-solid fa-sliders"></i> Pengaturan</a></li>
|
||||||
</ul>
|
</ul>
|
||||||
<div class="dropdown dropdown-end ml-2">
|
<div class="dropdown dropdown-end ml-2">
|
||||||
<div tabindex="0" role="button" class="btn btn-ghost gap-2">
|
<div tabindex="0" role="button" class="btn btn-ghost gap-2">
|
||||||
|
|||||||
@@ -0,0 +1,133 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="id" data-theme="light">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||||
|
<meta name="csrf-token" content="{{ csrf_token() }}">
|
||||||
|
<title>Konfigurasi Sistem - WebGIS</title>
|
||||||
|
<link href="https://cdn.jsdelivr.net/npm/daisyui@4/dist/full.min.css" rel="stylesheet">
|
||||||
|
<script src="https://cdn.tailwindcss.com"></script>
|
||||||
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.0/css/all.min.css">
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Plus+Jakarta+Sans:ital,wght@0,200..800;1,200..800&display=swap" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
* { font-family: 'Plus Jakarta Sans', sans-serif; }
|
||||||
|
body { font-weight: 500; background-color: #f8fafc; }
|
||||||
|
h1, h2, h3, .btn { font-weight: 700 !important; letter-spacing: -0.01em; }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body class="min-h-screen">
|
||||||
|
<div class="navbar bg-base-100 border-b border-base-200 shadow-sm sticky top-0 z-[1000]">
|
||||||
|
<div class="flex-1">
|
||||||
|
<a href="{{ url('/') }}" class="btn btn-ghost text-lg font-bold gap-2 text-primary">
|
||||||
|
<i class="fa-solid fa-house-chimney-crack"></i> WebGIS Pemetaan Kemiskinan
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
<div class="flex-none">
|
||||||
|
<ul class="menu menu-horizontal px-1 gap-1">
|
||||||
|
<li><a href="{{ url('/map') }}" class="font-medium"><i class="fa-solid fa-map"></i> Peta</a></li>
|
||||||
|
<li><a href="{{ url('/data') }}" class="font-medium"><i class="fa-solid fa-table-list"></i> Data</a></li>
|
||||||
|
<li><a href="{{ url('/dashboard') }}" class="font-medium"><i class="fa-solid fa-chart-bar"></i> Dashboard</a></li>
|
||||||
|
<li><a href="{{ url('/admin/users') }}" class="font-medium"><i class="fa-solid fa-users-gear"></i> Admin</a></li>
|
||||||
|
<li><a href="{{ url('/admin/logs') }}" class="font-medium"><i class="fa-solid fa-clock-rotate-left"></i> Log</a></li>
|
||||||
|
<li><a href="{{ url('/admin/settings') }}" class="font-semibold bg-primary/10 text-primary rounded-lg"><i class="fa-solid fa-sliders"></i> Pengaturan</a></li>
|
||||||
|
</ul>
|
||||||
|
<div class="dropdown dropdown-end ml-2">
|
||||||
|
<div tabindex="0" role="button" class="btn btn-ghost gap-2">
|
||||||
|
<div class="avatar placeholder"><div class="bg-primary text-primary-content rounded-full w-8"><span class="text-xs font-bold">{{ substr(auth()->user()->name, 0, 1) }}</span></div></div>
|
||||||
|
<span class="hidden sm:inline text-sm font-semibold">{{ auth()->user()->name }}</span>
|
||||||
|
</div>
|
||||||
|
<ul tabindex="0" class="dropdown-content menu bg-base-100 rounded-box z-50 w-52 p-2 shadow-lg border border-base-200 mt-2">
|
||||||
|
<li class="menu-title"><span class="text-xs text-base-content/50 capitalize">{{ str_replace('_', ' ', auth()->user()->getRoleNames()->first() ?? 'user') }}</span></li>
|
||||||
|
<li><form method="POST" action="{{ route('logout') }}">@csrf<button type="submit" class="text-error w-full text-left flex items-center gap-2"><i class="fa-solid fa-right-from-bracket"></i> Keluar</button></form></li>
|
||||||
|
</ul>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<main class="max-w-screen-xl mx-auto px-4 py-8">
|
||||||
|
<div class="mb-8">
|
||||||
|
<h1 class="text-3xl font-bold flex items-center gap-3"><i class="fa-solid fa-sliders text-primary"></i> Konfigurasi Sistem</h1>
|
||||||
|
<p class="text-base-content/50 mt-1">Atur parameter peta dan analisis. Perubahan berlaku setelah halaman peta di-refresh.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="settingsForm" class="bg-white rounded-2xl shadow-sm border border-base-200 p-6 max-w-2xl">
|
||||||
|
<p class="text-center py-8 opacity-30 italic" id="settingsLoading">Memuat pengaturan...</p>
|
||||||
|
</div>
|
||||||
|
</main>
|
||||||
|
|
||||||
|
<div class="toast toast-end toast-bottom z-[9999]" id="toastBox"></div>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
const csrfToken = document.querySelector('meta[name="csrf-token"]').getAttribute('content');
|
||||||
|
function esc(s) { return String(s).replace(/[&<>"']/g, c => ({'&':'&','<':'<','>':'>','"':'"',"'":'''}[c])); }
|
||||||
|
|
||||||
|
const settingMeta = {
|
||||||
|
map_center_lat: { type: 'number', step: '0.0001', label: 'Pusat Peta — Latitude', hint: 'Contoh: -0.0553 (Pontianak)' },
|
||||||
|
map_center_lng: { type: 'number', step: '0.0001', label: 'Pusat Peta — Longitude', hint: 'Contoh: 109.3463 (Pontianak)' },
|
||||||
|
map_default_zoom: { type: 'number', step: '1', min: '5', max: '19', label: 'Zoom Default Peta', hint: '1 (jauh) — 19 (dekat)' },
|
||||||
|
radius_min: { type: 'number', step: '50', min: '50', max: '500', label: 'Radius Minimum (meter)', hint: 'Minimal 50 m' },
|
||||||
|
radius_max: { type: 'number', step: '50', min: '500', max: '5000', label: 'Radius Maksimum (meter)', hint: 'Maksimal 5000 m' },
|
||||||
|
};
|
||||||
|
|
||||||
|
document.addEventListener('DOMContentLoaded', loadSettings);
|
||||||
|
|
||||||
|
async function loadSettings() {
|
||||||
|
const res = await fetch('{{ url('/api/admin/settings') }}', { headers: { 'X-CSRF-TOKEN': csrfToken } });
|
||||||
|
const settings = await res.json();
|
||||||
|
|
||||||
|
const form = document.getElementById('settingsForm');
|
||||||
|
form.innerHTML = settings.map(s => {
|
||||||
|
const m = settingMeta[s.key] || { type: 'text', label: s.label };
|
||||||
|
const extra = ['min', 'max', 'step'].map(a => m[a] ? `${a}="${esc(m[a])}"` : '').join(' ');
|
||||||
|
return `
|
||||||
|
<div class="form-control mb-5" id="row_${esc(s.key)}">
|
||||||
|
<label class="label pb-1">
|
||||||
|
<span class="label-text font-bold">${esc(s.label)}</span>
|
||||||
|
${m.hint ? `<span class="label-text-alt opacity-50">${esc(m.hint)}</span>` : ''}
|
||||||
|
</label>
|
||||||
|
<div class="flex gap-2">
|
||||||
|
<input id="input_${esc(s.key)}" type="${m.type || 'text'}" ${extra}
|
||||||
|
value="${esc(s.value)}"
|
||||||
|
class="input input-bordered flex-1 font-mono"
|
||||||
|
data-key="${esc(s.key)}">
|
||||||
|
<button onclick="saveSetting('${esc(s.key)}')" class="btn btn-primary btn-sm">
|
||||||
|
<i class="fa-solid fa-floppy-disk mr-1"></i>Simpan
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
}).join('<div class="divider my-2"></div>') + `
|
||||||
|
<div class="mt-6 alert alert-info text-sm">
|
||||||
|
<i class="fa-solid fa-circle-info"></i>
|
||||||
|
<span>Setelah menyimpan, buka ulang halaman <a href="{{ url('/map') }}" class="link font-bold">Peta Interaktif</a> untuk melihat perubahan.</span>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function saveSetting(key) {
|
||||||
|
const input = document.getElementById(`input_${key}`);
|
||||||
|
const value = input.value.trim();
|
||||||
|
if (!value) { showToast('Nilai tidak boleh kosong', 'error'); return; }
|
||||||
|
|
||||||
|
const res = await fetch(`{{ url('/api/admin/settings') }}/${key}`, {
|
||||||
|
method: 'PUT',
|
||||||
|
headers: { 'Content-Type': 'application/json', 'X-CSRF-TOKEN': csrfToken },
|
||||||
|
body: JSON.stringify({ value }),
|
||||||
|
});
|
||||||
|
const json = await res.json();
|
||||||
|
if (json.success) {
|
||||||
|
showToast('Pengaturan disimpan', 'success');
|
||||||
|
} else {
|
||||||
|
showToast(json.message || 'Gagal menyimpan', 'error');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function showToast(msg, type = 'info') {
|
||||||
|
const t = document.createElement('div');
|
||||||
|
const cls = type === 'success' ? 'alert-success' : type === 'error' ? 'alert-error' : 'alert-info';
|
||||||
|
t.className = `alert ${cls} shadow-lg py-3 text-xs font-bold`;
|
||||||
|
t.innerHTML = `<span><i class="fa-solid fa-circle-info mr-2"></i>${esc(msg)}</span>`;
|
||||||
|
document.getElementById('toastBox').appendChild(t);
|
||||||
|
setTimeout(() => t.remove(), 3000);
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -54,6 +54,11 @@
|
|||||||
<i class="fa-solid fa-clock-rotate-left"></i> Log
|
<i class="fa-solid fa-clock-rotate-left"></i> Log
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{{ url('/admin/settings') }}" class="font-medium">
|
||||||
|
<i class="fa-solid fa-sliders"></i> Pengaturan
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
</ul>
|
</ul>
|
||||||
<!-- User dropdown -->
|
<!-- User dropdown -->
|
||||||
<div class="dropdown dropdown-end ml-2">
|
<div class="dropdown dropdown-end ml-2">
|
||||||
|
|||||||
@@ -57,6 +57,11 @@
|
|||||||
<i class="fa-solid fa-clock-rotate-left"></i> Log
|
<i class="fa-solid fa-clock-rotate-left"></i> Log
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{{ url('/admin/settings') }}" class="font-medium">
|
||||||
|
<i class="fa-solid fa-sliders"></i> Pengaturan
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
@endif
|
@endif
|
||||||
</ul>
|
</ul>
|
||||||
<!-- User dropdown -->
|
<!-- User dropdown -->
|
||||||
|
|||||||
@@ -52,6 +52,11 @@
|
|||||||
<i class="fa-solid fa-clock-rotate-left"></i> Log
|
<i class="fa-solid fa-clock-rotate-left"></i> Log
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{{ url('/admin/settings') }}" class="font-medium">
|
||||||
|
<i class="fa-solid fa-sliders"></i> Pengaturan
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
@endif
|
@endif
|
||||||
</ul>
|
</ul>
|
||||||
<!-- User dropdown -->
|
<!-- User dropdown -->
|
||||||
|
|||||||
@@ -104,6 +104,11 @@
|
|||||||
<i class="fa-solid fa-clock-rotate-left"></i> Log
|
<i class="fa-solid fa-clock-rotate-left"></i> Log
|
||||||
</a>
|
</a>
|
||||||
</li>
|
</li>
|
||||||
|
<li>
|
||||||
|
<a href="{{ url('/admin/settings') }}" class="font-medium">
|
||||||
|
<i class="fa-solid fa-sliders"></i> Pengaturan
|
||||||
|
</a>
|
||||||
|
</li>
|
||||||
@endif
|
@endif
|
||||||
</ul>
|
</ul>
|
||||||
<!-- User dropdown -->
|
<!-- User dropdown -->
|
||||||
@@ -177,9 +182,9 @@
|
|||||||
|
|
||||||
<div class="form-control mb-4">
|
<div class="form-control mb-4">
|
||||||
<label class="label py-1">
|
<label class="label py-1">
|
||||||
<span class="label-text-alt font-bold">Radius: <span id="radiusVal">500</span>m</span>
|
<span class="label-text-alt font-bold">Radius: <span id="radiusVal">{{ $appSettings['radius_min'] ?? 50 }}</span>m</span>
|
||||||
</label>
|
</label>
|
||||||
<input type="range" min="50" max="2000" value="500" step="50" class="range range-xs range-primary" id="radiusSlider">
|
<input type="range" min="{{ $appSettings['radius_min'] ?? 50 }}" max="{{ $appSettings['radius_max'] ?? 2000 }}" value="{{ $appSettings['radius_min'] ?? 50 }}" step="50" class="range range-xs range-primary" id="radiusSlider">
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="grid grid-cols-3 gap-2">
|
<div class="grid grid-cols-3 gap-2">
|
||||||
@@ -527,8 +532,13 @@
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const appSettings = @json($appSettings);
|
||||||
|
|
||||||
function initMap() {
|
function initMap() {
|
||||||
map = L.map('map', { zoomControl: false }).setView([-0.0553, 109.3463], 17);
|
const centerLat = parseFloat(appSettings.map_center_lat ?? '-0.0553');
|
||||||
|
const centerLng = parseFloat(appSettings.map_center_lng ?? '109.3463');
|
||||||
|
const defaultZoom = parseInt(appSettings.map_default_zoom ?? '17');
|
||||||
|
map = L.map('map', { zoomControl: false }).setView([centerLat, centerLng], defaultZoom);
|
||||||
|
|
||||||
const osm = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19 });
|
const osm = L.tileLayer('https://tile.openstreetmap.org/{z}/{x}/{y}.png', { maxZoom: 19 });
|
||||||
const satellite = L.layerGroup([
|
const satellite = L.layerGroup([
|
||||||
|
|||||||
+7
-2
@@ -8,6 +8,7 @@ use App\Http\Controllers\ExportController;
|
|||||||
use App\Http\Controllers\IbadahController;
|
use App\Http\Controllers\IbadahController;
|
||||||
use App\Http\Controllers\ImportController;
|
use App\Http\Controllers\ImportController;
|
||||||
use App\Http\Controllers\MiskinController;
|
use App\Http\Controllers\MiskinController;
|
||||||
|
use App\Http\Controllers\SettingController;
|
||||||
use Illuminate\Support\Facades\Route;
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
// Auth routes (public, redirect to /map if already logged in)
|
// Auth routes (public, redirect to /map if already logged in)
|
||||||
@@ -65,8 +66,9 @@ Route::middleware('auth')->group(function () {
|
|||||||
|
|
||||||
// Admin routes — hanya administrator
|
// Admin routes — hanya administrator
|
||||||
Route::middleware('role:administrator')->group(function () {
|
Route::middleware('role:administrator')->group(function () {
|
||||||
Route::get('/admin/users', fn() => view('admin.users'))->name('admin.users');
|
Route::get('/admin/users', fn() => view('admin.users'))->name('admin.users');
|
||||||
Route::get('/admin/logs', fn() => view('admin.logs'))->name('admin.logs');
|
Route::get('/admin/logs', fn() => view('admin.logs'))->name('admin.logs');
|
||||||
|
Route::get('/admin/settings', fn() => view('admin.settings'))->name('admin.settings');
|
||||||
|
|
||||||
Route::prefix('api/admin')->group(function () {
|
Route::prefix('api/admin')->group(function () {
|
||||||
Route::get('/users', [AdminUserController::class, 'index']);
|
Route::get('/users', [AdminUserController::class, 'index']);
|
||||||
@@ -89,6 +91,9 @@ Route::middleware('auth')->group(function () {
|
|||||||
|
|
||||||
Route::get('/logs', [ActivityLogController::class, 'index']);
|
Route::get('/logs', [ActivityLogController::class, 'index']);
|
||||||
Route::get('/stats', [ActivityLogController::class, 'stats']);
|
Route::get('/stats', [ActivityLogController::class, 'stats']);
|
||||||
|
|
||||||
|
Route::get('/settings', [SettingController::class, 'index']);
|
||||||
|
Route::put('/settings/{key}', [SettingController::class, 'update']);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user