Add admin options functionality
This commit is contained in:
parent
2fda7a0651
commit
3de1246c50
|
@ -10,6 +10,7 @@ use Illuminate\Support\Str;
|
||||||
use App\Models\User;
|
use App\Models\User;
|
||||||
use App\Models\Role;
|
use App\Models\Role;
|
||||||
use App\Models\Permission;
|
use App\Models\Permission;
|
||||||
|
use App\Models\Option;
|
||||||
|
|
||||||
class AdminController extends Controller
|
class AdminController extends Controller
|
||||||
{
|
{
|
||||||
|
@ -316,6 +317,47 @@ class AdminController extends Controller
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function getOptions() {
|
||||||
|
if(!request()->user()->hasPermission("admin-options-read")) abort(401);
|
||||||
|
|
||||||
|
return response()->json(Option::all());
|
||||||
|
}
|
||||||
|
|
||||||
|
public function updateOption(Request $request, Option $option) {
|
||||||
|
if(!request()->user()->hasPermission("admin-options-update")) abort(401);
|
||||||
|
|
||||||
|
switch($option->type) {
|
||||||
|
case 'number':
|
||||||
|
$type_validation = 'numeric';
|
||||||
|
if($option->min) $type_validation .= '|min:'.$option->min;
|
||||||
|
if($option->max) $type_validation .= '|max:'.$option->max;
|
||||||
|
break;
|
||||||
|
case 'boolean':
|
||||||
|
$type_validation = 'boolean';
|
||||||
|
break;
|
||||||
|
case 'select':
|
||||||
|
$type_validation = 'in:'.implode(',', $option->options);
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
$type_validation = 'string';
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
$request->validate([
|
||||||
|
'value' => [
|
||||||
|
'required',
|
||||||
|
$type_validation
|
||||||
|
]
|
||||||
|
]);
|
||||||
|
|
||||||
|
$option->value = request()->input('value');
|
||||||
|
$option->save();
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'message' => 'Option updated successfully'
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
public function getPermissionsAndRoles() {
|
public function getPermissionsAndRoles() {
|
||||||
if(!request()->user()->hasPermission("admin-roles-read")) abort(401);
|
if(!request()->user()->hasPermission("admin-roles-read")) abort(401);
|
||||||
return response()->json([
|
return response()->json([
|
||||||
|
|
|
@ -0,0 +1,16 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Models;
|
||||||
|
|
||||||
|
use Illuminate\Database\Eloquent\Model;
|
||||||
|
|
||||||
|
class Option extends Model
|
||||||
|
{
|
||||||
|
protected $table = 'options';
|
||||||
|
|
||||||
|
protected $fillable = ['name', 'value', 'default', 'type', 'min', 'max'];
|
||||||
|
|
||||||
|
protected $casts = [
|
||||||
|
'options' => 'array'
|
||||||
|
];
|
||||||
|
}
|
|
@ -22,6 +22,7 @@ return [
|
||||||
'admin' => 'r',
|
'admin' => 'r',
|
||||||
'admin-info' => 'r,u',
|
'admin-info' => 'r,u',
|
||||||
'admin-maintenance' => 'r,u',
|
'admin-maintenance' => 'r,u',
|
||||||
|
'admin-options' => 'r,u',
|
||||||
'admin-roles' => 'r,u'
|
'admin-roles' => 'r,u'
|
||||||
],
|
],
|
||||||
'admin' => [
|
'admin' => [
|
||||||
|
@ -33,6 +34,7 @@ return [
|
||||||
'logs' => 'lr',
|
'logs' => 'lr',
|
||||||
'admin' => 'r',
|
'admin' => 'r',
|
||||||
'admin-info' => 'r,u',
|
'admin-info' => 'r,u',
|
||||||
|
'admin-options' => 'r,u',
|
||||||
'admin-roles' => 'r,u'
|
'admin-roles' => 'r,u'
|
||||||
],
|
],
|
||||||
'chief' => [
|
'chief' => [
|
||||||
|
|
|
@ -0,0 +1,34 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the migrations.
|
||||||
|
*/
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::create('options', function (Blueprint $table) {
|
||||||
|
$table->id();
|
||||||
|
$table->string('name')->unique();
|
||||||
|
$table->text('value')->nullable();
|
||||||
|
$table->text('default')->nullable();
|
||||||
|
$table->enum('type', ['number', 'string', 'boolean', 'select'])->default('string');
|
||||||
|
$table->json('options')->nullable();
|
||||||
|
$table->float('min')->nullable();
|
||||||
|
$table->float('max')->nullable();
|
||||||
|
$table->timestamps();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reverse the migrations.
|
||||||
|
*/
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::dropIfExists('options');
|
||||||
|
}
|
||||||
|
};
|
|
@ -0,0 +1,28 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Database\Seeders;
|
||||||
|
|
||||||
|
use Illuminate\Database\Console\Seeds\WithoutModelEvents;
|
||||||
|
use Illuminate\Database\Seeder;
|
||||||
|
|
||||||
|
class OptionsSeeder extends Seeder
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* Run the database seeds.
|
||||||
|
*/
|
||||||
|
public function run(): void
|
||||||
|
{
|
||||||
|
$options = [
|
||||||
|
[
|
||||||
|
'name' => 'service_place_selection_manual',
|
||||||
|
'value' => true,
|
||||||
|
'type' => 'boolean'
|
||||||
|
]
|
||||||
|
];
|
||||||
|
|
||||||
|
foreach ($options as $option) {
|
||||||
|
$option['default'] = $option['value'];
|
||||||
|
\App\Models\Option::create($option);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -117,6 +117,9 @@ Route::middleware('auth:sanctum')->group( function () {
|
||||||
Route::post('/admin/telegramBot/setWebhook', [AdminController::class, 'setTelegramWebhook']);
|
Route::post('/admin/telegramBot/setWebhook', [AdminController::class, 'setTelegramWebhook']);
|
||||||
Route::post('/admin/telegramBot/unsetWebhook', [AdminController::class, 'unsetTelegramWebhook']);
|
Route::post('/admin/telegramBot/unsetWebhook', [AdminController::class, 'unsetTelegramWebhook']);
|
||||||
|
|
||||||
|
Route::get('/admin/options', [AdminController::class, 'getOptions']);
|
||||||
|
Route::put('/admin/options/{option}', [AdminController::class, 'updateOption']);
|
||||||
|
|
||||||
Route::get('/admin/permissionsAndRoles', [AdminController::class, 'getPermissionsAndRoles']);
|
Route::get('/admin/permissionsAndRoles', [AdminController::class, 'getPermissionsAndRoles']);
|
||||||
Route::post('/admin/roles', [AdminController::class, 'updateRoles']);
|
Route::post('/admin/roles', [AdminController::class, 'updateRoles']);
|
||||||
});
|
});
|
||||||
|
|
|
@ -19,6 +19,12 @@ const routes: Routes = [{
|
||||||
canActivate: [AuthorizeGuard],
|
canActivate: [AuthorizeGuard],
|
||||||
data: {permissionsRequired: ['admin-read', 'admin-maintenance-read']}
|
data: {permissionsRequired: ['admin-read', 'admin-maintenance-read']}
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'options',
|
||||||
|
loadChildren: () => import('./options/admin-options.module').then(m => m.AdminOptionsModule),
|
||||||
|
canActivate: [AuthorizeGuard],
|
||||||
|
data: {permissionsRequired: ['admin-read', 'admin-options-read']}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: 'roles',
|
path: 'roles',
|
||||||
loadChildren: () => import('./roles/admin-roles.module').then(m => m.AdminRolesModule),
|
loadChildren: () => import('./roles/admin-roles.module').then(m => m.AdminRolesModule),
|
||||||
|
|
|
@ -21,6 +21,7 @@ export class AdminComponent implements OnInit {
|
||||||
tabs: ITab[] = [
|
tabs: ITab[] = [
|
||||||
{ title: 'info', id: 'info', active: false, permissionsRequired: ['admin-read', 'admin-info-read'] },
|
{ title: 'info', id: 'info', active: false, permissionsRequired: ['admin-read', 'admin-info-read'] },
|
||||||
{ title: 'maintenance', id: 'maintenance', active: false, permissionsRequired: ['admin-read', 'admin-maintenance-read'] },
|
{ title: 'maintenance', id: 'maintenance', active: false, permissionsRequired: ['admin-read', 'admin-maintenance-read'] },
|
||||||
|
{ title: 'options', id: 'options', active: false, permissionsRequired: ['admin-read', 'admin-options-read'] },
|
||||||
{ title: 'roles', id: 'roles', active: false, permissionsRequired: ['admin-read', 'admin-roles-read'] }
|
{ title: 'roles', id: 'roles', active: false, permissionsRequired: ['admin-read', 'admin-roles-read'] }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,11 @@
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
import { AdminOptionsComponent } from './admin-options.component';
|
||||||
|
|
||||||
|
const routes: Routes = [{ path: '', component: AdminOptionsComponent }];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [RouterModule.forChild(routes)],
|
||||||
|
exports: [RouterModule]
|
||||||
|
})
|
||||||
|
export class AdminOptionsRoutingModule { }
|
|
@ -0,0 +1,48 @@
|
||||||
|
<table class="table table-responsive table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{{ 'name'|translate|ftitlecase }}</th>
|
||||||
|
<th>{{ 'value'|translate|ftitlecase }}</th>
|
||||||
|
<th>{{ 'action'|translate|ftitlecase }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
<tr *ngFor="let option of options">
|
||||||
|
<td>{{ 'options.'+option.name|translate }}</td>
|
||||||
|
|
||||||
|
<!-- Data type specific input fields -->
|
||||||
|
<td *ngIf="option.type === 'number'">
|
||||||
|
<input
|
||||||
|
type="number" class="form-control"
|
||||||
|
[(ngModel)]="option.value" name="{{ option.name }}" [min]="option.min" [max]="option.max"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td *ngIf="option.type === 'string'">
|
||||||
|
<input
|
||||||
|
type="text" class="form-control"
|
||||||
|
[(ngModel)]="option.value" name="{{ option.name }}"
|
||||||
|
/>
|
||||||
|
</td>
|
||||||
|
<td *ngIf="option.type === 'boolean'">
|
||||||
|
<div class="form-check form-switch">
|
||||||
|
<input
|
||||||
|
class="form-check-input custom-check-input" type="checkbox" role="switch"
|
||||||
|
[(ngModel)]="option.value" name="{{ option.name }}"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
<td *ngIf="option.type === 'select'">
|
||||||
|
<select *ngIf="option.options" class="form-select" [(ngModel)]="option.value" name="{{ option.name }}">
|
||||||
|
<option *ngFor="let optionValue of option.options" [value]="optionValue">{{ 'options.'+optionValue|translate }}</option>
|
||||||
|
</select>
|
||||||
|
<div class="alert alert-warning" role="alert" *ngIf="!option.options">
|
||||||
|
{{ 'options.no_selection_available'|translate }}
|
||||||
|
</div>
|
||||||
|
</td>
|
||||||
|
|
||||||
|
<td>
|
||||||
|
<button (click)="updateOption(option.id)" class="btn btn-primary" [disabled]="option.value === option._origValue || option._updating">{{ 'update'|translate|ftitlecase }}</button>
|
||||||
|
</td>
|
||||||
|
</tr>
|
||||||
|
</tbody>
|
||||||
|
</table>
|
|
@ -0,0 +1,4 @@
|
||||||
|
.custom-check-input {
|
||||||
|
width: 3.6rem;
|
||||||
|
height: 1.8rem;
|
||||||
|
}
|
|
@ -0,0 +1,85 @@
|
||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { Router } from '@angular/router';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { ApiClientService } from 'src/app/_services/api-client.service';
|
||||||
|
import { AuthService } from 'src/app/_services/auth.service';
|
||||||
|
import Swal from 'sweetalert2';
|
||||||
|
|
||||||
|
@Component({
|
||||||
|
selector: 'app-admin-options',
|
||||||
|
templateUrl: './admin-options.component.html',
|
||||||
|
styleUrls: ['./admin-options.component.scss']
|
||||||
|
})
|
||||||
|
export class AdminOptionsComponent implements OnInit {
|
||||||
|
options: any[] = [];
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
private translateService: TranslateService,
|
||||||
|
private api: ApiClientService,
|
||||||
|
public auth: AuthService,
|
||||||
|
private router: Router
|
||||||
|
) { }
|
||||||
|
|
||||||
|
getOptions() {
|
||||||
|
this.api.get('admin/options').then((res: any) => {
|
||||||
|
res.forEach((option: any) => {
|
||||||
|
switch (option.type) {
|
||||||
|
case 'boolean':
|
||||||
|
option.value = option.value === '1' || option.value === 'true';
|
||||||
|
break;
|
||||||
|
case 'number':
|
||||||
|
option.value = parseFloat(option.value);
|
||||||
|
break;
|
||||||
|
case 'string':
|
||||||
|
option.value = option.value.toString();
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
//Add properties used in the UI
|
||||||
|
option._origValue = option.value;
|
||||||
|
option._updating = false;
|
||||||
|
});
|
||||||
|
this.options = res;
|
||||||
|
console.log(this.options);
|
||||||
|
}).catch((err: any) => {
|
||||||
|
console.error(err);
|
||||||
|
Swal.fire({
|
||||||
|
title: this.translateService.instant("error_title"),
|
||||||
|
text: err.error.message,
|
||||||
|
icon: 'error',
|
||||||
|
confirmButtonText: 'Ok'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
ngOnInit() {
|
||||||
|
this.getOptions();
|
||||||
|
}
|
||||||
|
|
||||||
|
updateOption(optionId: number) {
|
||||||
|
let option = this.options.find(o => o.id === optionId);
|
||||||
|
option._updating = true;
|
||||||
|
console.log(option);
|
||||||
|
this.api.put('admin/options/'+option.id, {
|
||||||
|
value: option.value
|
||||||
|
}).then((res: any) => {
|
||||||
|
console.log(res);
|
||||||
|
option._origValue = option.value;
|
||||||
|
Swal.fire({
|
||||||
|
title: this.translateService.instant("success_title"),
|
||||||
|
text: this.translateService.instant("admin.option_update_success"),
|
||||||
|
icon: 'success',
|
||||||
|
confirmButtonText: 'Ok'
|
||||||
|
});
|
||||||
|
}).catch((err: any) => {
|
||||||
|
console.error(err);
|
||||||
|
Swal.fire({
|
||||||
|
title: this.translateService.instant("error_title"),
|
||||||
|
text: err.error.message,
|
||||||
|
icon: 'error',
|
||||||
|
confirmButtonText: 'Ok'
|
||||||
|
});
|
||||||
|
}).finally(() => {
|
||||||
|
option._updating = false;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,23 @@
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CommonModule } from '@angular/common';
|
||||||
|
import { FormsModule } from '@angular/forms';
|
||||||
|
|
||||||
|
import { TranslationModule } from '../../../translation.module';
|
||||||
|
import { FirstLetterUppercasePipe } from '../../../_pipes/first-letter-uppercase.pipe';
|
||||||
|
|
||||||
|
import { AdminOptionsComponent } from './admin-options.component';
|
||||||
|
import { AdminOptionsRoutingModule } from './admin-options-routing.module';
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
declarations: [
|
||||||
|
AdminOptionsComponent
|
||||||
|
],
|
||||||
|
imports: [
|
||||||
|
CommonModule,
|
||||||
|
FormsModule,
|
||||||
|
AdminOptionsRoutingModule,
|
||||||
|
TranslationModule,
|
||||||
|
FirstLetterUppercasePipe
|
||||||
|
]
|
||||||
|
})
|
||||||
|
export class AdminOptionsModule { }
|
|
@ -65,7 +65,9 @@
|
||||||
"env_delete": "delete .env",
|
"env_delete": "delete .env",
|
||||||
"env_delete_title": "Delete .env file",
|
"env_delete_title": "Delete .env file",
|
||||||
"env_delete_confirm": "Are you sure you want to delete the .env file?",
|
"env_delete_confirm": "Are you sure you want to delete the .env file?",
|
||||||
"env_delete_success": ".env deleted successfully"
|
"env_delete_success": ".env deleted successfully",
|
||||||
|
"options": "Options",
|
||||||
|
"option_update_success": "Option updated successfully"
|
||||||
},
|
},
|
||||||
"table": {
|
"table": {
|
||||||
"remove_service_confirm": "Are you sure you want to remove this service?",
|
"remove_service_confirm": "Are you sure you want to remove this service?",
|
||||||
|
@ -185,6 +187,10 @@
|
||||||
"file_too_big": "File too big",
|
"file_too_big": "File too big",
|
||||||
"password_min_length": "Password must be at least 6 characters long"
|
"password_min_length": "Password must be at least 6 characters long"
|
||||||
},
|
},
|
||||||
|
"options": {
|
||||||
|
"service_place_selection_manual": "Manual place selection for services",
|
||||||
|
"no_selection_available": "No selection available"
|
||||||
|
},
|
||||||
"update_available": "Update available",
|
"update_available": "Update available",
|
||||||
"update_available_text": "A new version of the application is available. Do you want to update now?",
|
"update_available_text": "A new version of the application is available. Do you want to update now?",
|
||||||
"update_now": "Update now",
|
"update_now": "Update now",
|
||||||
|
|
|
@ -65,7 +65,9 @@
|
||||||
"env_delete": "rimuovi .env",
|
"env_delete": "rimuovi .env",
|
||||||
"env_delete_title": "Rimuovi il file .env",
|
"env_delete_title": "Rimuovi il file .env",
|
||||||
"env_delete_text": "Sei sicuro di voler rimuovere il file .env?",
|
"env_delete_text": "Sei sicuro di voler rimuovere il file .env?",
|
||||||
"env_delete_success": ".env rimosso con successo"
|
"env_delete_success": ".env rimosso con successo",
|
||||||
|
"options": "Opzioni",
|
||||||
|
"option_update_success": "Opzione aggiornata con successo"
|
||||||
},
|
},
|
||||||
"table": {
|
"table": {
|
||||||
"remove_service_confirm": "Sei sicuro di voler rimuovere questo intervento?",
|
"remove_service_confirm": "Sei sicuro di voler rimuovere questo intervento?",
|
||||||
|
@ -185,6 +187,10 @@
|
||||||
"file_too_big": "File troppo grande",
|
"file_too_big": "File troppo grande",
|
||||||
"password_min_length": "La password deve essere di almeno 6 caratteri"
|
"password_min_length": "La password deve essere di almeno 6 caratteri"
|
||||||
},
|
},
|
||||||
|
"options": {
|
||||||
|
"service_place_selection_manual": "Seleziona manualmente il luogo dell'intervento",
|
||||||
|
"no_selection_available": "Nessuna selezione disponibile"
|
||||||
|
},
|
||||||
"update_available": "Aggiornamento disponibile",
|
"update_available": "Aggiornamento disponibile",
|
||||||
"update_available_text": "È disponibile un aggiornamento per Allerta. Vuoi aggiornare ora?",
|
"update_available_text": "È disponibile un aggiornamento per Allerta. Vuoi aggiornare ora?",
|
||||||
"update_now": "Aggiorna ora",
|
"update_now": "Aggiorna ora",
|
||||||
|
|
Loading…
Reference in New Issue