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\Role;
|
||||
use App\Models\Permission;
|
||||
use App\Models\Option;
|
||||
|
||||
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() {
|
||||
if(!request()->user()->hasPermission("admin-roles-read")) abort(401);
|
||||
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-info' => 'r,u',
|
||||
'admin-maintenance' => 'r,u',
|
||||
'admin-options' => 'r,u',
|
||||
'admin-roles' => 'r,u'
|
||||
],
|
||||
'admin' => [
|
||||
|
@ -33,6 +34,7 @@ return [
|
|||
'logs' => 'lr',
|
||||
'admin' => 'r',
|
||||
'admin-info' => 'r,u',
|
||||
'admin-options' => 'r,u',
|
||||
'admin-roles' => 'r,u'
|
||||
],
|
||||
'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/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::post('/admin/roles', [AdminController::class, 'updateRoles']);
|
||||
});
|
||||
|
|
|
@ -19,6 +19,12 @@ const routes: Routes = [{
|
|||
canActivate: [AuthorizeGuard],
|
||||
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',
|
||||
loadChildren: () => import('./roles/admin-roles.module').then(m => m.AdminRolesModule),
|
||||
|
|
|
@ -21,6 +21,7 @@ export class AdminComponent implements OnInit {
|
|||
tabs: ITab[] = [
|
||||
{ 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: 'options', id: 'options', active: false, permissionsRequired: ['admin-read', 'admin-options-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_title": "Delete .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": {
|
||||
"remove_service_confirm": "Are you sure you want to remove this service?",
|
||||
|
@ -185,6 +187,10 @@
|
|||
"file_too_big": "File too big",
|
||||
"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_text": "A new version of the application is available. Do you want to update now?",
|
||||
"update_now": "Update now",
|
||||
|
|
|
@ -65,7 +65,9 @@
|
|||
"env_delete": "rimuovi .env",
|
||||
"env_delete_title": "Rimuovi 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": {
|
||||
"remove_service_confirm": "Sei sicuro di voler rimuovere questo intervento?",
|
||||
|
@ -185,6 +187,10 @@
|
|||
"file_too_big": "File troppo grande",
|
||||
"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_text": "È disponibile un aggiornamento per Allerta. Vuoi aggiornare ora?",
|
||||
"update_now": "Aggiorna ora",
|
||||
|
|
Loading…
Reference in New Issue