mirror of
https://github.com/getnamingo/registry.git
synced 2025-05-29 17:00:06 +02:00
Added server health page in CP
This commit is contained in:
parent
d04d940a14
commit
0d8eda7ea8
7 changed files with 306 additions and 2 deletions
|
@ -7,6 +7,7 @@ use Psr\Http\Message\ResponseInterface as Response;
|
|||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Nyholm\Psr7\Stream;
|
||||
use Utopia\System\System;
|
||||
|
||||
class ReportsController extends Controller
|
||||
{
|
||||
|
@ -100,4 +101,125 @@ class ReportsController extends Controller
|
|||
// Output the CSV content to the response body
|
||||
return $response->withBody($stream);
|
||||
}
|
||||
|
||||
public function serverHealth(Request $request, Response $response)
|
||||
{
|
||||
if ($_SESSION["auth_roles"] != 0) {
|
||||
return $response->withHeader('Location', '/dashboard')->withStatus(302);
|
||||
}
|
||||
|
||||
$csrfTokenName = $this->container->get('csrf')->getTokenName();
|
||||
$csrfTokenValue = $this->container->get('csrf')->getTokenValue();
|
||||
|
||||
$system = new System();
|
||||
|
||||
$serverHealth = [
|
||||
'getCPUCores' => $system->getCPUCores(),
|
||||
'getCPUUsage' => $system->getCPUUsage(),
|
||||
'getMemoryTotal' => $system->getMemoryTotal(),
|
||||
'getMemoryFree' => $system->getMemoryFree(),
|
||||
'getDiskTotal' => $system->getDiskTotal(),
|
||||
'getDiskFree' => $system->getDiskFree()
|
||||
];
|
||||
|
||||
$logFile = '/var/log/namingo/backup.log';
|
||||
|
||||
// Check if the file exists
|
||||
if (!file_exists($logFile)) {
|
||||
$backupSummary = "Backup log file not found.";
|
||||
} else {
|
||||
// Read and decode JSON file
|
||||
$logData = json_decode(file_get_contents($logFile), true);
|
||||
|
||||
if (json_last_error() !== JSON_ERROR_NONE || !is_array($logData)) {
|
||||
$backupSummary = "Invalid JSON format in backup log file.";
|
||||
} else {
|
||||
// Start building the summary
|
||||
$backupSummary = "Backup Summary:\n";
|
||||
$backupSummary .= "Timestamp: " . date('Y-m-d H:i:s', $logData['timestamp'] ?? time()) . "\n";
|
||||
$backupSummary .= "Duration: " . round($logData['duration'] ?? 0, 2) . " seconds\n";
|
||||
$backupSummary .= "Total Backups: " . ($logData['backupCount'] ?? 0) . "\n";
|
||||
$backupSummary .= "Failed Backups: " . ($logData['backupFailed'] ?? 0) . "\n";
|
||||
$backupSummary .= "Errors: " . ($logData['errorCount'] ?? 0) . "\n";
|
||||
|
||||
if (!empty($logData['backups'])) {
|
||||
foreach ($logData['backups'] as $backup) {
|
||||
$backupSummary .= "\nBackup: " . ($backup['name'] ?? 'Unknown') . "\n";
|
||||
$backupSummary .= "- Status: " . (($backup['status'] ?? 1) === 0 ? 'Success' : 'Failed') . "\n";
|
||||
$backupSummary .= "- Checks: " . ($backup['checks']['executed'] ?? 0) . " executed, " . ($backup['checks']['failed'] ?? 0) . " failed\n";
|
||||
$backupSummary .= "- Syncs: " . ($backup['syncs']['executed'] ?? 0) . " executed, " . ($backup['syncs']['failed'] ?? 0) . " failed\n";
|
||||
$backupSummary .= "- Cleanup: " . ($backup['cleanup']['executed'] ?? 0) . " executed, " . ($backup['cleanup']['failed'] ?? 0) . " failed\n";
|
||||
}
|
||||
}
|
||||
|
||||
if (!empty($logData['debug'])) {
|
||||
$backupSummary .= "\nDebug Info (last 5 entries):\n";
|
||||
$debugEntries = array_slice($logData['debug'], -5);
|
||||
foreach ($debugEntries as $entry) {
|
||||
$backupSummary .= "- $entry\n";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return $this->view->render($response, 'admin/reports/serverHealth.twig', [
|
||||
'serverHealth' => $serverHealth,
|
||||
'csrfTokenName' => $csrfTokenName,
|
||||
'csrfTokenValue' => $csrfTokenValue,
|
||||
'backupLog' => nl2br(htmlspecialchars($backupSummary)),
|
||||
]);
|
||||
}
|
||||
|
||||
public function clearCache(Request $request, Response $response): Response
|
||||
{
|
||||
if ($_SESSION["auth_roles"] != 0) {
|
||||
return $response->withHeader('Location', '/dashboard')->withStatus(302);
|
||||
}
|
||||
|
||||
$result = [
|
||||
'success' => true,
|
||||
'message' => 'Cache cleared successfully!',
|
||||
];
|
||||
$cacheDir = '/var/www/cp/cache';
|
||||
|
||||
try {
|
||||
// Check if the cache directory exists
|
||||
if (!is_dir($cacheDir)) {
|
||||
throw new RuntimeException('Cache directory does not exist.');
|
||||
}
|
||||
|
||||
// Iterate through the files and directories in the cache directory
|
||||
$files = new \RecursiveIteratorIterator(
|
||||
new \RecursiveDirectoryIterator($cacheDir, \RecursiveDirectoryIterator::SKIP_DOTS),
|
||||
\RecursiveIteratorIterator::CHILD_FIRST
|
||||
);
|
||||
|
||||
foreach ($files as $fileinfo) {
|
||||
// Check if the parent directory name is exactly two letters/numbers long
|
||||
if (preg_match('/^[a-zA-Z0-9]{2}$/', $fileinfo->getFilename()) ||
|
||||
preg_match('/^[a-zA-Z0-9]{2}$/', basename(dirname($fileinfo->getPathname())))) {
|
||||
$action = ($fileinfo->isDir() ? 'rmdir' : 'unlink');
|
||||
$action($fileinfo->getRealPath());
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the two-letter/number directories themselves
|
||||
$dirs = new \DirectoryIterator($cacheDir);
|
||||
foreach ($dirs as $dir) {
|
||||
if ($dir->isDir() && !$dir->isDot() && preg_match('/^[a-zA-Z0-9]{2}$/', $dir->getFilename())) {
|
||||
rmdir($dir->getRealPath());
|
||||
}
|
||||
}
|
||||
} catch (Exception $e) {
|
||||
$result = [
|
||||
'success' => false,
|
||||
'message' => 'Error clearing cache: ' . $e->getMessage(),
|
||||
];
|
||||
}
|
||||
|
||||
// Respond with the result as JSON
|
||||
$response->getBody()->write(json_encode($result));
|
||||
return $response->withHeader('Content-Type', 'application/json')->withStatus(200);
|
||||
}
|
||||
|
||||
}
|
|
@ -266,6 +266,9 @@ $csrfMiddleware = function ($request, $handler) use ($container) {
|
|||
if ($path && $path === '/create-crypto-payment') {
|
||||
return $handler->handle($request);
|
||||
}
|
||||
if ($path && $path === '/clear-cache') {
|
||||
return $handler->handle($request);
|
||||
}
|
||||
|
||||
// If not skipped, apply the CSRF Guard
|
||||
return $csrf->process($request, $handler);
|
||||
|
|
|
@ -45,7 +45,8 @@
|
|||
"giggsey/libphonenumber-for-php-lite": "^8.13",
|
||||
"egulias/email-validator": "^4.0",
|
||||
"utopia-php/messaging": "^0.12.0",
|
||||
"brick/postcode": "^0.3.3"
|
||||
"brick/postcode": "^0.3.3",
|
||||
"utopia-php/system": "^0.9.0"
|
||||
},
|
||||
"autoload": {
|
||||
"psr-4": {
|
||||
|
|
169
cp/resources/views/admin/reports/serverHealth.twig
Normal file
169
cp/resources/views/admin/reports/serverHealth.twig
Normal file
|
@ -0,0 +1,169 @@
|
|||
{% extends "layouts/app.twig" %}
|
||||
|
||||
{% block title %}{{ __('Server Health') }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<link href="/assets/css/sweetalert2.min.css" rel="stylesheet">
|
||||
<div class="page-wrapper">
|
||||
<!-- Page header -->
|
||||
<div class="page-header d-print-none">
|
||||
<div class="container-xl">
|
||||
<div class="row g-2 align-items-center">
|
||||
<div class="col">
|
||||
<!-- Page pre-title -->
|
||||
<div class="page-pretitle">
|
||||
{{ __('Overview') }}
|
||||
</div>
|
||||
<h2 class="page-title">
|
||||
{{ __('Server Health') }}
|
||||
</h2>
|
||||
</div>
|
||||
<!-- Page title actions -->
|
||||
<div class="col-auto ms-auto d-print-none">
|
||||
<div class="btn-list">
|
||||
<button onclick="clearSystemCache()" class="btn btn-primary d-none d-sm-inline-block">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M19 20h-10.5l-4.21 -4.3a1 1 0 0 1 0 -1.41l10 -10a1 1 0 0 1 1.41 0l5 5a1 1 0 0 1 0 1.41l-9.2 9.3" /><path d="M18 13.3l-6.3 -6.3" /></svg>
|
||||
{{ __('Clear Cache') }}
|
||||
</button>
|
||||
<button onclick="clearSystemCache()" class="btn btn-primary d-sm-none btn-icon" aria-label="{{ __('Clear Cache') }}">
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="icon"><path stroke="none" d="M0 0h24v24H0z" fill="none"/><path d="M19 20h-10.5l-4.21 -4.3a1 1 0 0 1 0 -1.41l10 -10a1 1 0 0 1 1.41 0l5 5a1 1 0 0 1 0 1.41l-9.2 9.3" /><path d="M18 13.3l-6.3 -6.3" /></svg>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Page body -->
|
||||
<div class="page-body">
|
||||
<div class="container-xl">
|
||||
<div class="col-12">
|
||||
<div class="row">
|
||||
<!-- CPU and Memory Card -->
|
||||
<div class="col-md-6">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">{{ __('CPU and Memory') }}</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<div>
|
||||
<h4 class="m-0">{{ __('CPU Cores') }}</h4>
|
||||
<div class="text-muted">{{ serverHealth.getCPUCores }}</div>
|
||||
</div>
|
||||
<div class="ms-auto">
|
||||
<span class="badge bg-primary text-primary-fg">{{ serverHealth.getCPUUsage }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="progress progress-xl">
|
||||
<div class="progress-bar bg-primary" style="width: {{ serverHealth.getCPUUsage }}%;" role="progressbar" aria-valuenow="{{ serverHealth.getCPUUsage }}" aria-valuemin="0" aria-valuemax="100">
|
||||
{{ serverHealth.getCPUUsage }}%
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<hr>
|
||||
|
||||
<div class="d-flex align-items-center">
|
||||
<div>
|
||||
<h4 class="m-0">{{ __('Memory') }}</h4>
|
||||
<div class="text-muted">{{ serverHealth.getMemoryFree }} {{ __('MB free of') }} {{ serverHealth.getMemoryTotal }} MB</div>
|
||||
</div>
|
||||
<div class="ms-auto">
|
||||
<span class="badge bg-green text-green-fg">{{ (serverHealth.getMemoryFree / serverHealth.getMemoryTotal * 100)|round(1) }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="progress progress-xl">
|
||||
<div class="progress-bar bg-success" style="width: {{ (serverHealth.getMemoryFree / serverHealth.getMemoryTotal * 100)|round(1) }}%;" role="progressbar" aria-valuenow="{{ (serverHealth.getMemoryFree / serverHealth.getMemoryTotal * 100)|round(1) }}" aria-valuemin="0" aria-valuemax="100">
|
||||
{{ (serverHealth.getMemoryFree / serverHealth.getMemoryTotal * 100)|round(1) }}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Disk and Network Card -->
|
||||
<div class="col-md-6 mt-md-0 mt-2">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">{{ __('Disk and Network') }}</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<div class="d-flex align-items-center mb-3">
|
||||
<div>
|
||||
<h4 class="m-0">{{ __('Disk Usage') }}</h4>
|
||||
<div class="text-muted">{{ serverHealth.getDiskFree }} {{ __('GB free of') }} {{ serverHealth.getDiskTotal }} GB</div>
|
||||
</div>
|
||||
<div class="ms-auto">
|
||||
<span class="badge bg-warning text-warning-fg">{{ (serverHealth.getDiskFree / serverHealth.getDiskTotal * 100)|round(1) }}%</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="progress progress-xl">
|
||||
<div class="progress-bar bg-warning" style="width: {{ (serverHealth.getDiskFree / serverHealth.getDiskTotal * 100)|round(1) }}%;" role="progressbar" aria-valuenow="{{ (serverHealth.getDiskFree / serverHealth.getDiskTotal * 100)|round(1) }}" aria-valuemin="0" aria-valuemax="100">
|
||||
{{ (serverHealth.getDiskFree / serverHealth.getDiskTotal * 100)|round(1) }}%
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row mt-2">
|
||||
<div class="col-md-12">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">{{ __('Backup Log') }}</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<pre>{{ backupLog }}</pre>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include 'partials/footer.twig' %}
|
||||
</div>
|
||||
<script>
|
||||
var csrfTokenName = "{{ csrfTokenName }}";
|
||||
var csrfTokenValue = "{{ csrfTokenValue }}";
|
||||
|
||||
function clearSystemCache() {
|
||||
fetch('/clear-cache', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'X-Requested-With': 'XMLHttpRequest',
|
||||
[csrfTokenName]: csrfTokenValue, // Include CSRF token in headers
|
||||
}
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
Swal.fire({
|
||||
icon: 'success',
|
||||
title: 'Success',
|
||||
text: data.message,
|
||||
confirmButtonText: 'OK'
|
||||
}).then(() => {
|
||||
window.location.reload(); // Reload the page after user acknowledges
|
||||
});
|
||||
} else {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Error',
|
||||
text: data.message,
|
||||
confirmButtonText: 'OK'
|
||||
});
|
||||
}
|
||||
})
|
||||
.catch(err => {
|
||||
Swal.fire({
|
||||
icon: 'error',
|
||||
title: 'Error',
|
||||
text: 'Error clearing cache: ' + err.message,
|
||||
confirmButtonText: 'OK'
|
||||
});
|
||||
});
|
||||
}
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -206,7 +206,7 @@
|
|||
</a>
|
||||
</div>
|
||||
</li>
|
||||
<li {{ is_current_url('epphistory') or is_current_url('poll') or is_current_url('log') or is_current_url('registry') or is_current_url('reports') or is_current_url('listTlds') or is_current_url('createTld') or 'tld' in currentUri or 'reserved' in currentUri or 'tokens' in currentUri or (roles != 0 and 'registrar' in currentUri) ? 'class="nav-item dropdown active"' : 'class="nav-item dropdown"' }}>
|
||||
<li {{ is_current_url('epphistory') or is_current_url('poll') or is_current_url('log') or is_current_url('registry') or is_current_url('reports') or is_current_url('serverHealth') or is_current_url('listTlds') or is_current_url('createTld') or 'tld' in currentUri or 'reserved' in currentUri or 'tokens' in currentUri or (roles != 0 and 'registrar' in currentUri) ? 'class="nav-item dropdown active"' : 'class="nav-item dropdown"' }}>
|
||||
<a class="nav-link dropdown-toggle" href="#" data-bs-toggle="dropdown" data-bs-auto-close="outside" role="button" aria-expanded="false">
|
||||
<span class="nav-link-icon d-md-none d-lg-inline-block"><svg xmlns="http://www.w3.org/2000/svg" class="icon" width="24" height="24" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round"><path stroke="none" d="M0 0h24v24H0z" fill="none"></path><path d="M10.325 4.317c.426 -1.756 2.924 -1.756 3.35 0a1.724 1.724 0 0 0 2.573 1.066c1.543 -.94 3.31 .826 2.37 2.37a1.724 1.724 0 0 0 1.065 2.572c1.756 .426 1.756 2.924 0 3.35a1.724 1.724 0 0 0 -1.066 2.573c.94 1.543 -.826 3.31 -2.37 2.37a1.724 1.724 0 0 0 -2.572 1.065c-.426 1.756 -2.924 1.756 -3.35 0a1.724 1.724 0 0 0 -2.573 -1.066c-1.543 .94 -3.31 -.826 -2.37 -2.37a1.724 1.724 0 0 0 -1.065 -2.572c-1.756 -.426 -1.756 -2.924 0 -3.35a1.724 1.724 0 0 0 1.066 -2.573c-.94 -1.543 .826 -3.31 2.37 -2.37c1 .608 2.296 .07 2.572 -1.065z"></path> <path d="M9 12a3 3 0 1 0 6 0a3 3 0 0 0 -6 0"></path></svg>
|
||||
</span>
|
||||
|
@ -227,6 +227,9 @@
|
|||
<a class="dropdown-item" href="{{route('reports')}}">
|
||||
{{ __('Reports') }}
|
||||
</a>
|
||||
<a class="dropdown-item" href="{{route('serverHealth')}}">
|
||||
{{ __('Server Health') }}
|
||||
</a>
|
||||
<div class="dropdown-divider"></div>{% endif %}
|
||||
<a class="dropdown-item" href="{{route('poll')}}">
|
||||
{{ __('Message Queue') }}
|
||||
|
@ -307,6 +310,8 @@
|
|||
{% include 'partials/js-tlds.twig' %}
|
||||
{% elseif route_is('profile') %}
|
||||
{% include 'partials/js-profile.twig' %}
|
||||
{% elseif route_is('server') %}
|
||||
{% include 'partials/js-server.twig' %}
|
||||
{% else %}
|
||||
{% include 'partials/js.twig' %}
|
||||
{% endif %}
|
||||
|
|
2
cp/resources/views/partials/js-server.twig
Normal file
2
cp/resources/views/partials/js-server.twig
Normal file
|
@ -0,0 +1,2 @@
|
|||
<script src="/assets/js/sweetalert2.min.js" defer></script>
|
||||
<script src="/assets/js/tabler.min.js" defer></script>
|
|
@ -108,6 +108,8 @@ $app->group('', function ($route) {
|
|||
|
||||
$route->get('/reports', ReportsController::class .':view')->setName('reports');
|
||||
$route->get('/export', ReportsController::class .':exportDomains')->setName('exportDomains');
|
||||
$route->get('/server', ReportsController::class .':serverHealth')->setName('serverHealth');
|
||||
$route->post('/clear-cache', ReportsController::class .':clearCache')->setName('clearCache');
|
||||
|
||||
$route->get('/invoices', FinancialsController::class .':invoices')->setName('invoices');
|
||||
$route->get('/invoice/{invoice}', FinancialsController::class . ':viewInvoice')->setName('viewInvoice');
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue