Accounts: Add account detail page
This commit is contained in:
parent
985f46c652
commit
4e8d879008
|
|
@ -116,18 +116,32 @@ a:hover, a:focus {
|
|||
color: gray;
|
||||
}
|
||||
|
||||
.green {
|
||||
color: green;
|
||||
}
|
||||
|
||||
.red {
|
||||
color: red;
|
||||
}
|
||||
|
||||
button {
|
||||
padding: 0.2em 1em;
|
||||
}
|
||||
|
||||
.inactive {
|
||||
color: gray;
|
||||
}
|
||||
|
||||
/* --- Tables --- */
|
||||
|
||||
table, tr, td, th {
|
||||
border: 1px solid #999999;
|
||||
border-collapse: collapse;
|
||||
padding: 0.25em 0.5em;
|
||||
}
|
||||
|
||||
.inactive {
|
||||
color: gray;
|
||||
.vertical_table_headers th {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
/* --- Filter options --- */
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ use MailAccountAdmin\Frontend\Login\LoginController;
|
|||
use MailAccountAdmin\Frontend\Dashboard\DashboardController;
|
||||
use MailAccountAdmin\Repositories\AccountRepository;
|
||||
use MailAccountAdmin\Repositories\AdminUserRepository;
|
||||
use MailAccountAdmin\Repositories\AliasRepository;
|
||||
use MailAccountAdmin\Repositories\DomainRepository;
|
||||
use PDO;
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
|
@ -70,6 +71,11 @@ class Dependencies
|
|||
$c->get(self::DATABASE),
|
||||
);
|
||||
});
|
||||
$container->set(AliasRepository::class, function (ContainerInterface $c) {
|
||||
return new AliasRepository(
|
||||
$c->get(self::DATABASE),
|
||||
);
|
||||
});
|
||||
|
||||
// Helper classes
|
||||
$container->set(UserHelper::class, function (ContainerInterface $c) {
|
||||
|
|
@ -104,6 +110,7 @@ class Dependencies
|
|||
$c->get(self::TWIG),
|
||||
$c->get(UserHelper::class),
|
||||
$c->get(AccountRepository::class),
|
||||
$c->get(AliasRepository::class),
|
||||
);
|
||||
});
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,8 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace MailAccountAdmin\Exceptions;
|
||||
|
||||
class AccountNotFoundException extends AppException
|
||||
{
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ namespace MailAccountAdmin\Frontend\Accounts;
|
|||
use MailAccountAdmin\Common\UserHelper;
|
||||
use MailAccountAdmin\Frontend\BaseController;
|
||||
use MailAccountAdmin\Repositories\AccountRepository;
|
||||
use MailAccountAdmin\Repositories\AliasRepository;
|
||||
use Psr\Http\Message\ResponseInterface as Response;
|
||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||
use Slim\Views\Twig;
|
||||
|
|
@ -14,11 +15,14 @@ class AccountController extends BaseController
|
|||
{
|
||||
/** @var AccountRepository */
|
||||
private $accountRepository;
|
||||
/** @var AliasRepository */
|
||||
private $aliasRepository;
|
||||
|
||||
public function __construct(Twig $view, UserHelper $userHelper, AccountRepository $accountRepository)
|
||||
public function __construct(Twig $view, UserHelper $userHelper, AccountRepository $accountRepository, AliasRepository $aliasRepository)
|
||||
{
|
||||
parent::__construct($view, $userHelper);
|
||||
$this->accountRepository = $accountRepository;
|
||||
$this->aliasRepository = $aliasRepository;
|
||||
}
|
||||
|
||||
public function showAccounts(Request $request, Response $response): Response
|
||||
|
|
@ -34,4 +38,36 @@ class AccountController extends BaseController
|
|||
|
||||
return $this->view->render($response, 'accounts.html.twig', $renderData);
|
||||
}
|
||||
|
||||
public function showAccountDetails(Request $request, Response $response, array $args): Response
|
||||
{
|
||||
// Parse URL arguments
|
||||
$accountId = (int)$args['id'];
|
||||
|
||||
// Get account data from database
|
||||
$accountData = $this->accountRepository->fetchAccountById($accountId);
|
||||
$accountUsername = $accountData['username'];
|
||||
$accountData['domain'] = explode('@', $accountUsername, 2)[1];
|
||||
|
||||
// Don't display the password hash, but at least the type of hash (used hash algorithm)
|
||||
if ($accountData['password'] === '') {
|
||||
$passwordHashType = 'empty';
|
||||
} else {
|
||||
$passwordHashInfo = password_get_info($accountData['password']);
|
||||
$passwordHashType = $passwordHashInfo['algoName'] ?? 'unknown';
|
||||
}
|
||||
|
||||
// Get list of aliases for this account
|
||||
$aliases = $this->aliasRepository->fetchAliasesForUserId($accountId);
|
||||
|
||||
$renderData = [
|
||||
'id' => $accountId,
|
||||
'accountUsername' => $accountUsername,
|
||||
'accountData' => $accountData,
|
||||
'passwordHashType' => $passwordHashType,
|
||||
'aliases' => $aliases,
|
||||
];
|
||||
|
||||
return $this->view->render($response, 'account_details.html.twig', $renderData);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ declare(strict_types=1);
|
|||
|
||||
namespace MailAccountAdmin\Repositories;
|
||||
|
||||
use MailAccountAdmin\Exceptions\AccountNotFoundException;
|
||||
use PDO;
|
||||
|
||||
class AccountRepository extends BaseRepository
|
||||
|
|
@ -32,4 +33,16 @@ class AccountRepository extends BaseRepository
|
|||
$statement->execute($queryParams);
|
||||
return $statement->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
|
||||
public function fetchAccountById(int $accountId): array
|
||||
{
|
||||
$statement = $this->pdo->prepare('SELECT * FROM mail_users WHERE user_id = :user_id LIMIT 1');
|
||||
$statement->execute(['user_id' => $accountId]);
|
||||
|
||||
if ($statement->rowCount() < 1) {
|
||||
throw new AccountNotFoundException("Account with user ID '$accountId' was not found.");
|
||||
}
|
||||
|
||||
return $statement->fetch(PDO::FETCH_ASSOC);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,16 @@
|
|||
<?php
|
||||
declare(strict_types=1);
|
||||
|
||||
namespace MailAccountAdmin\Repositories;
|
||||
|
||||
use PDO;
|
||||
|
||||
class AliasRepository extends BaseRepository
|
||||
{
|
||||
public function fetchAliasesForUserId(int $userId): array
|
||||
{
|
||||
$statement = $this->pdo->prepare('SELECT * FROM mail_aliases WHERE user_id = :user_id ORDER BY mail_address');
|
||||
$statement->execute(['user_id' => $userId]);
|
||||
return $statement->fetchAll(PDO::FETCH_ASSOC);
|
||||
}
|
||||
}
|
||||
|
|
@ -23,10 +23,11 @@ class Routes
|
|||
|
||||
// Domains
|
||||
$app->get('/domains', DomainController::class . ':showDomains');
|
||||
$app->get('/domains/{foo}', DomainController::class . ':showDomains');
|
||||
|
||||
// Accounts
|
||||
$app->get('/accounts', AccountController::class . ':showAccounts');
|
||||
$app->get('/accounts/{foo}', AccountController::class . ':showAccounts');
|
||||
$app->get('/accounts/{id:[1-9][0-9]*}', AccountController::class . ':showAccountDetails');
|
||||
$app->get('/accounts/{id:[1-9][0-9]*}/edit', AccountController::class . ':showAccountDetails');
|
||||
$app->get('/accounts/{id:[1-9][0-9]*}/delete', AccountController::class . ':showAccountDetails');
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,82 @@
|
|||
{% extends "base.html.twig" %}
|
||||
|
||||
{% block title %}Accounts{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<h2>Account: {{ accountUsername }}</h2>
|
||||
|
||||
<p>
|
||||
<b>Actions:</b>
|
||||
<a href="/accounts/{{ id }}/edit">Edit</a> |
|
||||
<a href="/accounts/{{ id }}/delete">Delete</a> |
|
||||
<a href="mailto:{{ accountUsername }}">Send mail</a>
|
||||
</p>
|
||||
|
||||
<table class="vertical_table_headers">
|
||||
<tr>
|
||||
<th style="min-width: 10em">User ID</th>
|
||||
<td style="min-width: 20em">{{ accountData['user_id'] }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Username</th>
|
||||
<td>{{ accountData['username'] }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Domain</th>
|
||||
<td><a href="/accounts?domain={{ accountData['domain'] | escape('url') }}">{{ accountData['domain'] }}</a></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Password</th>
|
||||
<td>
|
||||
{% if passwordHashType == 'empty' %}
|
||||
<span class="red">[No password set]</span>
|
||||
{% elseif passwordHashType == 'unknown' %}
|
||||
<span class="red">[Not hashed / unknown hash algorithm]</span>
|
||||
{% else %}
|
||||
<span class="gray">[{{ passwordHashType }} hash]</span>
|
||||
{% endif %}
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Status</th>
|
||||
<td>{{ accountData['is_active'] == '1' ? '<span class="green">Active</span>' : '<span class="red">Inactive</span>' }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Home directory</th>
|
||||
<td><span class="gray">/srv/vmail/</span>{{ accountData['home_dir'] }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Admin memo</th>
|
||||
<td>{{ accountData['memo'] | nl2br }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Created at</th>
|
||||
<td>{{ accountData['created_at'] }}</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Last modified at</th>
|
||||
<td>{{ accountData['modified_at'] }}</td>
|
||||
</tr>
|
||||
</table>
|
||||
|
||||
<h3>Aliases</h3>
|
||||
|
||||
{% if aliases %}
|
||||
<table>
|
||||
<tr>
|
||||
<th>Address</th>
|
||||
<th>Created at</th>
|
||||
<th>Actions</th>
|
||||
</tr>
|
||||
{% for alias in aliases %}
|
||||
<tr>
|
||||
<td>{{ alias['mail_address'] }}</td>
|
||||
<td>{{ alias['created_at'] }}</td>
|
||||
<td><a href="#">Delete</a></td> {# TODO #}
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% else %}
|
||||
<p>No aliases.</p>
|
||||
{% endif %}
|
||||
{% endblock %}
|
||||
Loading…
Reference in New Issue