mirror of
https://github.com/getnamingo/registry.git
synced 2025-07-05 18:43:19 +02:00
Added basic domain history page; will be expanded
This commit is contained in:
parent
6ca9e5256a
commit
5e49fbc2bb
6 changed files with 220 additions and 1 deletions
|
@ -1056,7 +1056,61 @@ class DomainsController extends Controller
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function historyDomain(Request $request, Response $response, $args)
|
||||||
|
{
|
||||||
|
$db = $this->container->get('db');
|
||||||
|
$db_audit = $this->container->get('db_audit');
|
||||||
|
// Get the current URI
|
||||||
|
$uri = $request->getUri()->getPath();
|
||||||
|
|
||||||
|
if ($args) {
|
||||||
|
$args = strtolower(trim($args));
|
||||||
|
|
||||||
|
if (!preg_match('/^([a-z0-9]([-a-z0-9]*[a-z0-9])?\.)*[a-z0-9]([-a-z0-9]*[a-z0-9])?$/', $args)) {
|
||||||
|
$this->container->get('flash')->addMessage('error', 'Invalid domain name format');
|
||||||
|
return $response->withHeader('Location', '/domains')->withStatus(302);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$exists = $db_audit->selectValue('SELECT 1 FROM domain LIMIT 1');
|
||||||
|
} catch (\PDOException $e) {
|
||||||
|
throw new \RuntimeException('Audit table is empty or not configured');
|
||||||
|
}
|
||||||
|
|
||||||
|
$domain = $db->selectRow('SELECT id,name FROM domain WHERE name = ?',
|
||||||
|
[ $args ]);
|
||||||
|
|
||||||
|
if ($domain) {
|
||||||
|
$history = $db_audit->select(
|
||||||
|
'SELECT * FROM domain WHERE name = ? ORDER BY audit_timestamp DESC, audit_rownum ASC',
|
||||||
|
[$args]
|
||||||
|
);
|
||||||
|
|
||||||
|
if (strpos($domain['name'], 'xn--') === 0) {
|
||||||
|
$domain['name_o'] = $domain['name'];
|
||||||
|
$domain['name'] = idn_to_utf8($domain['name'], IDNA_NONTRANSITIONAL_TO_ASCII, INTL_IDNA_VARIANT_UTS46);
|
||||||
|
} else {
|
||||||
|
$domain['name_o'] = $domain['name'];
|
||||||
|
}
|
||||||
|
|
||||||
|
return view($response,'admin/domains/historyDomain.twig', [
|
||||||
|
'domain' => $domain,
|
||||||
|
'history' => $history,
|
||||||
|
'currentUri' => $uri
|
||||||
|
]);
|
||||||
|
} else {
|
||||||
|
// Domain does not exist, redirect to the domains view
|
||||||
|
return $response->withHeader('Location', '/domains')->withStatus(302);
|
||||||
|
}
|
||||||
|
|
||||||
|
} else {
|
||||||
|
// Redirect to the domains view
|
||||||
|
return $response->withHeader('Location', '/domains')->withStatus(302);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
public function updateDomain(Request $request, Response $response, $args)
|
public function updateDomain(Request $request, Response $response, $args)
|
||||||
{
|
{
|
||||||
$db = $this->container->get('db');
|
$db = $this->container->get('db');
|
||||||
|
|
|
@ -66,6 +66,14 @@ $container->set('pdo', function () use ($pdo) {
|
||||||
return $pdo;
|
return $pdo;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
$container->set('db_audit', function () use ($db_audit) {
|
||||||
|
return $db_audit;
|
||||||
|
});
|
||||||
|
|
||||||
|
$container->set('pdo_audit', function () use ($pdo_audit) {
|
||||||
|
return $pdo_audit;
|
||||||
|
});
|
||||||
|
|
||||||
$container->set('auth', function() {
|
$container->set('auth', function() {
|
||||||
//$responseFactory = new \Nyholm\Psr7\Factory\Psr17Factory();
|
//$responseFactory = new \Nyholm\Psr7\Factory\Psr17Factory();
|
||||||
//$response = $responseFactory->createResponse();
|
//$response = $responseFactory->createResponse();
|
||||||
|
|
|
@ -32,4 +32,28 @@ try {
|
||||||
|
|
||||||
} catch (PDOException $e) {
|
} catch (PDOException $e) {
|
||||||
$log->alert("Database connection failed: " . $e->getMessage(), ['driver' => $defaultDriver]);
|
$log->alert("Database connection failed: " . $e->getMessage(), ['driver' => $defaultDriver]);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Audit DB (optional)
|
||||||
|
try {
|
||||||
|
$auditDriver = match ($defaultDriver) {
|
||||||
|
'mysql' => "{$config['mysql']['driver']}:dbname=registryAudit;host={$config['mysql']['host']};charset={$config['mysql']['charset']}",
|
||||||
|
'sqlite' => "{$config['sqlite']['driver']}:{$config['sqlite']['audit_path']}", // assumes audit_path is set for SQLite
|
||||||
|
'pgsql' => "{$config['pgsql']['driver']}:dbname=registryAudit;host={$config['pgsql']['host']}",
|
||||||
|
default => throw new \RuntimeException('Unsupported database driver for audit'),
|
||||||
|
};
|
||||||
|
|
||||||
|
if (str_starts_with($auditDriver, "sqlite")) {
|
||||||
|
$pdo_audit = new \PDO($auditDriver);
|
||||||
|
} else {
|
||||||
|
$pdo_audit = new \PDO(
|
||||||
|
$auditDriver,
|
||||||
|
$config[$defaultDriver]['username'],
|
||||||
|
$config[$defaultDriver]['password']
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
$db_audit = PdoDatabase::fromPdo($pdo_audit);
|
||||||
|
} catch (PDOException $e) {
|
||||||
|
$log->alert("Audit database connection failed: " . $e->getMessage(), ['driver' => 'audit']);
|
||||||
}
|
}
|
125
cp/resources/views/admin/domains/historyDomain.twig
Normal file
125
cp/resources/views/admin/domains/historyDomain.twig
Normal file
|
@ -0,0 +1,125 @@
|
||||||
|
{% extends "layouts/app.twig" %}
|
||||||
|
|
||||||
|
{% block title %}{{ __('Domain History') }}{% 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">
|
||||||
|
<div class="mb-1">
|
||||||
|
<ol class="breadcrumb">
|
||||||
|
<li class="breadcrumb-item">
|
||||||
|
<a href="{{route('home')}}"><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" /><polyline points="5 12 3 12 12 3 21 12 19 12" /><path d="M5 12v8a2 2 0 0 0 2 2h10a2 2 0 0 0 2 -2v-8" /><rect x="10" y="12" width="4" height="4" /></svg></a>
|
||||||
|
</li>
|
||||||
|
<li class="breadcrumb-item">
|
||||||
|
<a href="{{route('listDomains')}}">{{ __('Domains') }}</a>
|
||||||
|
</li>
|
||||||
|
<li class="breadcrumb-item">
|
||||||
|
<a href="/domain/view/{{ domain.name_o }}">{{ __('Domain') }} {{ domain.name }}</a>
|
||||||
|
</li>
|
||||||
|
<li class="breadcrumb-item active">
|
||||||
|
{{ __('Domain History') }}
|
||||||
|
</li>
|
||||||
|
</ol>
|
||||||
|
</div>
|
||||||
|
<h2 class="page-title">
|
||||||
|
{{ __('Domain History') }}
|
||||||
|
</h2>
|
||||||
|
</div>
|
||||||
|
<!-- Page title actions -->
|
||||||
|
<div class="col-auto ms-auto d-print-none">
|
||||||
|
<div class="btn-list">
|
||||||
|
<a href="/domain/view/{{ domain.name_o }}" class="btn 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="M9 11l-4 4l4 4m-4 -4h11a4 4 0 0 0 0 -8h-1" /></svg>
|
||||||
|
{{ __('Back to View') }}
|
||||||
|
</a>
|
||||||
|
<a href="/domain/view/{{ domain.name_o }}" class="btn d-sm-none btn-icon" aria-label="{{ __('Back to View') }}">
|
||||||
|
<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="M9 11l-4 4l4 4m-4 -4h11a4 4 0 0 0 0 -8h-1" /></svg>
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<!-- Page body -->
|
||||||
|
<div class="page-body">
|
||||||
|
<div class="container-xl">
|
||||||
|
<div class="col-12">
|
||||||
|
<div class="card mb-3">
|
||||||
|
<div class="card-header">
|
||||||
|
<h3 class="card-title">
|
||||||
|
{{ __('Domain') }} {{ domain.name }}
|
||||||
|
</h3>
|
||||||
|
</div>
|
||||||
|
<div class="card-body">
|
||||||
|
<div class="table-responsive">
|
||||||
|
<table class="table table-vcenter card-table table-striped">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>{{ __('Timestamp') }}</th>
|
||||||
|
<th>{{ __('Action') }}</th>
|
||||||
|
<th>{{ __('User') }}</th>
|
||||||
|
<th>{{ __('Session') }}</th>
|
||||||
|
<th>{{ __('Changed Field') }}</th>
|
||||||
|
<th>{{ __('Old Value') }}</th>
|
||||||
|
<th>{{ __('New Value') }}</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody>
|
||||||
|
{% if history|length == 0 %}
|
||||||
|
<tr>
|
||||||
|
<td colspan="7" class="text-center text-muted">{{ __('No audit history available for this domain.') }}</td>
|
||||||
|
</tr>
|
||||||
|
{% else %}
|
||||||
|
{% set max = history|length %}
|
||||||
|
{% for i in 0..max-1 %}
|
||||||
|
{% set entry = history[i] %}
|
||||||
|
{% if entry.audit_statement == 'UPDATE' and entry.audit_type == 'OLD' %}
|
||||||
|
{% set old = entry %}
|
||||||
|
{% set new = history[i + 1] is defined and history[i + 1].audit_type == 'NEW' ? history[i + 1] : {} %}
|
||||||
|
{% for key in old|keys %}
|
||||||
|
{% if old[key] != new[key] and key not in ['audit_timestamp','audit_statement','audit_type','audit_uuid','audit_rownum','audit_user','audit_ses_id','audit_usr_id'] %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ old.audit_timestamp }}</td>
|
||||||
|
<td>{{ old.audit_statement }}</td>
|
||||||
|
<td>{{ old.audit_usr_id|default('–') }}</td>
|
||||||
|
<td>{{ old.audit_ses_id|default('–') }}</td>
|
||||||
|
<td><strong>{{ key }}</strong></td>
|
||||||
|
<td class="text-muted">{{ old[key]|default('–') }}</td>
|
||||||
|
<td class="text-success">{{ new[key]|default('–') }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% elseif entry.audit_statement == 'INSERT' %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ entry.audit_timestamp }}</td>
|
||||||
|
<td>{{ entry.audit_statement }}</td>
|
||||||
|
<td>{{ entry.audit_usr_id|default('–') }}</td>
|
||||||
|
<td>{{ entry.audit_ses_id|default('–') }}</td>
|
||||||
|
<td colspan="3" class="text-muted">{{ __('New domain inserted.') }}</td>
|
||||||
|
</tr>
|
||||||
|
{% elseif entry.audit_statement == 'DELETE' %}
|
||||||
|
<tr>
|
||||||
|
<td>{{ entry.audit_timestamp }}</td>
|
||||||
|
<td>{{ entry.audit_statement }}</td>
|
||||||
|
<td>{{ entry.audit_usr_id|default('–') }}</td>
|
||||||
|
<td>{{ entry.audit_ses_id|default('–') }}</td>
|
||||||
|
<td colspan="3" class="text-muted">{{ __('Domain was deleted.') }}</td>
|
||||||
|
</tr>
|
||||||
|
{% endif %}
|
||||||
|
{% endfor %}
|
||||||
|
{% endif %}
|
||||||
|
</tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{% include 'partials/footer.twig' %}
|
||||||
|
</div>
|
||||||
|
{% endblock %}
|
|
@ -29,6 +29,13 @@
|
||||||
<!-- Page title actions -->
|
<!-- Page title actions -->
|
||||||
<div class="col-auto ms-auto d-print-none">
|
<div class="col-auto ms-auto d-print-none">
|
||||||
<div class="btn-list">
|
<div class="btn-list">
|
||||||
|
<a href="/domain/history/{{ domain.name_o }}" class="btn btn-outline-purple 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="M12 8l0 4l2 2" /><path d="M3.05 11a9 9 0 1 1 .5 4m-.5 5v-5h5" /></svg>
|
||||||
|
{{ __('Domain History') }}
|
||||||
|
</a>
|
||||||
|
<a href="/domain/history/{{ domain.name_o }}" class="btn btn-outline-purple d-sm-none btn-icon" aria-label="{{ __('Domain History') }}">
|
||||||
|
<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="M12 8l0 4l2 2" /><path d="M3.05 11a9 9 0 1 1 .5 4m-.5 5v-5h5" /></svg>
|
||||||
|
</a>
|
||||||
<a href="/domain/renew/{{ domain.name_o }}" class="btn btn-outline-success d-none d-sm-inline-block">
|
<a href="/domain/renew/{{ domain.name_o }}" class="btn btn-outline-success 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="M20 11a8.1 8.1 0 0 0 -15.5 -2m-.5 -4v4h4" /><path d="M4 13a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4" /></svg>
|
<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="M20 11a8.1 8.1 0 0 0 -15.5 -2m-.5 -4v4h4" /><path d="M4 13a8.1 8.1 0 0 0 15.5 2m.5 4v-4h-4" /></svg>
|
||||||
{{ __('Renew Domain') }}
|
{{ __('Renew Domain') }}
|
||||||
|
|
|
@ -48,6 +48,7 @@ $app->group('', function ($route) {
|
||||||
$route->map(['GET', 'POST'], '/domain/check', DomainsController::class . ':checkDomain')->setName('checkDomain');
|
$route->map(['GET', 'POST'], '/domain/check', DomainsController::class . ':checkDomain')->setName('checkDomain');
|
||||||
$route->map(['GET', 'POST'], '/domain/create', DomainsController::class . ':createDomain')->setName('createDomain');
|
$route->map(['GET', 'POST'], '/domain/create', DomainsController::class . ':createDomain')->setName('createDomain');
|
||||||
$route->get('/domain/view/{domain}', DomainsController::class . ':viewDomain')->setName('viewDomain');
|
$route->get('/domain/view/{domain}', DomainsController::class . ':viewDomain')->setName('viewDomain');
|
||||||
|
$route->get('/domain/history/{domain}', DomainsController::class . ':historyDomain')->setName('historyDomain');
|
||||||
$route->get('/domain/update/{domain}', DomainsController::class . ':updateDomain')->setName('updateDomain');
|
$route->get('/domain/update/{domain}', DomainsController::class . ':updateDomain')->setName('updateDomain');
|
||||||
$route->post('/domain/update', DomainsController::class . ':updateDomainProcess')->setName('updateDomainProcess');
|
$route->post('/domain/update', DomainsController::class . ':updateDomainProcess')->setName('updateDomainProcess');
|
||||||
$route->post('/domain/deletesecdns', DomainsController::class . ':domainDeleteSecdns')->setName('domainDeleteSecdns');
|
$route->post('/domain/deletesecdns', DomainsController::class . ':domainDeleteSecdns')->setName('domainDeleteSecdns');
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue