Implement admin maintenance functionality
This commit is contained in:
parent
f1bc4c4a45
commit
4c6197a4c1
|
@ -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'
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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",
|
||||
|
|
|
@ -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"
|
||||
}
|
||||
|
|
|
@ -21,6 +21,7 @@ return [
|
|||
'logs' => 'r',
|
||||
'admin' => 'r',
|
||||
'admin-info' => 'r,u',
|
||||
'admin-maintenance' => 'r,u',
|
||||
'admin-roles' => 'r,u'
|
||||
],
|
||||
'admin' => [
|
||||
|
|
|
@ -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 () {
|
||||
|
|
|
@ -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),
|
||||
|
|
|
@ -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;
|
||||
});
|
||||
});
|
||||
|
|
|
@ -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 { }
|
|
@ -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>
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
|
@ -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 { }
|
|
@ -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",
|
||||
|
|
|
@ -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",
|
||||
|
|
Loading…
Reference in New Issue