Compare commits
No commits in common. "985f46c65266fb7f6cdabb8f016675e3a0d29332" and "b890432e5b9a5b95e820373c695dc8b1a13ca358" have entirely different histories.
985f46c652
...
b890432e5b
|
|
@ -54,13 +54,12 @@ nav a {
|
||||||
|
|
||||||
nav a:link, nav a:visited {
|
nav a:link, nav a:visited {
|
||||||
background-color: #eeeeee;
|
background-color: #eeeeee;
|
||||||
color: #ff00e6;
|
color: blue;
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
}
|
}
|
||||||
|
|
||||||
nav a:hover, nav a:focus {
|
nav a:hover, nav a:focus {
|
||||||
background-color: #ffffff;
|
background-color: #ffffff;
|
||||||
color: #0066ff;
|
|
||||||
text-decoration: underline;
|
text-decoration: underline;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -95,16 +94,6 @@ h2 {
|
||||||
margin: 0 0 0.5em 0;
|
margin: 0 0 0.5em 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
a:link, a:visited {
|
|
||||||
color: #ff00e6;
|
|
||||||
text-decoration: none;
|
|
||||||
}
|
|
||||||
|
|
||||||
a:hover, a:focus {
|
|
||||||
color: #0066ff;
|
|
||||||
text-decoration: underline;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error {
|
.error {
|
||||||
background: #ff4444;
|
background: #ff4444;
|
||||||
width: 30em;
|
width: 30em;
|
||||||
|
|
@ -112,40 +101,6 @@ a:hover, a:focus {
|
||||||
padding: 1em;
|
padding: 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
.gray {
|
|
||||||
color: gray;
|
|
||||||
}
|
|
||||||
|
|
||||||
button {
|
button {
|
||||||
padding: 0.2em 1em;
|
padding: 0.2em 1em;
|
||||||
}
|
}
|
||||||
|
|
||||||
table, tr, td, th {
|
|
||||||
border: 1px solid #999999;
|
|
||||||
border-collapse: collapse;
|
|
||||||
padding: 0.25em 0.5em;
|
|
||||||
}
|
|
||||||
|
|
||||||
.inactive {
|
|
||||||
color: gray;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Filter options --- */
|
|
||||||
.filter_options {
|
|
||||||
border: 1px solid #999999;
|
|
||||||
padding: 1em;
|
|
||||||
margin: 1em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
.filter_options h4 {
|
|
||||||
margin: 0 0 0.5em 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* --- Detail columns --- */
|
|
||||||
input#show_details_checkbox {
|
|
||||||
margin-bottom: 1em;
|
|
||||||
}
|
|
||||||
|
|
||||||
input#show_details_checkbox:not(:checked) ~ table .detail_column {
|
|
||||||
display: none;
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -9,9 +9,7 @@ use MailAccountAdmin\Frontend\Accounts\AccountController;
|
||||||
use MailAccountAdmin\Frontend\Domains\DomainController;
|
use MailAccountAdmin\Frontend\Domains\DomainController;
|
||||||
use MailAccountAdmin\Frontend\Login\LoginController;
|
use MailAccountAdmin\Frontend\Login\LoginController;
|
||||||
use MailAccountAdmin\Frontend\Dashboard\DashboardController;
|
use MailAccountAdmin\Frontend\Dashboard\DashboardController;
|
||||||
use MailAccountAdmin\Repositories\AccountRepository;
|
|
||||||
use MailAccountAdmin\Repositories\AdminUserRepository;
|
use MailAccountAdmin\Repositories\AdminUserRepository;
|
||||||
use MailAccountAdmin\Repositories\DomainRepository;
|
|
||||||
use PDO;
|
use PDO;
|
||||||
use Psr\Container\ContainerInterface;
|
use Psr\Container\ContainerInterface;
|
||||||
use Slim\Views\Twig;
|
use Slim\Views\Twig;
|
||||||
|
|
@ -60,16 +58,6 @@ class Dependencies
|
||||||
$c->get(self::DATABASE),
|
$c->get(self::DATABASE),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
$container->set(DomainRepository::class, function (ContainerInterface $c) {
|
|
||||||
return new DomainRepository(
|
|
||||||
$c->get(self::DATABASE),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
$container->set(AccountRepository::class, function (ContainerInterface $c) {
|
|
||||||
return new AccountRepository(
|
|
||||||
$c->get(self::DATABASE),
|
|
||||||
);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Helper classes
|
// Helper classes
|
||||||
$container->set(UserHelper::class, function (ContainerInterface $c) {
|
$container->set(UserHelper::class, function (ContainerInterface $c) {
|
||||||
|
|
@ -96,14 +84,12 @@ class Dependencies
|
||||||
return new DomainController(
|
return new DomainController(
|
||||||
$c->get(self::TWIG),
|
$c->get(self::TWIG),
|
||||||
$c->get(UserHelper::class),
|
$c->get(UserHelper::class),
|
||||||
$c->get(DomainRepository::class),
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
$container->set(AccountController::class, function (ContainerInterface $c) {
|
$container->set(AccountController::class, function (ContainerInterface $c) {
|
||||||
return new AccountController(
|
return new AccountController(
|
||||||
$c->get(self::TWIG),
|
$c->get(self::TWIG),
|
||||||
$c->get(UserHelper::class),
|
$c->get(UserHelper::class),
|
||||||
$c->get(AccountRepository::class),
|
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -3,33 +3,15 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace MailAccountAdmin\Frontend\Accounts;
|
namespace MailAccountAdmin\Frontend\Accounts;
|
||||||
|
|
||||||
use MailAccountAdmin\Common\UserHelper;
|
|
||||||
use MailAccountAdmin\Frontend\BaseController;
|
use MailAccountAdmin\Frontend\BaseController;
|
||||||
use MailAccountAdmin\Repositories\AccountRepository;
|
|
||||||
use Psr\Http\Message\ResponseInterface as Response;
|
use Psr\Http\Message\ResponseInterface as Response;
|
||||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||||
use Slim\Views\Twig;
|
|
||||||
|
|
||||||
class AccountController extends BaseController
|
class AccountController extends BaseController
|
||||||
{
|
{
|
||||||
/** @var AccountRepository */
|
|
||||||
private $accountRepository;
|
|
||||||
|
|
||||||
public function __construct(Twig $view, UserHelper $userHelper, AccountRepository $accountRepository)
|
|
||||||
{
|
|
||||||
parent::__construct($view, $userHelper);
|
|
||||||
$this->accountRepository = $accountRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function showAccounts(Request $request, Response $response): Response
|
public function showAccounts(Request $request, Response $response): Response
|
||||||
{
|
{
|
||||||
// Parse query parameters for filters
|
|
||||||
$queryParams = $request->getQueryParams();
|
|
||||||
$filterByDomain = $queryParams['domain'] ?? null;
|
|
||||||
|
|
||||||
$renderData = [
|
$renderData = [
|
||||||
'filterDomain' => $filterByDomain,
|
|
||||||
'accountList' => $this->accountRepository->fetchAccountList($filterByDomain),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
return $this->view->render($response, 'accounts.html.twig', $renderData);
|
return $this->view->render($response, 'accounts.html.twig', $renderData);
|
||||||
|
|
|
||||||
|
|
@ -3,28 +3,15 @@ declare(strict_types=1);
|
||||||
|
|
||||||
namespace MailAccountAdmin\Frontend\Domains;
|
namespace MailAccountAdmin\Frontend\Domains;
|
||||||
|
|
||||||
use MailAccountAdmin\Common\UserHelper;
|
|
||||||
use MailAccountAdmin\Frontend\BaseController;
|
use MailAccountAdmin\Frontend\BaseController;
|
||||||
use MailAccountAdmin\Repositories\DomainRepository;
|
|
||||||
use Psr\Http\Message\ResponseInterface as Response;
|
use Psr\Http\Message\ResponseInterface as Response;
|
||||||
use Psr\Http\Message\ServerRequestInterface as Request;
|
use Psr\Http\Message\ServerRequestInterface as Request;
|
||||||
use Slim\Views\Twig;
|
|
||||||
|
|
||||||
class DomainController extends BaseController
|
class DomainController extends BaseController
|
||||||
{
|
{
|
||||||
/** @var DomainRepository */
|
|
||||||
private $domainRepository;
|
|
||||||
|
|
||||||
public function __construct(Twig $view, UserHelper $userHelper, DomainRepository $domainRepository)
|
|
||||||
{
|
|
||||||
parent::__construct($view, $userHelper);
|
|
||||||
$this->domainRepository = $domainRepository;
|
|
||||||
}
|
|
||||||
|
|
||||||
public function showDomains(Request $request, Response $response): Response
|
public function showDomains(Request $request, Response $response): Response
|
||||||
{
|
{
|
||||||
$renderData = [
|
$renderData = [
|
||||||
'domainList' => $this->domainRepository->fetchDomainList(),
|
|
||||||
];
|
];
|
||||||
|
|
||||||
return $this->view->render($response, 'domains.html.twig', $renderData);
|
return $this->view->render($response, 'domains.html.twig', $renderData);
|
||||||
|
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
<?php
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace MailAccountAdmin\Repositories;
|
|
||||||
|
|
||||||
use PDO;
|
|
||||||
|
|
||||||
class AccountRepository extends BaseRepository
|
|
||||||
{
|
|
||||||
public function fetchAccountList(string $filterByDomain = null): array
|
|
||||||
{
|
|
||||||
$queryWhere = '';
|
|
||||||
$queryParams = [];
|
|
||||||
if (!empty($filterByDomain)) {
|
|
||||||
$queryWhere = 'WHERE REGEXP_REPLACE(username, "^.*@", "") LIKE :domain';
|
|
||||||
$queryParams['domain'] = str_replace('*', '%', $filterByDomain);
|
|
||||||
}
|
|
||||||
|
|
||||||
$query = '
|
|
||||||
SELECT
|
|
||||||
mail_users.*,
|
|
||||||
REGEXP_REPLACE(username, "^.*@", "") AS domain,
|
|
||||||
COUNT(alias_id) AS alias_count
|
|
||||||
FROM mail_users
|
|
||||||
LEFT JOIN mail_aliases ON mail_users.user_id = mail_aliases.user_id
|
|
||||||
' . $queryWhere . '
|
|
||||||
GROUP BY username
|
|
||||||
ORDER BY domain, username
|
|
||||||
';
|
|
||||||
|
|
||||||
$statement = $this->pdo->prepare($query);
|
|
||||||
$statement->execute($queryParams);
|
|
||||||
return $statement->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -7,8 +7,16 @@ use MailAccountAdmin\Exceptions\AdminUserNotFoundException;
|
||||||
use MailAccountAdmin\Models\AdminUser;
|
use MailAccountAdmin\Models\AdminUser;
|
||||||
use PDO;
|
use PDO;
|
||||||
|
|
||||||
class AdminUserRepository extends BaseRepository
|
class AdminUserRepository
|
||||||
{
|
{
|
||||||
|
/** @var PDO */
|
||||||
|
private $pdo;
|
||||||
|
|
||||||
|
public function __construct(PDO $pdo)
|
||||||
|
{
|
||||||
|
$this->pdo = $pdo;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @throws AdminUserNotFoundException
|
* @throws AdminUserNotFoundException
|
||||||
*/
|
*/
|
||||||
|
|
|
||||||
|
|
@ -1,17 +0,0 @@
|
||||||
<?php
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace MailAccountAdmin\Repositories;
|
|
||||||
|
|
||||||
use PDO;
|
|
||||||
|
|
||||||
class BaseRepository
|
|
||||||
{
|
|
||||||
/** @var PDO */
|
|
||||||
protected $pdo;
|
|
||||||
|
|
||||||
public function __construct(PDO $pdo)
|
|
||||||
{
|
|
||||||
$this->pdo = $pdo;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
<?php
|
|
||||||
declare(strict_types=1);
|
|
||||||
|
|
||||||
namespace MailAccountAdmin\Repositories;
|
|
||||||
|
|
||||||
use PDO;
|
|
||||||
|
|
||||||
class DomainRepository extends BaseRepository
|
|
||||||
{
|
|
||||||
public function fetchDomainList(): array
|
|
||||||
{
|
|
||||||
// Fetch domains with account/alias count from database
|
|
||||||
$statement = $this->pdo->query('
|
|
||||||
SELECT
|
|
||||||
REGEXP_REPLACE(address, "^.*@", "") AS domain,
|
|
||||||
COUNT(is_account) AS account_count,
|
|
||||||
COUNT(is_alias) AS alias_count
|
|
||||||
FROM (
|
|
||||||
SELECT username AS address, 1 AS is_account, NULL AS is_alias FROM mail_users
|
|
||||||
UNION
|
|
||||||
SELECT mail_address AS address, NULL AS is_account, 1 AS is_alias FROM mail_aliases
|
|
||||||
) AS addresses
|
|
||||||
GROUP BY domain
|
|
||||||
');
|
|
||||||
|
|
||||||
$domainRows = $statement->fetchAll(PDO::FETCH_ASSOC);
|
|
||||||
$domainsCounted = [];
|
|
||||||
foreach ($domainRows as $row) {
|
|
||||||
$domainsCounted[$row['domain']] = [
|
|
||||||
'accounts' => $row['account_count'],
|
|
||||||
'aliases' => $row['alias_count'],
|
|
||||||
];
|
|
||||||
}
|
|
||||||
|
|
||||||
ksort($domainsCounted);
|
|
||||||
return $domainsCounted;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -5,48 +5,6 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>Accounts</h2>
|
<h2>Accounts</h2>
|
||||||
|
|
||||||
<p>List of all mail accounts.</p>
|
<p>List of accounts ... <b>TODO</b></p>
|
||||||
|
<p><a href="/accounts/42">Test</a></p>
|
||||||
<div class="filter_options">
|
|
||||||
<form action="/accounts" method="GET">
|
|
||||||
<h4>Filter:</h4>
|
|
||||||
<label for="filter_domain">Domain: </label>
|
|
||||||
<input name="domain" id="filter_domain" value="{{ filterDomain | default('') }}"/>
|
|
||||||
<button type="submit">Apply</button>
|
|
||||||
<button type="reset" onclick="location.href='/accounts'">Clear</button>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<input type="checkbox" id="show_details_checkbox"><label for="show_details_checkbox"> Show detail columns</label>
|
|
||||||
|
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<th style="min-width: 15em">Username</th>
|
|
||||||
<th style="min-width: 10em">Domain</th>
|
|
||||||
<th>Aliases</th>
|
|
||||||
<th>Active</th>
|
|
||||||
<th class="detail_column">Home directory</th>
|
|
||||||
<th class="detail_column" style="min-width: 10em">Memo</th>
|
|
||||||
<th>Created</th>
|
|
||||||
<th class="detail_column">Last modified</th>
|
|
||||||
</tr>
|
|
||||||
{% if accountList %}
|
|
||||||
{% for account in accountList -%}
|
|
||||||
<tr{% if account['is_active'] != 1 %} class="inactive"{% endif %}>
|
|
||||||
<td><a href="/accounts/{{ account['user_id'] }}">{{ account['username'] }}</a></td>
|
|
||||||
<td><a href="/accounts?domain={{ account['domain'] }}">{{ account['domain'] }}</a></td>
|
|
||||||
<td>{{ account['alias_count'] }}</td>
|
|
||||||
<td>{{ account['is_active'] == 1 ? 'Yes' : 'No' }}</td>
|
|
||||||
<td class="detail_column"><span class="gray">vmail/</span>{{ account['home_dir'] }}</td>
|
|
||||||
<td class="detail_column">{{ account['memo'] }}</td>
|
|
||||||
<td>{{ account['created_at'] }}</td>
|
|
||||||
<td class="detail_column">{{ account['modified_at'] }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
{% else %}
|
|
||||||
<tr>
|
|
||||||
<td colspan="100" style="text-align: center">No accounts found.</td>
|
|
||||||
</tr>
|
|
||||||
{% endif %}
|
|
||||||
</table>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
|
|
@ -5,26 +5,6 @@
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<h2>Domains</h2>
|
<h2>Domains</h2>
|
||||||
|
|
||||||
<p>This is a list of all domains auto-generated from the existing mail accounts and aliases. As such it is read-only.</p>
|
<p>List of domains ... <b>TODO</b></p>
|
||||||
|
<p><a href="/domains/42">Test</a></p>
|
||||||
<table>
|
|
||||||
<tr>
|
|
||||||
<th style="min-width: 12em">Domain</th>
|
|
||||||
<th>Accounts</th>
|
|
||||||
<th>Aliases</th>
|
|
||||||
</tr>
|
|
||||||
{% if domainList %}
|
|
||||||
{% for domain, domainCounts in domainList -%}
|
|
||||||
<tr>
|
|
||||||
<td><a href="/accounts?domain={{ domain | escape('url') }}">{{ domain }}</a></td>
|
|
||||||
<td>{{ domainCounts['accounts'] }}</td>
|
|
||||||
<td>{{ domainCounts['aliases'] }}</td>
|
|
||||||
</tr>
|
|
||||||
{% endfor %}
|
|
||||||
{% else %}
|
|
||||||
<tr>
|
|
||||||
<td colspan="100" style="text-align: center">No domains found.</td>
|
|
||||||
</tr>
|
|
||||||
{% endif %}
|
|
||||||
</table>
|
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue