Implement admin maintenance functionality

This commit is contained in:
Matteo Gheza 2024-01-11 01:08:07 +01:00
parent f1bc4c4a45
commit 4c6197a4c1
14 changed files with 882 additions and 9 deletions

View File

@ -0,0 +1,119 @@
<?php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Artisan;
use Illuminate\Support\Str;
class AdminController extends Controller
{
public function getDBData() {
if(!request()->user()->hasPermission("admin-maintenance-read")) abort(401);
Artisan::call('db:show', ['--json' => true, '--counts' => true]);
$output = Artisan::output();
$parsedOutput = json_decode($output, true);
$platformConfigUnsets = [
'options',
'collation',
'unix_socket',
'url',
'strict',
'engine',
'timezone'
];
foreach($parsedOutput['platform']['config'] as $key => $value) {
if(in_array($key, $platformConfigUnsets)) unset($parsedOutput['platform']['config'][$key]);
}
$globalSize = 0;
foreach($parsedOutput['tables'] as $table) {
$globalSize += $table['size'];
}
$parsedOutput['global_size'] = $globalSize;
Artisan::call('migrate:status');
$output = Artisan::output();
// Parse the output into an array
$lines = explode("\n", $output);
$migrations = [];
$migrationsRan = 0;
$migrationsPending = 0;
foreach ($lines as $line) {
if (str_contains($line, ' Ran')) {
$migrationName = trim(substr($line, 0, strpos($line, '...')));
$migrations[] = ['name' => $migrationName, 'status' => 'Ran'];
$migrationsRan++;
} elseif (str_contains($line, ' Pending')) {
$migrationName = trim(substr($line, 0, strpos($line, '...')));
$migrations[] = ['name' => $migrationName, 'status' => 'Pending'];
$migrationsPending++;
}
}
$parsedOutput['migrations'] = $migrations;
$parsedOutput['migrations_ran'] = $migrationsRan;
$parsedOutput['migrations_pending'] = $migrationsPending;
return response()->json($parsedOutput);
}
public function runMigrations() {
if(!request()->user()->hasPermission("admin-maintenance-update")) abort(401);
Artisan::call('migrate', ['--force' => true]);
return response()->json([
'message' => 'Migrations ran successfully'
]);
}
public function runSeeding() {
if(!request()->user()->hasPermission("admin-maintenance-update")) abort(401);
Artisan::call('db:seed', ['--force' => true]);
return response()->json([
'message' => 'Seeders ran successfully'
]);
}
public function getMaintenanceMode() {
if(!request()->user()->hasPermission("admin-maintenance-read")) abort(401);
if (App::isDownForMaintenance()) {
return response()->json(['enabled' => true]);
} else {
return response()->json(['enabled' => false]);
}
}
public function updateMaintenanceMode(Request $request) {
if(!request()->user()->hasPermission("admin-maintenance-update")) abort(401);
$request->validate([
'enabled' => 'required|boolean'
]);
$maintenanceMode = $request->input('enabled');
if($maintenanceMode) {
$uuid = Str::uuid()->toString();
$secret = "api/admin/bypass_maintenance/".$uuid;
Artisan::call('down', ['--secret' => $secret]);
return response()->json([
'message' => 'Maintenance mode enabled',
'secret_endpoint' => substr($secret, 4)
]);
} else {
Artisan::call('up');
return response()->json([
'message' => 'Maintenance mode disabled'
]);
}
}
}

View File

@ -7,6 +7,7 @@
"require": {
"php": "^8.1",
"defstudio/telegraph": "^1.38",
"doctrine/dbal": "3.5.1",
"guzzlehttp/guzzle": "^7.8",
"lab404/laravel-impersonate": "^1.7",
"laravel/framework": "^10.0",

395
backend/composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically"
],
"content-hash": "1ef9e17b559ea2d200ce1d82e7c7c353",
"content-hash": "656ebf4814121631af14a935953b360d",
"packages": [
{
"name": "brick/math",
@ -298,6 +298,348 @@
},
"time": "2022-10-27T11:44:00+00:00"
},
{
"name": "doctrine/cache",
"version": "2.2.0",
"source": {
"type": "git",
"url": "https://github.com/doctrine/cache.git",
"reference": "1ca8f21980e770095a31456042471a57bc4c68fb"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/cache/zipball/1ca8f21980e770095a31456042471a57bc4c68fb",
"reference": "1ca8f21980e770095a31456042471a57bc4c68fb",
"shasum": ""
},
"require": {
"php": "~7.1 || ^8.0"
},
"conflict": {
"doctrine/common": ">2.2,<2.4"
},
"require-dev": {
"cache/integration-tests": "dev-master",
"doctrine/coding-standard": "^9",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
"psr/cache": "^1.0 || ^2.0 || ^3.0",
"symfony/cache": "^4.4 || ^5.4 || ^6",
"symfony/var-exporter": "^4.4 || ^5.4 || ^6"
},
"type": "library",
"autoload": {
"psr-4": {
"Doctrine\\Common\\Cache\\": "lib/Doctrine/Common/Cache"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Guilherme Blanco",
"email": "guilhermeblanco@gmail.com"
},
{
"name": "Roman Borschel",
"email": "roman@code-factory.org"
},
{
"name": "Benjamin Eberlei",
"email": "kontakt@beberlei.de"
},
{
"name": "Jonathan Wage",
"email": "jonwage@gmail.com"
},
{
"name": "Johannes Schmitt",
"email": "schmittjoh@gmail.com"
}
],
"description": "PHP Doctrine Cache library is a popular cache implementation that supports many different drivers such as redis, memcache, apc, mongodb and others.",
"homepage": "https://www.doctrine-project.org/projects/cache.html",
"keywords": [
"abstraction",
"apcu",
"cache",
"caching",
"couchdb",
"memcached",
"php",
"redis",
"xcache"
],
"support": {
"issues": "https://github.com/doctrine/cache/issues",
"source": "https://github.com/doctrine/cache/tree/2.2.0"
},
"funding": [
{
"url": "https://www.doctrine-project.org/sponsorship.html",
"type": "custom"
},
{
"url": "https://www.patreon.com/phpdoctrine",
"type": "patreon"
},
{
"url": "https://tidelift.com/funding/github/packagist/doctrine%2Fcache",
"type": "tidelift"
}
],
"time": "2022-05-20T20:07:39+00:00"
},
{
"name": "doctrine/dbal",
"version": "3.5.1",
"source": {
"type": "git",
"url": "https://github.com/doctrine/dbal.git",
"reference": "f38ee8aaca2d58ee88653cb34a6a3880c23f38a5"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/dbal/zipball/f38ee8aaca2d58ee88653cb34a6a3880c23f38a5",
"reference": "f38ee8aaca2d58ee88653cb34a6a3880c23f38a5",
"shasum": ""
},
"require": {
"composer-runtime-api": "^2",
"doctrine/cache": "^1.11|^2.0",
"doctrine/deprecations": "^0.5.3|^1",
"doctrine/event-manager": "^1|^2",
"php": "^7.4 || ^8.0",
"psr/cache": "^1|^2|^3",
"psr/log": "^1|^2|^3"
},
"require-dev": {
"doctrine/coding-standard": "10.0.0",
"jetbrains/phpstorm-stubs": "2022.2",
"phpstan/phpstan": "1.8.10",
"phpstan/phpstan-strict-rules": "^1.4",
"phpunit/phpunit": "9.5.25",
"psalm/plugin-phpunit": "0.17.0",
"squizlabs/php_codesniffer": "3.7.1",
"symfony/cache": "^5.4|^6.0",
"symfony/console": "^4.4|^5.4|^6.0",
"vimeo/psalm": "4.29.0"
},
"suggest": {
"symfony/console": "For helpful console commands such as SQL execution and import of files."
},
"bin": [
"bin/doctrine-dbal"
],
"type": "library",
"autoload": {
"psr-4": {
"Doctrine\\DBAL\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Guilherme Blanco",
"email": "guilhermeblanco@gmail.com"
},
{
"name": "Roman Borschel",
"email": "roman@code-factory.org"
},
{
"name": "Benjamin Eberlei",
"email": "kontakt@beberlei.de"
},
{
"name": "Jonathan Wage",
"email": "jonwage@gmail.com"
}
],
"description": "Powerful PHP database abstraction layer (DBAL) with many features for database schema introspection and management.",
"homepage": "https://www.doctrine-project.org/projects/dbal.html",
"keywords": [
"abstraction",
"database",
"db2",
"dbal",
"mariadb",
"mssql",
"mysql",
"oci8",
"oracle",
"pdo",
"pgsql",
"postgresql",
"queryobject",
"sasql",
"sql",
"sqlite",
"sqlserver",
"sqlsrv"
],
"support": {
"issues": "https://github.com/doctrine/dbal/issues",
"source": "https://github.com/doctrine/dbal/tree/3.5.1"
},
"funding": [
{
"url": "https://www.doctrine-project.org/sponsorship.html",
"type": "custom"
},
{
"url": "https://www.patreon.com/phpdoctrine",
"type": "patreon"
},
{
"url": "https://tidelift.com/funding/github/packagist/doctrine%2Fdbal",
"type": "tidelift"
}
],
"time": "2022-10-24T07:26:18+00:00"
},
{
"name": "doctrine/deprecations",
"version": "1.1.2",
"source": {
"type": "git",
"url": "https://github.com/doctrine/deprecations.git",
"reference": "4f2d4f2836e7ec4e7a8625e75c6aa916004db931"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/deprecations/zipball/4f2d4f2836e7ec4e7a8625e75c6aa916004db931",
"reference": "4f2d4f2836e7ec4e7a8625e75c6aa916004db931",
"shasum": ""
},
"require": {
"php": "^7.1 || ^8.0"
},
"require-dev": {
"doctrine/coding-standard": "^9",
"phpstan/phpstan": "1.4.10 || 1.10.15",
"phpstan/phpstan-phpunit": "^1.0",
"phpunit/phpunit": "^7.5 || ^8.5 || ^9.5",
"psalm/plugin-phpunit": "0.18.4",
"psr/log": "^1 || ^2 || ^3",
"vimeo/psalm": "4.30.0 || 5.12.0"
},
"suggest": {
"psr/log": "Allows logging deprecations via PSR-3 logger implementation"
},
"type": "library",
"autoload": {
"psr-4": {
"Doctrine\\Deprecations\\": "lib/Doctrine/Deprecations"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"description": "A small layer on top of trigger_error(E_USER_DEPRECATED) or PSR-3 logging with options to disable all deprecations or selectively for packages.",
"homepage": "https://www.doctrine-project.org/",
"support": {
"issues": "https://github.com/doctrine/deprecations/issues",
"source": "https://github.com/doctrine/deprecations/tree/1.1.2"
},
"time": "2023-09-27T20:04:15+00:00"
},
{
"name": "doctrine/event-manager",
"version": "2.0.0",
"source": {
"type": "git",
"url": "https://github.com/doctrine/event-manager.git",
"reference": "750671534e0241a7c50ea5b43f67e23eb5c96f32"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/doctrine/event-manager/zipball/750671534e0241a7c50ea5b43f67e23eb5c96f32",
"reference": "750671534e0241a7c50ea5b43f67e23eb5c96f32",
"shasum": ""
},
"require": {
"php": "^8.1"
},
"conflict": {
"doctrine/common": "<2.9"
},
"require-dev": {
"doctrine/coding-standard": "^10",
"phpstan/phpstan": "^1.8.8",
"phpunit/phpunit": "^9.5",
"vimeo/psalm": "^4.28"
},
"type": "library",
"autoload": {
"psr-4": {
"Doctrine\\Common\\": "src"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Guilherme Blanco",
"email": "guilhermeblanco@gmail.com"
},
{
"name": "Roman Borschel",
"email": "roman@code-factory.org"
},
{
"name": "Benjamin Eberlei",
"email": "kontakt@beberlei.de"
},
{
"name": "Jonathan Wage",
"email": "jonwage@gmail.com"
},
{
"name": "Johannes Schmitt",
"email": "schmittjoh@gmail.com"
},
{
"name": "Marco Pivetta",
"email": "ocramius@gmail.com"
}
],
"description": "The Doctrine Event Manager is a simple PHP event system that was built to be used with the various Doctrine projects.",
"homepage": "https://www.doctrine-project.org/projects/event-manager.html",
"keywords": [
"event",
"event dispatcher",
"event manager",
"event system",
"events"
],
"support": {
"issues": "https://github.com/doctrine/event-manager/issues",
"source": "https://github.com/doctrine/event-manager/tree/2.0.0"
},
"funding": [
{
"url": "https://www.doctrine-project.org/sponsorship.html",
"type": "custom"
},
{
"url": "https://www.patreon.com/phpdoctrine",
"type": "patreon"
},
{
"url": "https://tidelift.com/funding/github/packagist/doctrine%2Fevent-manager",
"type": "tidelift"
}
],
"time": "2022-10-12T20:59:15+00:00"
},
{
"name": "doctrine/inflector",
"version": "2.0.8",
@ -3300,6 +3642,55 @@
],
"time": "2023-02-25T19:38:58+00:00"
},
{
"name": "psr/cache",
"version": "3.0.0",
"source": {
"type": "git",
"url": "https://github.com/php-fig/cache.git",
"reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/php-fig/cache/zipball/aa5030cfa5405eccfdcb1083ce040c2cb8d253bf",
"reference": "aa5030cfa5405eccfdcb1083ce040c2cb8d253bf",
"shasum": ""
},
"require": {
"php": ">=8.0.0"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.0.x-dev"
}
},
"autoload": {
"psr-4": {
"Psr\\Cache\\": "src/"
}
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "PHP-FIG",
"homepage": "https://www.php-fig.org/"
}
],
"description": "Common interface for caching libraries",
"keywords": [
"cache",
"psr",
"psr-6"
],
"support": {
"source": "https://github.com/php-fig/cache/tree/3.0.0"
},
"time": "2021-02-03T23:26:27+00:00"
},
{
"name": "psr/clock",
"version": "1.0.0",
@ -9697,5 +10088,5 @@
"php": "^8.1"
},
"platform-dev": [],
"plugin-api-version": "2.6.0"
"plugin-api-version": "2.3.0"
}

View File

@ -21,6 +21,7 @@ return [
'logs' => 'r',
'admin' => 'r',
'admin-info' => 'r,u',
'admin-maintenance' => 'r,u',
'admin-roles' => 'r,u'
],
'admin' => [

View File

@ -15,6 +15,7 @@ use App\Http\Controllers\PlacesController;
use App\Http\Controllers\ServiceTypeController;
use App\Http\Controllers\TrainingCourseTypeController;
use App\Http\Controllers\TrainingController;
use App\Http\Controllers\AdminController;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Artisan;
use \Matthewbdaly\ETagMiddleware\ETag;
@ -90,6 +91,13 @@ Route::middleware('auth:sanctum')->group( function () {
Route::post('/telegram_login_token', [TelegramController::class, 'loginToken']);
Route::post('/logout', [AuthController::class, 'logout']);
Route::get('/admin/db', [AdminController::class, 'getDBData']);
Route::post('/admin/runMigrations', [AdminController::class, 'runMigrations']);
Route::post('/admin/runSeeding', [AdminController::class, 'runSeeding']);
Route::get('/admin/maintenanceMode', [AdminController::class, 'getMaintenanceMode']);
Route::post('/admin/maintenanceMode', [AdminController::class, 'updateMaintenanceMode']);
});
Route::middleware('signed')->group( function () {

View File

@ -13,6 +13,12 @@ const routes: Routes = [{
canActivate: [AuthorizeGuard],
data: {permissionsRequired: ['admin-read', 'admin-info-read']}
},
{
path: 'maintenance',
loadChildren: () => import('./maintenance/admin-maintenance.module').then(m => m.AdminMaintenanceModule),
canActivate: [AuthorizeGuard],
data: {permissionsRequired: ['admin-read', 'admin-maintenance-read']}
},
{
path: 'roles',
loadChildren: () => import('./roles/admin-roles.module').then(m => m.AdminRolesModule),

View File

@ -20,6 +20,7 @@ export class AdminComponent implements OnInit {
currRoute: string | undefined = '';
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: 'roles', id: 'roles', active: false, permissionsRequired: ['admin-read', 'admin-roles-read'] }
];
@ -34,7 +35,7 @@ export class AdminComponent implements OnInit {
// Translate tab titles
this.tabs.forEach((t) => {
this.translate.get(`menu.${t.title}`).subscribe((res: string) => {
this.translate.get(`admin.${t.title}`).subscribe((res: string) => {
t.title = res;
});
});

View File

@ -0,0 +1,11 @@
import { NgModule } from '@angular/core';
import { RouterModule, Routes } from '@angular/router';
import { AdminMaintenanceComponent } from './admin-maintenance.component';
const routes: Routes = [{ path: '', component: AdminMaintenanceComponent }];
@NgModule({
imports: [RouterModule.forChild(routes)],
exports: [RouterModule]
})
export class AdminMaintenanceRoutingModule { }

View File

@ -0,0 +1,111 @@
<div class="container">
<div class="row">
<div class="col-md-6 right-border">
<div class="row">
<h4>Database:</h4>
<div class="table-responsive ms-3 pe-5">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>{{ 'property'|translate|ftitlecase }}</th>
<th>{{ 'value'|translate|ftitlecase }}</th>
</tr>
</thead>
<tbody>
<tr *ngIf="db && db.migrations_ran">
<td>{{ 'admin.installed_migrations'|translate|ftitlecase }}</td>
<td [class.text-bg-danger]="db.migrations_pending > 0">
{{ db.migrations_ran }}/{{ db.migrations_ran+db.migrations_pending }}
</td>
</tr>
<tr *ngIf="db && db.platform.open_connections">
<td>{{ 'admin.open_connections'|translate|ftitlecase }}</td>
<td>{{ db.platform.open_connections }}</td>
</tr>
<tr *ngIf="db && db.global_size">
<td>{{ 'size'|translate|ftitlecase }}</td>
<td>{{ sizeToHuman(db.global_size) }}</td>
</tr>
<tr *ngIf="db && db.platform.name">
<td>{{ 'admin.db_engine_name'|translate|ftitlecase }}</td>
<td>{{ db.platform.name }}</td>
</tr>
<tr *ngIf="db && db.platform.config.database">
<td>{{ 'admin.database'|translate|ftitlecase }}</td>
<td>{{ db.platform.config.database }}</td>
</tr>
<tr *ngIf="db && db.platform.config.host">
<td>{{ 'admin.host'|translate|ftitlecase }}</td>
<td>{{ db.platform.config.host }}</td>
</tr>
<tr *ngIf="db && db.platform.config.port">
<td>{{ 'admin.port'|translate|ftitlecase }}</td>
<td>{{ db.platform.config.port }}</td>
</tr>
<tr *ngIf="db && db.platform.config.username">
<td>{{ 'username'|translate|ftitlecase }}</td>
<td>{{ db.platform.config.username }}</td>
</tr>
<tr *ngIf="db && db.platform.config.charset">
<td>{{ 'admin.charset'|translate|ftitlecase }}</td>
<td>{{ db.platform.config.charset }}</td>
</tr>
<tr *ngIf="db && db.platform.config.prefix">
<td>{{ 'admin.prefix'|translate|ftitlecase }}</td>
<td>{{ db.platform.config.prefix }}</td>
</tr>
</tbody>
</table>
</div>
<div class="btn-group-vertical mb-3 ms-3 mt-2 pe-5 ps-5" *ngIf="db && db.migrations_pending >= 0">
<p class="btn-group-label">{{ 'admin.operations'|translate|ftitlecase }}</p>
<button type="button" class="btn btn-lg" (click)="runMigrations()"
[ngClass]="{'blink': db.migrations_pending > 0, 'btn-success': db.migrations_pending > 0, 'btn-primary': db.migrations_pending <= 0}">{{ 'admin.run_migrations'|translate|ftitlecase }}</button>
<button type="button" class="btn btn-lg btn-danger" (click)="runSeeding()">{{ 'admin.run_seeding'|translate|ftitlecase }}</button>
</div>
<hr>
<div class="mb-2 ms-3 ps-5 pe-5" *ngIf="db && db.tables">
<button type="button" class="btn btn-primary" (click)="isTableListCollaped = !isTableListCollaped"
[attr.aria-expanded]="!isTableListCollaped" aria-controls="collapseBasic">
<ng-container *ngIf="isTableListCollaped">{{ 'admin.show_tables'|translate|ftitlecase }}</ng-container>
<ng-container *ngIf="!isTableListCollaped">{{ 'admin.hide_tables'|translate|ftitlecase }}</ng-container>
</button>
</div>
<div class="table-responsive ms-3 pe-5" *ngIf="db && db.tables" [collapse]="isTableListCollaped">
<table class="table table-striped table-bordered">
<thead>
<tr>
<th>{{ 'admin.table'|translate|ftitlecase }}</th>
<th>{{ 'size'|translate|ftitlecase }}</th>
<th>{{ 'admin.rows'|translate|ftitlecase }}</th>
</tr>
</thead>
<tbody>
<tr *ngFor="let row of db.tables">
<td>{{ row.table }}</td>
<td>{{ sizeToHuman(row.size) }}</td>
<td>{{ row.rows }}</td>
</tr>
</tbody>
</table>
</div>
</div>
</div>
<div class="col-md-6">
<div class="row">
<h4>{{ 'admin.updates_and_maintenance_title'|translate }}</h4>
<div class="btn-group-vertical mt-2 ps-5 pe-5">
<p class="btn-group-label">{{ 'maintenance_mode'|translate|ftitlecase }}</p>
<button type="button" class="btn btn-lg btn-danger" (click)="updateMaintenanceMode(true)" [disabled]="isMaintenanceModeActive">{{ 'enable'|translate|ftitlecase }}</button>
<button type="button" class="btn btn-lg btn-warning" (click)="updateMaintenanceMode(false)" [disabled]="!isMaintenanceModeActive">{{ 'disable'|translate|ftitlecase }}</button>
</div>
</div>
<hr>
</div>
</div>
</div>

View File

@ -0,0 +1,17 @@
.btn-group-label {
margin-bottom: 0px !important;
}
@keyframes blink {
0% { opacity: 1; }
50% { opacity: 0.7; }
100% { opacity: 1; }
}
.blink {
animation: blink 2s linear infinite;
}
.right-border {
border-right: #0000004a 1px solid
}

View File

@ -0,0 +1,127 @@
import { Component, OnInit } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import { ApiClientService } from 'src/app/_services/api-client.service';
import Swal from 'sweetalert2';
@Component({
selector: 'app-admin-maintenance',
templateUrl: './admin-maintenance.component.html',
styleUrls: ['./admin-maintenance.component.scss']
})
export class AdminMaintenanceComponent implements OnInit {
public db: any | undefined = undefined;
public isTableListCollaped = true;
public isMaintenanceModeActive = false;
constructor(
private translateService: TranslateService,
private api: ApiClientService
) { }
sizeToHuman(size: number) {
const i = Math.floor(Math.log(size) / Math.log(1024));
return (size / Math.pow(1024, i)).toFixed(2) + ' ' + ['B', 'kB', 'MB', 'GB', 'TB'][i];
}
getDB() {
this.api.get('admin/db').then((res: any) => {
this.db = res;
console.log(this.db);
}).catch((err: any) => {
Swal.fire({
icon: 'error',
title: this.translateService.instant('error_title'),
text: err.message
});
});
}
getMaintenanceMode() {
this.api.get('admin/maintenanceMode').then((res: any) => {
this.isMaintenanceModeActive = res.enabled;
}).catch((err: any) => {
Swal.fire({
icon: 'error',
title: this.translateService.instant('error_title'),
text: err.message
});
});
}
ngOnInit(): void {
this.getDB();
this.getMaintenanceMode();
}
runMigrations() {
this.api.post('admin/runMigrations').then((res: any) => {
Swal.fire({
icon: 'success',
title: this.translateService.instant('success_title'),
text: this.translateService.instant('admin.run_migrations_success')
});
this.getDB();
}).catch((err: any) => {
Swal.fire({
icon: 'error',
title: this.translateService.instant('error_title'),
text: err.message
});
});
}
runSeeding() {
//Require confirmation before proceeding
Swal.fire({
title: this.translateService.instant('admin.run_seeding_confirm_title'),
text: this.translateService.instant('admin.run_seeding_confirm_text'),
icon: 'warning',
showCancelButton: true,
confirmButtonText: this.translateService.instant('yes'),
cancelButtonText: this.translateService.instant('no')
}).then((result) => {
if (result.isConfirmed) {
this.api.post('admin/runSeeding').then((res: any) => {
Swal.fire({
icon: 'success',
title: this.translateService.instant('success_title'),
text: this.translateService.instant('admin.run_seeding_success')
});
this.getDB();
}).catch((err: any) => {
Swal.fire({
icon: 'error',
title: this.translateService.instant('error_title'),
text: err.message
});
});
}
});
}
updateMaintenanceMode(enabled: boolean) {
this.api.post('admin/maintenanceMode', { enabled }).then((res: any) => {
this.isMaintenanceModeActive = enabled;
if(enabled) {
//Call res.secret_endpoint to bypass maintenance mode in this session
this.api.get(res.secret_endpoint).then((res: any) => {
console.log(res);
}).catch((err: any) => {
console.log(err);
});
}
Swal.fire({
icon: 'success',
title: this.translateService.instant('success_title'),
text: this.translateService.instant('admin.maintenance_mode_success')
});
}).catch((err: any) => {
Swal.fire({
icon: 'error',
title: this.translateService.instant('error_title'),
text: err.message
});
});
}
}

View File

@ -0,0 +1,23 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { CollapseModule } from 'ngx-bootstrap/collapse';
import { TranslationModule } from '../../../translation.module';
import { FirstLetterUppercasePipe } from '../../../_pipes/first-letter-uppercase.pipe';
import { AdminMaintenanceComponent } from './admin-maintenance.component';
import { AdminMaintenanceRoutingModule } from './admin-maintenance-routing.module';
@NgModule({
declarations: [
AdminMaintenanceComponent
],
imports: [
CommonModule,
AdminMaintenanceRoutingModule,
CollapseModule.forRoot(),
TranslationModule,
FirstLetterUppercasePipe
]
})
export class AdminMaintenanceModule { }

View File

@ -7,9 +7,33 @@
"admin": "Admin",
"logout": "Logout",
"stop_impersonating": "Stop impersonating",
"hi": "hi",
"info": "Info",
"roles": "Roles"
"hi": "hi"
},
"admin": {
"info": "info",
"maintenance": "maintenance",
"roles": "roles",
"installed_migrations": "installed migrations",
"open_connections": "open connections",
"db_engine_name": "database engine name",
"database": "database",
"host": "host",
"port": "port",
"charset": "charset",
"prefix": "prefix",
"operations": "operations",
"run_migrations": "run migrations",
"run_migrations_success": "Migrations executed successfully",
"run_seeding": "run seeding",
"run_seeding_confirm_title": "Are you sure you want to run the seeding?",
"run_seeding_confirm_text": "This action cannot be undone, and data can be loss in the DB.",
"run_seeding_success": "Seeding executed successfully",
"show_tables": "show tables list",
"hide_tables": "hide tables list",
"table": "table",
"rows": "rows",
"updates_and_maintenance_title": "Updates and maintenance",
"maintenance_mode_success": "Maintenance mode updated successfully"
},
"table": {
"yes_remove": "Yes, remove",
@ -130,6 +154,9 @@
"document_format_not_supported": "Document format not supported",
"file_too_big": "File too big"
},
"enable": "enable",
"disable": "disable",
"maintenance_mode": "maintenance mode",
"maintenance_mode_warning": "The application is currently in maintenance mode. Some features may not be available.",
"property": "property",
"value": "value",
@ -190,6 +217,7 @@
"upload_medical_examination_certificate": "upload medical examination certificate",
"upload_training_course_doc": "upload training course document",
"clothings": "clothings",
"size": "size",
"suit_size": "suit size",
"boot_size": "boot size",
"medical_examinations": "medical examinations",

View File

@ -7,9 +7,33 @@
"admin": "Amministrazione",
"logout": "Logout",
"stop_impersonating": "Torna al vero account",
"hi": "Ciao",
"hi": "Ciao"
},
"admin": {
"info": "Info",
"roles": "Ruoli"
"maintenance": "Manutenzione",
"roles": "Ruoli",
"installed_migrations": "migrazioni installate",
"open_connections": "connessioni aperte",
"db_engine_name": "nome del motore del database",
"database": "database",
"host": "host",
"port": "porta",
"charset": "charset",
"prefix": "prefisso",
"operations": "operazioni",
"run_migrations": "esegui migrazioni",
"run_migrations_success": "Migrazioni eseguite con successo",
"run_seeding": "esegui seeding",
"run_seeding_confirm_title": "Sei sicuro di voler eseguire il seeding?",
"run_seeding_confirm_text": "Questa operazione potrebbe sovrascrivere i dati presenti nel database.",
"run_seeding_success": "Seeding eseguito con successo",
"show_tables": "mostra lista tabelle",
"hide_tables": "nascondi lista tabelle",
"table": "tabella",
"rows": "righe",
"updates_and_maintenance_title": "Aggiornamenti e manutenzione",
"maintenance_mode_success": "Modalità manutenzione aggiornata con successo"
},
"table": {
"yes_remove": "Si, rimuovi",
@ -79,7 +103,6 @@
"insert_code": "Inserisci il progressivo dell'intervento",
"other_crew_members": "Altri membri della squadra",
"select_service_type": "Seleziona una tipologia di intervento",
"type_already_exists": "La tipologia è già presente",
"type_added_successfully": "Tipologia aggiunta con successo",
"service_added_successfully": "Intervento aggiunto con successo",
"service_add_failed": "Errore durante l'aggiunta dell'intervento. Riprovare più tardi",
@ -126,9 +149,14 @@
"validation": {
"place_min_length": "Il nome della località deve essere di almeno 3 caratteri",
"type_must_be_two_characters_long": "La tipologia deve essere di almeno 2 caratteri",
"type_already_exists": "La tipologia è già presente",
"image_format_not_supported": "Formato immagine non supportato",
"document_format_not_supported": "Formato documento non supportato",
"file_too_big": "File troppo grande"
},
"enable": "attiva",
"disable": "disattiva",
"maintenance_mode": "modalità manutenzione",
"maintenance_mode_warning": "Il gestionale è in manutenzione. Alcune funzionalità potrebbero non essere disponibili.",
"property": "proprietà",
"value": "valore",
@ -189,6 +217,7 @@
"upload_medical_examination_certificate": "carica certificato visita medica",
"upload_training_course_doc": "carica Ordine del Giorno",
"clothings": "indumenti",
"size": "dimensione",
"suit_size": "taglia tuta",
"boot_size": "taglia scarponi",
"medical_examinations": "visite mediche",