mirror of
https://github.com/getnamingo/registry.git
synced 2025-07-24 03:20:33 +02:00
Added ability to send registrar notifications, fixed #166
Also fixed msg_producer (again)
This commit is contained in:
parent
6968bfafa2
commit
5711546f78
5 changed files with 244 additions and 17 deletions
|
@ -37,13 +37,13 @@ class RedisPool {
|
|||
*/
|
||||
public function initialize(int $size = 10): void {
|
||||
for ($i = 0; $i < $size; $i++) {
|
||||
// Create a coroutine for each connection.
|
||||
Swoole\Coroutine\run(function () {
|
||||
go(function () {
|
||||
$redis = new Redis();
|
||||
if (!$redis->connect($this->host, $this->port)) {
|
||||
throw new Exception("Failed to connect to Redis at {$this->host}:{$this->port}");
|
||||
}
|
||||
$this->pool->push($redis);
|
||||
echo "Added Redis connection to pool\n"; // Debugging log
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -52,7 +52,7 @@ class RedisPool {
|
|||
* Get a Redis connection from the pool.
|
||||
* Optionally, you can add a timeout to avoid indefinite blocking.
|
||||
*/
|
||||
public function get(float $timeout = 1.0): Redis {
|
||||
public function get(float $timeout = 2.0): Redis {
|
||||
$conn = $this->pool->pop($timeout);
|
||||
if (!$conn) {
|
||||
throw new Exception("No available Redis connection in pool");
|
||||
|
@ -63,13 +63,13 @@ class RedisPool {
|
|||
/**
|
||||
* Return a Redis connection back to the pool.
|
||||
*/
|
||||
public function put(Redis $redis): void {
|
||||
$this->pool->push($redis);
|
||||
public function put(?Redis $redis): void {
|
||||
if ($redis && $redis->isConnected()) {
|
||||
$this->pool->push($redis);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Global RedisPool instance
|
||||
$redisPool = new RedisPool('127.0.0.1', 6379, 10);
|
||||
}
|
||||
|
||||
// Create the Swoole HTTP server
|
||||
$server = new Server("127.0.0.1", 8250);
|
||||
|
@ -80,24 +80,28 @@ $server->set([
|
|||
'log_file' => '/var/log/namingo/msg_producer.log',
|
||||
'log_level' => SWOOLE_LOG_INFO,
|
||||
'worker_num' => swoole_cpu_num() * 2,
|
||||
'pid_file' => '/var/run/msg_producer.pid'
|
||||
'pid_file' => '/var/run/msg_producer.pid',
|
||||
'enable_coroutine' => true
|
||||
]);
|
||||
|
||||
/**
|
||||
* Instead of initializing the Redis pool in the "start" event (which runs in the master process),
|
||||
* we initialize it in the "workerStart" event so that it runs in a coroutine-enabled worker process.
|
||||
*/
|
||||
$server->on("workerStart", function () use ($redisPool, $logger) {
|
||||
$server->on("workerStart", function ($server, $workerId) use (&$logger) {
|
||||
try {
|
||||
$redisPool->initialize(10);
|
||||
$logger->info("Redis pool initialized in worker process");
|
||||
$server->redisPool = new RedisPool('127.0.0.1', 6379, 10); // Store in server object
|
||||
$server->redisPool->initialize(10);
|
||||
$logger->info("Redis pool initialized in worker process {$workerId}");
|
||||
} catch (Exception $e) {
|
||||
$logger->error("Failed to initialize Redis pool: " . $e->getMessage());
|
||||
$logger->error("Worker {$workerId}: Failed to initialize Redis pool - " . $e->getMessage());
|
||||
}
|
||||
});
|
||||
|
||||
// Handle incoming requests
|
||||
$server->on("request", function (Request $request, Response $response) use ($redisPool, $logger) {
|
||||
$server->on("request", function (Request $request, Response $response) use ($server, $logger) {
|
||||
$redisPool = $server->redisPool ?? null;
|
||||
|
||||
if (!$redisPool) {
|
||||
$logger->error("Redis pool not initialized");
|
||||
$response->status(500);
|
||||
|
|
|
@ -1467,4 +1467,129 @@ class RegistrarsController extends Controller
|
|||
Auth::leaveImpersonation();
|
||||
}
|
||||
|
||||
public function notifyRegistrars(Request $request, Response $response)
|
||||
{
|
||||
if ($_SESSION["auth_roles"] != 0) {
|
||||
return $response->withHeader('Location', '/dashboard')->withStatus(302);
|
||||
}
|
||||
|
||||
if ($request->getMethod() === 'POST') {
|
||||
// Retrieve POST data
|
||||
$data = $request->getParsedBody();
|
||||
$db = $this->container->get('db');
|
||||
|
||||
// Ensure registrars array exists and is not empty
|
||||
if (!isset($data['registrars']) || empty($data['registrars'])) {
|
||||
$this->container->get('flash')->addMessage('error', 'No registrars selected');
|
||||
return $response->withHeader('Location', '/registrars/notify')->withStatus(302);
|
||||
}
|
||||
|
||||
$registrars = $data['registrars']; // Array of registrar IDs
|
||||
$subject = isset($data['subject']) && is_string($data['subject']) ? trim($data['subject']) : 'No subject';
|
||||
$message = isset($data['message']) && is_string($data['message']) ? trim($data['message']) : 'No message';
|
||||
|
||||
// Enforce length limits
|
||||
$subjectMaxLength = 255;
|
||||
$messageMaxLength = 5000;
|
||||
|
||||
if (strlen($subject) > $subjectMaxLength) {
|
||||
$subject = substr($subject, 0, $subjectMaxLength);
|
||||
}
|
||||
|
||||
if (strlen($message) > $messageMaxLength) {
|
||||
$message = substr($message, 0, $messageMaxLength);
|
||||
}
|
||||
|
||||
// Escape HTML to prevent XSS if displaying in HTML later
|
||||
$subject = htmlspecialchars($subject, ENT_QUOTES, 'UTF-8');
|
||||
$message = htmlspecialchars($message, ENT_QUOTES, 'UTF-8');
|
||||
|
||||
$url = 'http://127.0.0.1:8250';
|
||||
|
||||
// Retrieve registrar names from database
|
||||
$registrarNames = [];
|
||||
$registrarEmails = [];
|
||||
$placeholders = implode(',', array_fill(0, count($registrars), '?'));
|
||||
$rows = $db->select(
|
||||
"SELECT id, name, email FROM registrar WHERE id IN ($placeholders)",
|
||||
$registrars
|
||||
);
|
||||
|
||||
foreach ($rows as $row) {
|
||||
$registrarNames[$row['id']] = $row['name'];
|
||||
$registrarEmails[$row['id']] = $row['email'];
|
||||
}
|
||||
|
||||
$notifiedRegistrars = [];
|
||||
|
||||
foreach ($registrars as $registrarId) {
|
||||
$data = [
|
||||
'type' => 'sendmail',
|
||||
'toEmail' => $registrarEmails[$registrarId] ?? null,
|
||||
'subject' => $subject,
|
||||
'body' => $message
|
||||
];
|
||||
|
||||
$jsonData = json_encode($data);
|
||||
|
||||
$options = [
|
||||
CURLOPT_RETURNTRANSFER => true,
|
||||
CURLOPT_CUSTOMREQUEST => 'POST',
|
||||
CURLOPT_POSTFIELDS => $jsonData,
|
||||
CURLOPT_HTTPHEADER => [
|
||||
'Content-Type: application/json',
|
||||
'Content-Length: ' . strlen($jsonData)
|
||||
],
|
||||
];
|
||||
|
||||
$curl = curl_init($url);
|
||||
curl_setopt_array($curl, $options);
|
||||
$curlResponse = curl_exec($curl);
|
||||
|
||||
if ($curlResponse === false) {
|
||||
$this->container->get('flash')->addMessage('error', 'cURL Error: ' . curl_error($curl));
|
||||
curl_close($curl);
|
||||
return $response->withHeader('Location', '/registrars/notify')->withStatus(302);
|
||||
} else {
|
||||
$notifiedRegistrars[] = $registrarNames[$registrarId] ?? "Registrar ID: $registrarId";
|
||||
}
|
||||
|
||||
curl_close($curl);
|
||||
}
|
||||
|
||||
// Create success message with registrar names
|
||||
$successMessage = "Notification sent to: " . implode(', ', $notifiedRegistrars);
|
||||
$this->container->get('flash')->addMessage('success', $successMessage);
|
||||
|
||||
return $response->withHeader('Location', '/registrars/notify')->withStatus(302);
|
||||
} else {
|
||||
// Prepare the view
|
||||
$db = $this->container->get('db');
|
||||
$uri = $request->getUri()->getPath();
|
||||
|
||||
// Get all registrars
|
||||
$registrars = $db->select("SELECT id, clid, name, email, abuse_email FROM registrar");
|
||||
|
||||
// Fetch last login for each registrar
|
||||
foreach ($registrars as &$registrar) {
|
||||
// Get the latest user_id associated with the registrar
|
||||
$user_id = $db->selectValue("SELECT user_id FROM registrar_users WHERE registrar_id = ? ORDER BY user_id DESC LIMIT 1", [$registrar['id']]);
|
||||
|
||||
// Fetch last login time if user_id exists
|
||||
if ($user_id) {
|
||||
$last_login = $db->selectValue("SELECT last_login FROM users WHERE id = ?", [$user_id]);
|
||||
$registrar['last_login'] = ($last_login && is_numeric($last_login)) ? date('Y-m-d H:i:s', $last_login) : null;
|
||||
} else {
|
||||
$registrar['last_login'] = null;
|
||||
}
|
||||
}
|
||||
|
||||
// Default view for GET requests or if POST data is not set
|
||||
return view($response,'admin/registrars/notifyRegistrars.twig', [
|
||||
'registrars' => $registrars,
|
||||
'currentUri' => $uri,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
93
cp/resources/views/admin/registrars/notifyRegistrars.twig
Normal file
93
cp/resources/views/admin/registrars/notifyRegistrars.twig
Normal file
|
@ -0,0 +1,93 @@
|
|||
{% extends "layouts/app.twig" %}
|
||||
|
||||
{% block title %}{{ __('Notify Registrars') }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<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">
|
||||
{{ __('Notify Registrars') }}
|
||||
</h2>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Page body -->
|
||||
<div class="page-body">
|
||||
<div class="container-xl">
|
||||
<div class="col-12">
|
||||
{% include 'partials/flash.twig' %}
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h3 class="card-title">{{ __('Send Registrar Notification') }}</h3>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<form method="post" action="/registrars/notify">
|
||||
{{ csrf.field | raw }}
|
||||
<!-- Select Registrars -->
|
||||
<div class="mb-3">
|
||||
<label class="form-label"><strong>{{ __('Select Registrars') }}</strong></label>
|
||||
<div class="card p-2">
|
||||
<div class="d-flex justify-content-between mb-2">
|
||||
<button type="button" class="btn btn-outline-primary btn-sm" id="selectAll">{{ __('Select All') }}</button>
|
||||
<button type="button" class="btn btn-outline-secondary btn-sm" id="deselectAll">{{ __('Deselect All') }}</button>
|
||||
</div>
|
||||
<div class="row">
|
||||
{% for registrar in registrars %}
|
||||
<div class="col-md-6">
|
||||
<div class="form-check">
|
||||
<input class="form-check-input registrar-checkbox" type="checkbox" name="registrars[]" value="{{ registrar.id }}" id="registrar{{ registrar.id }}">
|
||||
<label class="form-check-label" for="registrar{{ registrar.id }}">
|
||||
<strong>{{ registrar.name }}</strong> ({{ registrar.clid }})
|
||||
<br><small class="text-muted">{{ registrar.email }}</small>
|
||||
<br><small class="text-muted">{{ __('Last Login:') }} {{ registrar.last_login is not null ? registrar.last_login : 'Not available' }}</small>
|
||||
</label>
|
||||
</div>
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Subject Field -->
|
||||
<div class="mb-3">
|
||||
<label class="form-label">{{ __('Subject') }}</label>
|
||||
<input type="text" name="subject" class="form-control" placeholder="{{ __('Enter subject') }}" required>
|
||||
</div>
|
||||
|
||||
<!-- Message Field -->
|
||||
<div class="mb-3">
|
||||
<label class="form-label">{{ __('Message') }}</label>
|
||||
<textarea name="message" class="form-control" rows="4" placeholder="{{ __('Type your message...') }}" required></textarea>
|
||||
</div>
|
||||
|
||||
<!-- Submit Button -->
|
||||
<div class="text-end">
|
||||
<button type="submit" class="btn btn-primary">{{ __('Send Notification') }}</button>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
{% include 'partials/footer.twig' %}
|
||||
</div>
|
||||
<script>
|
||||
document.getElementById("selectAll").addEventListener("click", function() {
|
||||
document.querySelectorAll(".registrar-checkbox").forEach(el => el.checked = true);
|
||||
});
|
||||
|
||||
document.getElementById("deselectAll").addEventListener("click", function() {
|
||||
document.querySelectorAll(".registrar-checkbox").forEach(el => el.checked = false);
|
||||
});
|
||||
</script>
|
||||
{% endblock %}
|
|
@ -6,7 +6,7 @@
|
|||
<meta http-equiv="X-UA-Compatible" content="ie=edge"/>
|
||||
<title>{% block title %}{% endblock %} | Namingo</title>
|
||||
<!-- CSS files -->
|
||||
{% if route_is('domains') or route_is('applications') or route_is('contacts') or route_is('hosts') or route_is('epphistory') or route_is('registrars') or route_is('transactions') or route_is('overview') or route_is('reports') or route_is('transfers') or route_is('users') or is_current_url('ticketview') or route_is('poll') or route_is('log') or route_is('invoices') or route_is('registry/tlds') or route_is('profile') %}{% include 'partials/css-tables.twig' %}{% else %}{% include 'partials/css.twig' %}{% endif %}
|
||||
{% if route_is('domains') or route_is('applications') or route_is('contacts') or route_is('hosts') or route_is('epphistory') or is_current_url('registrars') or route_is('transactions') or route_is('overview') or route_is('reports') or route_is('transfers') or route_is('users') or is_current_url('ticketview') or route_is('poll') or route_is('log') or route_is('invoices') or route_is('registry/tlds') or route_is('profile') %}{% include 'partials/css-tables.twig' %}{% else %}{% include 'partials/css.twig' %}{% endif %}
|
||||
</head>
|
||||
<body{% if screen_mode == 'dark' %} data-bs-theme="dark"{% endif %}>
|
||||
<div class="page">
|
||||
|
@ -147,7 +147,7 @@
|
|||
</a>
|
||||
</div>
|
||||
</li>
|
||||
{% if roles == 0 %}<li {{ is_current_url('registrars') or is_current_url('listUsers') or is_current_url('transferRegistrar') or is_current_url('createUser') or is_current_url('registrarcreate') or 'user/update/' in currentUri or 'registrar/' in currentUri ? 'class="nav-item dropdown active"' : 'class="nav-item dropdown"' }}>
|
||||
{% if roles == 0 %}<li {{ is_current_url('registrars') or is_current_url('listUsers') or is_current_url('transferRegistrar') or is_current_url('createUser') or is_current_url('registrarcreate') or is_current_url('notifyRegistrars') or 'user/update/' in currentUri or '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="M12 13a3 3 0 1 0 0 -6a3 3 0 0 0 0 6z"></path><path d="M12 3c7.2 0 9 1.8 9 9s-1.8 9 -9 9s-9 -1.8 -9 -9s1.8 -9 9 -9z"></path><path d="M6 20.05v-.05a4 4 0 0 1 4 -4h4a4 4 0 0 1 4 4v.05"></path></svg>
|
||||
</span>
|
||||
|
@ -163,6 +163,10 @@
|
|||
{{ __('Create Registrar') }}
|
||||
</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item" href="{{route('notifyRegistrars')}}">
|
||||
{{ __('Notify Registrars') }}
|
||||
</a>
|
||||
<div class="dropdown-divider"></div>
|
||||
<a class="dropdown-item" href="{{route('listUsers')}}">
|
||||
{{ __('List Users') }}
|
||||
</a>
|
||||
|
@ -275,7 +279,7 @@
|
|||
{% include 'partials/js-hosts.twig' %}
|
||||
{% elseif route_is('epphistory') %}
|
||||
{% include 'partials/js-logs.twig' %}
|
||||
{% elseif route_is('registrars') %}
|
||||
{% elseif is_current_url('registrars') %}
|
||||
{% include 'partials/js-registrars.twig' %}
|
||||
{% elseif route_is('transactions') %}
|
||||
{% include 'partials/js-transactions.twig' %}
|
||||
|
|
|
@ -100,6 +100,7 @@ $app->group('', function ($route) {
|
|||
$route->map(['GET', 'POST'], '/registrar/process', RegistrarsController::class . ':transferRegistrarProcess')->setName('transferRegistrarProcess');
|
||||
$route->get('/registrar/impersonate/{registrar}', RegistrarsController::class . ':impersonateRegistrar')->setName('impersonateRegistrar');
|
||||
$route->get('/leave_impersonation', RegistrarsController::class . ':leave_impersonation')->setName('leave_impersonation');
|
||||
$route->map(['GET', 'POST'], '/registrars/notify', RegistrarsController::class .':notifyRegistrars')->setName('notifyRegistrars');
|
||||
|
||||
$route->get('/users', UsersController::class .':listUsers')->setName('listUsers');
|
||||
$route->map(['GET', 'POST'], '/user/create', UsersController::class . ':createUser')->setName('createUser');
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue