Add Hallinta module with iRedMail email management
New "Hallinta" main tab (superadmin only) with "Sähköposti" sub-tab for managing email via iRedAdmin-Pro REST API. Features: - IRedMailClient PHP class with cookie-based session auth + auto-retry - Domain CRUD (list, create, delete) - Mailbox CRUD (list, create, delete, password change) - Alias CRUD (list, create, delete) - Configuration modal (API URL, admin credentials, connection test) - Search/filter for mailboxes - 13 new API endpoints, all requireSuperAdmin() Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
343
api.php
343
api.php
@@ -902,6 +902,145 @@ class ImapClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== IREDMAIL CLIENT ====================
|
||||||
|
|
||||||
|
class IRedMailClient {
|
||||||
|
private string $baseUrl;
|
||||||
|
private string $adminEmail;
|
||||||
|
private string $adminPassword;
|
||||||
|
private ?string $cookie = null;
|
||||||
|
|
||||||
|
public function __construct(string $baseUrl, string $adminEmail, string $adminPassword) {
|
||||||
|
$this->baseUrl = rtrim($baseUrl, '/');
|
||||||
|
if (!preg_match('#^https?://#i', $this->baseUrl)) {
|
||||||
|
$this->baseUrl = 'https://' . $this->baseUrl;
|
||||||
|
}
|
||||||
|
$this->adminEmail = $adminEmail;
|
||||||
|
$this->adminPassword = $adminPassword;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function login(): void {
|
||||||
|
$ch = curl_init($this->baseUrl . '/api/login');
|
||||||
|
curl_setopt_array($ch, [
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_POST => true,
|
||||||
|
CURLOPT_POSTFIELDS => http_build_query([
|
||||||
|
'username' => $this->adminEmail,
|
||||||
|
'password' => $this->adminPassword,
|
||||||
|
]),
|
||||||
|
CURLOPT_TIMEOUT => 10,
|
||||||
|
CURLOPT_CONNECTTIMEOUT => 5,
|
||||||
|
CURLOPT_HEADER => true,
|
||||||
|
CURLOPT_SSL_VERIFYPEER => false,
|
||||||
|
]);
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$headerSize = curl_getinfo($ch, CURLINFO_HEADER_SIZE);
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
$headers = substr($response, 0, $headerSize);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
if ($httpCode >= 400) {
|
||||||
|
throw new \RuntimeException('iRedMail kirjautuminen epäonnistui (HTTP ' . $httpCode . ')');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Parse Set-Cookie header
|
||||||
|
if (preg_match('/Set-Cookie:\s*([^;\r\n]+)/i', $headers, $m)) {
|
||||||
|
$this->cookie = $m[1];
|
||||||
|
} else {
|
||||||
|
throw new \RuntimeException('iRedMail: session-cookie puuttuu vastauksesta');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public function request(string $method, string $endpoint, ?array $data = null, bool $retried = false): array {
|
||||||
|
if (!$this->cookie) $this->login();
|
||||||
|
|
||||||
|
$url = $this->baseUrl . '/api/' . ltrim($endpoint, '/');
|
||||||
|
$ch = curl_init($url);
|
||||||
|
$opts = [
|
||||||
|
CURLOPT_RETURNTRANSFER => true,
|
||||||
|
CURLOPT_TIMEOUT => 15,
|
||||||
|
CURLOPT_CONNECTTIMEOUT => 5,
|
||||||
|
CURLOPT_HTTPHEADER => ['Cookie: ' . $this->cookie],
|
||||||
|
CURLOPT_SSL_VERIFYPEER => false,
|
||||||
|
];
|
||||||
|
|
||||||
|
if ($method === 'POST') {
|
||||||
|
$opts[CURLOPT_POST] = true;
|
||||||
|
if ($data) $opts[CURLOPT_POSTFIELDS] = http_build_query($data);
|
||||||
|
} elseif ($method === 'PUT') {
|
||||||
|
$opts[CURLOPT_CUSTOMREQUEST] = 'PUT';
|
||||||
|
if ($data) $opts[CURLOPT_POSTFIELDS] = http_build_query($data);
|
||||||
|
} elseif ($method === 'DELETE') {
|
||||||
|
$opts[CURLOPT_CUSTOMREQUEST] = 'DELETE';
|
||||||
|
}
|
||||||
|
|
||||||
|
curl_setopt_array($ch, $opts);
|
||||||
|
$response = curl_exec($ch);
|
||||||
|
$httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);
|
||||||
|
curl_close($ch);
|
||||||
|
|
||||||
|
// Retry login on 401
|
||||||
|
if ($httpCode === 401 && !$retried) {
|
||||||
|
$this->cookie = null;
|
||||||
|
return $this->request($method, $endpoint, $data, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
$result = json_decode($response, true) ?: [];
|
||||||
|
if ($httpCode >= 400 || (isset($result['_success']) && !$result['_success'])) {
|
||||||
|
throw new \RuntimeException('iRedMail API: ' . ($result['_msg'] ?? "HTTP $httpCode"));
|
||||||
|
}
|
||||||
|
return $result;
|
||||||
|
}
|
||||||
|
|
||||||
|
public function getDomains(): array { return $this->request('GET', 'domains'); }
|
||||||
|
public function createDomain(string $domain, array $opts = []): array {
|
||||||
|
return $this->request('POST', 'domain/' . urlencode($domain), $opts);
|
||||||
|
}
|
||||||
|
public function deleteDomain(string $domain): array {
|
||||||
|
return $this->request('DELETE', 'domain/' . urlencode($domain));
|
||||||
|
}
|
||||||
|
public function getUsers(string $domain): array {
|
||||||
|
return $this->request('GET', 'users/' . urlencode($domain));
|
||||||
|
}
|
||||||
|
public function getUser(string $email): array {
|
||||||
|
return $this->request('GET', 'user/' . urlencode($email));
|
||||||
|
}
|
||||||
|
public function createUser(string $email, string $password, array $opts = []): array {
|
||||||
|
$opts['password'] = $password;
|
||||||
|
return $this->request('POST', 'user/' . urlencode($email), $opts);
|
||||||
|
}
|
||||||
|
public function updateUser(string $email, array $opts): array {
|
||||||
|
return $this->request('PUT', 'user/' . urlencode($email), $opts);
|
||||||
|
}
|
||||||
|
public function deleteUser(string $email): array {
|
||||||
|
return $this->request('DELETE', 'user/' . urlencode($email));
|
||||||
|
}
|
||||||
|
public function getAliases(string $domain): array {
|
||||||
|
return $this->request('GET', 'aliases/' . urlencode($domain));
|
||||||
|
}
|
||||||
|
public function createAlias(string $alias, array $opts = []): array {
|
||||||
|
return $this->request('POST', 'alias/' . urlencode($alias), $opts);
|
||||||
|
}
|
||||||
|
public function deleteAlias(string $alias): array {
|
||||||
|
return $this->request('DELETE', 'alias/' . urlencode($alias));
|
||||||
|
}
|
||||||
|
public function testConnection(): array {
|
||||||
|
$domains = $this->getDomains();
|
||||||
|
return ['ok' => true, 'domains' => count($domains['_data'] ?? [])];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getIRedMailClient(): IRedMailClient {
|
||||||
|
$config = dbLoadConfig();
|
||||||
|
$url = $config['iredmail_api_url'] ?? '';
|
||||||
|
$email = $config['iredmail_admin_email'] ?? '';
|
||||||
|
$pw = $config['iredmail_admin_password'] ?? '';
|
||||||
|
if (!$url || !$email || !$pw) {
|
||||||
|
throw new \RuntimeException('iRedMail-asetukset puuttuvat. Aseta ensin URL, admin-sähköposti ja salasana.');
|
||||||
|
}
|
||||||
|
return new IRedMailClient($url, $email, $pw);
|
||||||
|
}
|
||||||
|
|
||||||
// ==================== TICKETS HELPER ====================
|
// ==================== TICKETS HELPER ====================
|
||||||
|
|
||||||
function sendTelegramAlert(string $companyId, array $ticket): void {
|
function sendTelegramAlert(string $companyId, array $ticket): void {
|
||||||
@@ -5872,6 +6011,210 @@ switch ($action) {
|
|||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
// ==================== IREDMAIL HALLINTA ====================
|
||||||
|
|
||||||
|
case 'iredmail_config':
|
||||||
|
requireSuperAdmin();
|
||||||
|
$config = dbLoadConfig();
|
||||||
|
echo json_encode([
|
||||||
|
'url' => $config['iredmail_api_url'] ?? '',
|
||||||
|
'admin_email' => $config['iredmail_admin_email'] ?? '',
|
||||||
|
'has_password' => !empty($config['iredmail_admin_password']),
|
||||||
|
]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'iredmail_config_save':
|
||||||
|
requireSuperAdmin();
|
||||||
|
if ($method !== 'POST') break;
|
||||||
|
$input = json_decode(file_get_contents('php://input'), true);
|
||||||
|
$config = dbLoadConfig();
|
||||||
|
if (isset($input['url'])) $config['iredmail_api_url'] = trim($input['url']);
|
||||||
|
if (isset($input['admin_email'])) $config['iredmail_admin_email'] = trim($input['admin_email']);
|
||||||
|
if (!empty($input['password'])) $config['iredmail_admin_password'] = $input['password'];
|
||||||
|
dbSaveConfig($config);
|
||||||
|
echo json_encode(['ok' => true]);
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'iredmail_test':
|
||||||
|
requireSuperAdmin();
|
||||||
|
if ($method !== 'POST') break;
|
||||||
|
try {
|
||||||
|
$client = getIRedMailClient();
|
||||||
|
$result = $client->testConnection();
|
||||||
|
echo json_encode($result);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
http_response_code(400);
|
||||||
|
echo json_encode(['error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'iredmail_domains':
|
||||||
|
requireSuperAdmin();
|
||||||
|
try {
|
||||||
|
$client = getIRedMailClient();
|
||||||
|
$result = $client->getDomains();
|
||||||
|
echo json_encode($result['_data'] ?? []);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'iredmail_domain_create':
|
||||||
|
requireSuperAdmin();
|
||||||
|
if ($method !== 'POST') break;
|
||||||
|
$input = json_decode(file_get_contents('php://input'), true);
|
||||||
|
$domain = trim($input['domain'] ?? '');
|
||||||
|
if (!$domain) { http_response_code(400); echo json_encode(['error' => 'Domain puuttuu']); break; }
|
||||||
|
try {
|
||||||
|
$client = getIRedMailClient();
|
||||||
|
$opts = [];
|
||||||
|
if (!empty($input['cn'])) $opts['cn'] = $input['cn'];
|
||||||
|
if (isset($input['quota'])) $opts['quota'] = intval($input['quota']);
|
||||||
|
$result = $client->createDomain($domain, $opts);
|
||||||
|
echo json_encode(['ok' => true]);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'iredmail_domain_delete':
|
||||||
|
requireSuperAdmin();
|
||||||
|
if ($method !== 'POST') break;
|
||||||
|
$input = json_decode(file_get_contents('php://input'), true);
|
||||||
|
$domain = trim($input['domain'] ?? '');
|
||||||
|
if (!$domain) { http_response_code(400); echo json_encode(['error' => 'Domain puuttuu']); break; }
|
||||||
|
try {
|
||||||
|
$client = getIRedMailClient();
|
||||||
|
$client->deleteDomain($domain);
|
||||||
|
echo json_encode(['ok' => true]);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'iredmail_users':
|
||||||
|
requireSuperAdmin();
|
||||||
|
$domain = trim($_GET['domain'] ?? '');
|
||||||
|
if (!$domain) { http_response_code(400); echo json_encode(['error' => 'Domain puuttuu']); break; }
|
||||||
|
try {
|
||||||
|
$client = getIRedMailClient();
|
||||||
|
$result = $client->getUsers($domain);
|
||||||
|
echo json_encode($result['_data'] ?? []);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'iredmail_user_create':
|
||||||
|
requireSuperAdmin();
|
||||||
|
if ($method !== 'POST') break;
|
||||||
|
$input = json_decode(file_get_contents('php://input'), true);
|
||||||
|
$email = trim($input['email'] ?? '');
|
||||||
|
$password = $input['password'] ?? '';
|
||||||
|
if (!$email || !$password) { http_response_code(400); echo json_encode(['error' => 'Sähköposti ja salasana vaaditaan']); break; }
|
||||||
|
try {
|
||||||
|
$client = getIRedMailClient();
|
||||||
|
$opts = [];
|
||||||
|
if (!empty($input['cn'])) $opts['cn'] = $input['cn'];
|
||||||
|
if (isset($input['mailQuota'])) $opts['mailQuota'] = intval($input['mailQuota']);
|
||||||
|
$client->createUser($email, $password, $opts);
|
||||||
|
echo json_encode(['ok' => true]);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'iredmail_user_update':
|
||||||
|
requireSuperAdmin();
|
||||||
|
if ($method !== 'POST') break;
|
||||||
|
$input = json_decode(file_get_contents('php://input'), true);
|
||||||
|
$email = trim($input['email'] ?? '');
|
||||||
|
if (!$email) { http_response_code(400); echo json_encode(['error' => 'Sähköposti puuttuu']); break; }
|
||||||
|
try {
|
||||||
|
$client = getIRedMailClient();
|
||||||
|
$opts = [];
|
||||||
|
if (!empty($input['password'])) $opts['password'] = $input['password'];
|
||||||
|
if (!empty($input['cn'])) $opts['cn'] = $input['cn'];
|
||||||
|
if (isset($input['mailQuota'])) $opts['mailQuota'] = intval($input['mailQuota']);
|
||||||
|
if (isset($input['accountStatus'])) $opts['accountStatus'] = $input['accountStatus'];
|
||||||
|
$client->updateUser($email, $opts);
|
||||||
|
echo json_encode(['ok' => true]);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'iredmail_user_delete':
|
||||||
|
requireSuperAdmin();
|
||||||
|
if ($method !== 'POST') break;
|
||||||
|
$input = json_decode(file_get_contents('php://input'), true);
|
||||||
|
$email = trim($input['email'] ?? '');
|
||||||
|
if (!$email) { http_response_code(400); echo json_encode(['error' => 'Sähköposti puuttuu']); break; }
|
||||||
|
try {
|
||||||
|
$client = getIRedMailClient();
|
||||||
|
$client->deleteUser($email);
|
||||||
|
echo json_encode(['ok' => true]);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'iredmail_aliases':
|
||||||
|
requireSuperAdmin();
|
||||||
|
$domain = trim($_GET['domain'] ?? '');
|
||||||
|
if (!$domain) { http_response_code(400); echo json_encode(['error' => 'Domain puuttuu']); break; }
|
||||||
|
try {
|
||||||
|
$client = getIRedMailClient();
|
||||||
|
$result = $client->getAliases($domain);
|
||||||
|
echo json_encode($result['_data'] ?? []);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'iredmail_alias_create':
|
||||||
|
requireSuperAdmin();
|
||||||
|
if ($method !== 'POST') break;
|
||||||
|
$input = json_decode(file_get_contents('php://input'), true);
|
||||||
|
$alias = trim($input['alias'] ?? '');
|
||||||
|
if (!$alias) { http_response_code(400); echo json_encode(['error' => 'Alias puuttuu']); break; }
|
||||||
|
try {
|
||||||
|
$client = getIRedMailClient();
|
||||||
|
$opts = [];
|
||||||
|
if (!empty($input['cn'])) $opts['cn'] = $input['cn'];
|
||||||
|
if (!empty($input['members'])) $opts['accessPolicy'] = 'membersonly';
|
||||||
|
$client->createAlias($alias, $opts);
|
||||||
|
echo json_encode(['ok' => true]);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
|
case 'iredmail_alias_delete':
|
||||||
|
requireSuperAdmin();
|
||||||
|
if ($method !== 'POST') break;
|
||||||
|
$input = json_decode(file_get_contents('php://input'), true);
|
||||||
|
$alias = trim($input['alias'] ?? '');
|
||||||
|
if (!$alias) { http_response_code(400); echo json_encode(['error' => 'Alias puuttuu']); break; }
|
||||||
|
try {
|
||||||
|
$client = getIRedMailClient();
|
||||||
|
$client->deleteAlias($alias);
|
||||||
|
echo json_encode(['ok' => true]);
|
||||||
|
} catch (\Throwable $e) {
|
||||||
|
http_response_code(500);
|
||||||
|
echo json_encode(['error' => $e->getMessage()]);
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
|
||||||
default:
|
default:
|
||||||
http_response_code(404);
|
http_response_code(404);
|
||||||
echo json_encode(['error' => 'Tuntematon toiminto']);
|
echo json_encode(['error' => 'Tuntematon toiminto']);
|
||||||
|
|||||||
233
index.html
233
index.html
@@ -89,6 +89,7 @@
|
|||||||
<button class="tab" data-tab="documents">Dokumentit</button>
|
<button class="tab" data-tab="documents">Dokumentit</button>
|
||||||
<button class="tab" data-tab="netadmin">NetAdmin</button>
|
<button class="tab" data-tab="netadmin">NetAdmin</button>
|
||||||
<button class="tab" data-tab="changelog">Muutosloki</button>
|
<button class="tab" data-tab="changelog">Muutosloki</button>
|
||||||
|
<button class="tab" data-tab="hallinta" id="tab-hallinta" style="display:none">Hallinta</button>
|
||||||
<button class="tab" data-tab="settings" id="tab-settings" style="display:none">API</button>
|
<button class="tab" data-tab="settings" id="tab-settings" style="display:none">API</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -1509,6 +1510,238 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Tab: Hallinta (vain superadmin) -->
|
||||||
|
<div class="tab-content" id="tab-content-hallinta">
|
||||||
|
<div class="sub-tab-bar" id="hallinta-sub-tab-bar">
|
||||||
|
<button class="sub-tab active" data-hallinta-subtab="hallinta-email">Sähköposti</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="subtab-hallinta-email" class="sub-tab-content active">
|
||||||
|
<div class="main-container">
|
||||||
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem;">
|
||||||
|
<h3 style="color:var(--primary-dark);margin:0;">Sähköpostinhallinta (iRedMail)</h3>
|
||||||
|
<button class="btn-secondary" id="btn-iredmail-settings">⚙ Asetukset</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div id="iredmail-status" class="stat-card" style="margin-bottom:1rem;padding:0.75rem 1rem;font-size:0.9rem;">
|
||||||
|
<span id="iredmail-status-text">Yhteyttä ei ole määritetty</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Domain-listaus -->
|
||||||
|
<div id="iredmail-domain-section">
|
||||||
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:0.5rem;">
|
||||||
|
<h4 style="margin:0;color:var(--primary-dark);">Domainit</h4>
|
||||||
|
<button class="btn-primary" id="btn-iredmail-add-domain">+ Lisää domain</button>
|
||||||
|
</div>
|
||||||
|
<div class="table-card">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Domain</th>
|
||||||
|
<th>Tilejä</th>
|
||||||
|
<th>Aliaksia</th>
|
||||||
|
<th>Kiintiö (MB)</th>
|
||||||
|
<th style="width:100px;">Toiminnot</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="iredmail-domain-tbody"></tbody>
|
||||||
|
</table>
|
||||||
|
<div id="no-iredmail-domains" style="text-align:center;padding:2rem;color:#aaa;display:none;">
|
||||||
|
Ei domaineja. Määritä ensin iRedMail-yhteys asetuksista.
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Käyttäjät (näkyy kun domain valittu) -->
|
||||||
|
<div id="iredmail-users-section" style="display:none;margin-top:1.5rem;">
|
||||||
|
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:0.5rem;flex-wrap:wrap;gap:0.5rem;">
|
||||||
|
<h4 style="margin:0;color:var(--primary-dark);">
|
||||||
|
<a href="#" id="iredmail-back-to-domains" style="text-decoration:none;color:var(--primary-color);">← Domainit</a>
|
||||||
|
/ <span id="iredmail-current-domain"></span> — Tilit
|
||||||
|
</h4>
|
||||||
|
<div style="display:flex;gap:0.5rem;">
|
||||||
|
<input type="text" id="iredmail-user-search" placeholder="Hae tilejä..." style="width:200px;">
|
||||||
|
<button class="btn-primary" id="btn-iredmail-add-user">+ Lisää tili</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="table-card">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Sähköposti</th>
|
||||||
|
<th>Nimi</th>
|
||||||
|
<th>Kiintiö (MB)</th>
|
||||||
|
<th>Tila</th>
|
||||||
|
<th style="width:160px;">Toiminnot</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="iredmail-user-tbody"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div style="display:flex;justify-content:space-between;align-items:center;margin:1.5rem 0 0.5rem;">
|
||||||
|
<h4 style="margin:0;color:var(--primary-dark);">Aliakset</h4>
|
||||||
|
<button class="btn-primary" id="btn-iredmail-add-alias">+ Lisää alias</button>
|
||||||
|
</div>
|
||||||
|
<div class="table-card">
|
||||||
|
<table>
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Alias</th>
|
||||||
|
<th>Kohde(t)</th>
|
||||||
|
<th style="width:80px;">Toiminnot</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody id="iredmail-alias-tbody"></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Modaalit: iRedMail -->
|
||||||
|
<div class="modal" id="iredmail-config-modal" style="display:none;">
|
||||||
|
<div class="modal-content" style="max-width:500px;">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3>iRedMail-asetukset</h3>
|
||||||
|
<button class="modal-close" onclick="document.getElementById('iredmail-config-modal').style.display='none'">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="form-grid" style="max-width:100%;">
|
||||||
|
<div class="form-group full-width">
|
||||||
|
<label>API URL</label>
|
||||||
|
<input type="text" id="iredmail-cfg-url" placeholder="https://mail.example.com/iredadmin">
|
||||||
|
</div>
|
||||||
|
<div class="form-group full-width">
|
||||||
|
<label>Admin-sähköposti</label>
|
||||||
|
<input type="text" id="iredmail-cfg-email" placeholder="postmaster@example.com">
|
||||||
|
</div>
|
||||||
|
<div class="form-group full-width">
|
||||||
|
<label>Salasana</label>
|
||||||
|
<input type="password" id="iredmail-cfg-password" placeholder="Jätä tyhjäksi jos ei muuteta">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="display:flex;gap:0.5rem;margin-top:1rem;">
|
||||||
|
<button class="btn-primary" id="btn-iredmail-cfg-save">Tallenna</button>
|
||||||
|
<button class="btn-secondary" id="btn-iredmail-cfg-test">Testaa yhteyttä</button>
|
||||||
|
</div>
|
||||||
|
<div id="iredmail-cfg-status" style="margin-top:0.5rem;font-size:0.85rem;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal" id="iredmail-domain-modal" style="display:none;">
|
||||||
|
<div class="modal-content" style="max-width:450px;">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3>Lisää domain</h3>
|
||||||
|
<button class="modal-close" onclick="document.getElementById('iredmail-domain-modal').style.display='none'">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="form-grid" style="max-width:100%;">
|
||||||
|
<div class="form-group full-width">
|
||||||
|
<label>Domain *</label>
|
||||||
|
<input type="text" id="iredmail-domain-name" placeholder="esim. yritys.fi">
|
||||||
|
</div>
|
||||||
|
<div class="form-group full-width">
|
||||||
|
<label>Kuvaus</label>
|
||||||
|
<input type="text" id="iredmail-domain-cn" placeholder="Yrityksen nimi">
|
||||||
|
</div>
|
||||||
|
<div class="form-group full-width">
|
||||||
|
<label>Kiintiö (MB, 0 = rajaton)</label>
|
||||||
|
<input type="number" id="iredmail-domain-quota" value="0" min="0">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="display:flex;gap:0.5rem;margin-top:1rem;">
|
||||||
|
<button class="btn-primary" id="btn-iredmail-domain-save">Lisää</button>
|
||||||
|
<button class="btn-secondary" onclick="document.getElementById('iredmail-domain-modal').style.display='none'">Peruuta</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal" id="iredmail-user-modal" style="display:none;">
|
||||||
|
<div class="modal-content" style="max-width:450px;">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3 id="iredmail-user-modal-title">Lisää tili</h3>
|
||||||
|
<button class="modal-close" onclick="document.getElementById('iredmail-user-modal').style.display='none'">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="form-grid" style="max-width:100%;">
|
||||||
|
<div class="form-group full-width" id="iredmail-user-email-group">
|
||||||
|
<label>Sähköpostiosoite *</label>
|
||||||
|
<div style="display:flex;gap:0.3rem;align-items:center;">
|
||||||
|
<input type="text" id="iredmail-user-local" placeholder="kayttaja" style="flex:1;">
|
||||||
|
<span>@</span>
|
||||||
|
<span id="iredmail-user-domain-label" style="font-weight:600;"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group full-width">
|
||||||
|
<label>Nimi</label>
|
||||||
|
<input type="text" id="iredmail-user-cn" placeholder="Etunimi Sukunimi">
|
||||||
|
</div>
|
||||||
|
<div class="form-group full-width">
|
||||||
|
<label>Salasana *</label>
|
||||||
|
<input type="password" id="iredmail-user-password" placeholder="Vähintään 8 merkkiä">
|
||||||
|
</div>
|
||||||
|
<div class="form-group full-width">
|
||||||
|
<label>Kiintiö (MB, 0 = rajaton)</label>
|
||||||
|
<input type="number" id="iredmail-user-quota" value="1024" min="0">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="display:flex;gap:0.5rem;margin-top:1rem;">
|
||||||
|
<button class="btn-primary" id="btn-iredmail-user-save">Tallenna</button>
|
||||||
|
<button class="btn-secondary" onclick="document.getElementById('iredmail-user-modal').style.display='none'">Peruuta</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal" id="iredmail-password-modal" style="display:none;">
|
||||||
|
<div class="modal-content" style="max-width:400px;">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3>Vaihda salasana</h3>
|
||||||
|
<button class="modal-close" onclick="document.getElementById('iredmail-password-modal').style.display='none'">×</button>
|
||||||
|
</div>
|
||||||
|
<p style="color:#666;font-size:0.9rem;margin-bottom:1rem;" id="iredmail-pw-email-label"></p>
|
||||||
|
<div class="form-grid" style="max-width:100%;">
|
||||||
|
<div class="form-group full-width">
|
||||||
|
<label>Uusi salasana *</label>
|
||||||
|
<input type="password" id="iredmail-pw-new" placeholder="Vähintään 8 merkkiä">
|
||||||
|
</div>
|
||||||
|
<div class="form-group full-width">
|
||||||
|
<label>Vahvista salasana *</label>
|
||||||
|
<input type="password" id="iredmail-pw-confirm" placeholder="Sama salasana uudelleen">
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="display:flex;gap:0.5rem;margin-top:1rem;">
|
||||||
|
<button class="btn-primary" id="btn-iredmail-pw-save">Vaihda</button>
|
||||||
|
<button class="btn-secondary" onclick="document.getElementById('iredmail-password-modal').style.display='none'">Peruuta</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="modal" id="iredmail-alias-modal" style="display:none;">
|
||||||
|
<div class="modal-content" style="max-width:450px;">
|
||||||
|
<div class="modal-header">
|
||||||
|
<h3>Lisää alias</h3>
|
||||||
|
<button class="modal-close" onclick="document.getElementById('iredmail-alias-modal').style.display='none'">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="form-grid" style="max-width:100%;">
|
||||||
|
<div class="form-group full-width">
|
||||||
|
<label>Alias-osoite *</label>
|
||||||
|
<div style="display:flex;gap:0.3rem;align-items:center;">
|
||||||
|
<input type="text" id="iredmail-alias-local" placeholder="info" style="flex:1;">
|
||||||
|
<span>@</span>
|
||||||
|
<span id="iredmail-alias-domain-label" style="font-weight:600;"></span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="form-group full-width">
|
||||||
|
<label>Kohdeosoitteet (yksi per rivi)</label>
|
||||||
|
<textarea id="iredmail-alias-members" rows="4" placeholder="user1@example.com user2@example.com"></textarea>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="display:flex;gap:0.5rem;margin-top:1rem;">
|
||||||
|
<button class="btn-primary" id="btn-iredmail-alias-save">Tallenna</button>
|
||||||
|
<button class="btn-secondary" onclick="document.getElementById('iredmail-alias-modal').style.display='none'">Peruuta</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Tab: Asetukset (vain admin) -->
|
<!-- Tab: Asetukset (vain admin) -->
|
||||||
<div class="tab-content" id="tab-content-settings">
|
<div class="tab-content" id="tab-content-settings">
|
||||||
<div class="main-container">
|
<div class="main-container">
|
||||||
|
|||||||
327
script.js
327
script.js
@@ -329,6 +329,10 @@ function switchToTab(target, subTab, extra) {
|
|||||||
if (target === 'users') loadUsers();
|
if (target === 'users') loadUsers();
|
||||||
if (target === 'settings') loadSettings();
|
if (target === 'settings') loadSettings();
|
||||||
if (target === 'companies') loadCompaniesTab();
|
if (target === 'companies') loadCompaniesTab();
|
||||||
|
if (target === 'hallinta') {
|
||||||
|
const hallintaSubMap = { email: 'hallinta-email' };
|
||||||
|
switchHallintaSubTab(hallintaSubMap[subTab] || 'hallinta-email');
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
document.querySelectorAll('.tab').forEach(tab => {
|
document.querySelectorAll('.tab').forEach(tab => {
|
||||||
@@ -6739,6 +6743,9 @@ function applyModules(modules, hasIntegrations) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// Hallinta-tabi: aina näkyvä superadmineille, ei moduuliriippuvuutta
|
||||||
|
const hallintaTab = document.getElementById('tab-hallinta');
|
||||||
|
if (hallintaTab) hallintaTab.style.display = isSuperAdmin ? '' : 'none';
|
||||||
// Jos aktiivinen tabi on piilotettu → vaihda ensimmäiseen näkyvään
|
// Jos aktiivinen tabi on piilotettu → vaihda ensimmäiseen näkyvään
|
||||||
const activeTab = document.querySelector('.tab.active');
|
const activeTab = document.querySelector('.tab.active');
|
||||||
if (activeTab && activeTab.style.display === 'none') {
|
if (activeTab && activeTab.style.display === 'none') {
|
||||||
@@ -6800,6 +6807,326 @@ async function loadBranding() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==================== HALLINTA: IREDMAIL SÄHKÖPOSTI ====================
|
||||||
|
|
||||||
|
let iredmailCurrentDomain = '';
|
||||||
|
|
||||||
|
function switchHallintaSubTab(target) {
|
||||||
|
document.querySelectorAll('#hallinta-sub-tab-bar .sub-tab').forEach(t => t.classList.remove('active'));
|
||||||
|
document.querySelectorAll('#tab-content-hallinta > .sub-tab-content').forEach(c => c.classList.remove('active'));
|
||||||
|
const btn = document.querySelector(`[data-hallinta-subtab="${target}"]`);
|
||||||
|
if (btn) btn.classList.add('active');
|
||||||
|
const content = document.getElementById('subtab-' + target);
|
||||||
|
if (content) content.classList.add('active');
|
||||||
|
if (target === 'hallinta-email') loadIRedMailDomains();
|
||||||
|
window.location.hash = 'hallinta/' + target.replace('hallinta-', '');
|
||||||
|
}
|
||||||
|
|
||||||
|
document.querySelectorAll('#hallinta-sub-tab-bar .sub-tab').forEach(btn => {
|
||||||
|
btn.addEventListener('click', () => switchHallintaSubTab(btn.dataset.hallintaSubtab));
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- Domainit ---
|
||||||
|
|
||||||
|
async function loadIRedMailDomains() {
|
||||||
|
const tbody = document.getElementById('iredmail-domain-tbody');
|
||||||
|
const noData = document.getElementById('no-iredmail-domains');
|
||||||
|
const statusEl = document.getElementById('iredmail-status-text');
|
||||||
|
try {
|
||||||
|
const domains = await apiCall('iredmail_domains');
|
||||||
|
if (!domains || domains.length === 0) {
|
||||||
|
tbody.innerHTML = '';
|
||||||
|
noData.style.display = '';
|
||||||
|
statusEl.innerHTML = '⚠ Yhteys OK, mutta ei domaineja';
|
||||||
|
statusEl.parentElement.style.background = '#fff3cd';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
noData.style.display = 'none';
|
||||||
|
statusEl.innerHTML = '✓ Yhteys OK — ' + domains.length + ' domainia';
|
||||||
|
statusEl.parentElement.style.background = '#d4edda';
|
||||||
|
renderIRedMailDomains(domains);
|
||||||
|
} catch (e) {
|
||||||
|
tbody.innerHTML = '';
|
||||||
|
noData.style.display = '';
|
||||||
|
statusEl.innerHTML = '✗ ' + (e.message || 'Yhteysvirhe');
|
||||||
|
statusEl.parentElement.style.background = '#f8d7da';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderIRedMailDomains(domains) {
|
||||||
|
const tbody = document.getElementById('iredmail-domain-tbody');
|
||||||
|
tbody.innerHTML = domains.map(d => {
|
||||||
|
const domain = d.domain || d.primaryDomain || d.domainName || (typeof d === 'string' ? d : JSON.stringify(d));
|
||||||
|
const users = d.mailboxes || d.numberOfUsers || d.aliases_count || '–';
|
||||||
|
const aliases = d.aliases || d.numberOfAliases || '–';
|
||||||
|
const quota = d.maxQuotaSize || d.quota || '0';
|
||||||
|
return `<tr>
|
||||||
|
<td><a href="#" onclick="openIRedMailDomain('${esc(domain)}');return false;" style="font-weight:600;color:var(--primary-color);">${esc(domain)}</a></td>
|
||||||
|
<td>${esc(String(users))}</td>
|
||||||
|
<td>${esc(String(aliases))}</td>
|
||||||
|
<td>${esc(String(quota))}</td>
|
||||||
|
<td>
|
||||||
|
<button onclick="deleteIRedMailDomain('${esc(domain)}')" class="btn-danger" style="font-size:0.8rem;padding:3px 8px;">Poista</button>
|
||||||
|
</td>
|
||||||
|
</tr>`;
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
async function openIRedMailDomain(domain) {
|
||||||
|
iredmailCurrentDomain = domain;
|
||||||
|
document.getElementById('iredmail-domain-section').style.display = 'none';
|
||||||
|
document.getElementById('iredmail-users-section').style.display = '';
|
||||||
|
document.getElementById('iredmail-current-domain').textContent = domain;
|
||||||
|
document.getElementById('iredmail-user-domain-label').textContent = domain;
|
||||||
|
document.getElementById('iredmail-alias-domain-label').textContent = domain;
|
||||||
|
await Promise.all([loadIRedMailUsers(domain), loadIRedMailAliases(domain)]);
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('iredmail-back-to-domains').addEventListener('click', (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
iredmailCurrentDomain = '';
|
||||||
|
document.getElementById('iredmail-users-section').style.display = 'none';
|
||||||
|
document.getElementById('iredmail-domain-section').style.display = '';
|
||||||
|
});
|
||||||
|
|
||||||
|
async function deleteIRedMailDomain(domain) {
|
||||||
|
if (!confirm('Poistetaanko domain ' + domain + ' ja KAIKKI sen tilit? Tätä ei voi perua!')) return;
|
||||||
|
try {
|
||||||
|
await apiCall('iredmail_domain_delete', 'POST', { domain });
|
||||||
|
await loadIRedMailDomains();
|
||||||
|
} catch (e) { alert(e.message); }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Lisää domain
|
||||||
|
document.getElementById('btn-iredmail-add-domain').addEventListener('click', () => {
|
||||||
|
document.getElementById('iredmail-domain-name').value = '';
|
||||||
|
document.getElementById('iredmail-domain-cn').value = '';
|
||||||
|
document.getElementById('iredmail-domain-quota').value = '0';
|
||||||
|
document.getElementById('iredmail-domain-modal').style.display = 'flex';
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('btn-iredmail-domain-save').addEventListener('click', async () => {
|
||||||
|
const domain = document.getElementById('iredmail-domain-name').value.trim();
|
||||||
|
if (!domain) { alert('Domain puuttuu'); return; }
|
||||||
|
try {
|
||||||
|
await apiCall('iredmail_domain_create', 'POST', {
|
||||||
|
domain,
|
||||||
|
cn: document.getElementById('iredmail-domain-cn').value.trim(),
|
||||||
|
quota: parseInt(document.getElementById('iredmail-domain-quota').value) || 0,
|
||||||
|
});
|
||||||
|
document.getElementById('iredmail-domain-modal').style.display = 'none';
|
||||||
|
await loadIRedMailDomains();
|
||||||
|
} catch (e) { alert(e.message); }
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- Käyttäjät ---
|
||||||
|
|
||||||
|
async function loadIRedMailUsers(domain) {
|
||||||
|
const tbody = document.getElementById('iredmail-user-tbody');
|
||||||
|
try {
|
||||||
|
const users = await apiCall('iredmail_users&domain=' + encodeURIComponent(domain));
|
||||||
|
renderIRedMailUsers(users || []);
|
||||||
|
} catch (e) {
|
||||||
|
tbody.innerHTML = `<tr><td colspan="5" style="color:#e74c3c;text-align:center;">${esc(e.message)}</td></tr>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderIRedMailUsers(users) {
|
||||||
|
const tbody = document.getElementById('iredmail-user-tbody');
|
||||||
|
if (users.length === 0) {
|
||||||
|
tbody.innerHTML = '<tr><td colspan="5" style="text-align:center;color:#aaa;">Ei tilejä</td></tr>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tbody.innerHTML = users.map(u => {
|
||||||
|
const email = u.mail || u.email || u.username || '';
|
||||||
|
const name = u.cn || u.name || u.displayName || '';
|
||||||
|
const quota = u.mailQuota || u.quota || '0';
|
||||||
|
const status = u.accountStatus === 'disabled' ? '<span style="color:#e74c3c;">Ei käytössä</span>' : '<span style="color:#27ae60;">Aktiivinen</span>';
|
||||||
|
return `<tr data-email="${esc(email.toLowerCase())}">
|
||||||
|
<td style="font-weight:500;">${esc(email)}</td>
|
||||||
|
<td>${esc(name)}</td>
|
||||||
|
<td>${esc(String(quota))}</td>
|
||||||
|
<td>${status}</td>
|
||||||
|
<td>
|
||||||
|
<button onclick="showIRedMailPasswordModal('${esc(email)}')" class="btn-secondary" style="font-size:0.75rem;padding:2px 6px;">Salasana</button>
|
||||||
|
<button onclick="deleteIRedMailUser('${esc(email)}')" class="btn-danger" style="font-size:0.75rem;padding:2px 6px;">Poista</button>
|
||||||
|
</td>
|
||||||
|
</tr>`;
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Haku
|
||||||
|
document.getElementById('iredmail-user-search').addEventListener('input', function() {
|
||||||
|
const q = this.value.toLowerCase();
|
||||||
|
document.querySelectorAll('#iredmail-user-tbody tr[data-email]').forEach(row => {
|
||||||
|
const email = row.dataset.email || '';
|
||||||
|
const name = row.children[1]?.textContent?.toLowerCase() || '';
|
||||||
|
row.style.display = (email.includes(q) || name.includes(q)) ? '' : 'none';
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Lisää tili
|
||||||
|
document.getElementById('btn-iredmail-add-user').addEventListener('click', () => {
|
||||||
|
document.getElementById('iredmail-user-modal-title').textContent = 'Lisää tili';
|
||||||
|
document.getElementById('iredmail-user-local').value = '';
|
||||||
|
document.getElementById('iredmail-user-cn').value = '';
|
||||||
|
document.getElementById('iredmail-user-password').value = '';
|
||||||
|
document.getElementById('iredmail-user-quota').value = '1024';
|
||||||
|
document.getElementById('iredmail-user-modal').style.display = 'flex';
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('btn-iredmail-user-save').addEventListener('click', async () => {
|
||||||
|
const local = document.getElementById('iredmail-user-local').value.trim();
|
||||||
|
const password = document.getElementById('iredmail-user-password').value;
|
||||||
|
if (!local) { alert('Käyttäjänimi puuttuu'); return; }
|
||||||
|
if (!password || password.length < 8) { alert('Salasana vähintään 8 merkkiä'); return; }
|
||||||
|
const email = local + '@' + iredmailCurrentDomain;
|
||||||
|
try {
|
||||||
|
await apiCall('iredmail_user_create', 'POST', {
|
||||||
|
email,
|
||||||
|
password,
|
||||||
|
cn: document.getElementById('iredmail-user-cn').value.trim(),
|
||||||
|
mailQuota: parseInt(document.getElementById('iredmail-user-quota').value) || 0,
|
||||||
|
});
|
||||||
|
document.getElementById('iredmail-user-modal').style.display = 'none';
|
||||||
|
await loadIRedMailUsers(iredmailCurrentDomain);
|
||||||
|
} catch (e) { alert(e.message); }
|
||||||
|
});
|
||||||
|
|
||||||
|
async function deleteIRedMailUser(email) {
|
||||||
|
if (!confirm('Poistetaanko tili ' + email + '? Kaikki viestit menetetään!')) return;
|
||||||
|
try {
|
||||||
|
await apiCall('iredmail_user_delete', 'POST', { email });
|
||||||
|
await loadIRedMailUsers(iredmailCurrentDomain);
|
||||||
|
} catch (e) { alert(e.message); }
|
||||||
|
}
|
||||||
|
|
||||||
|
// Salasanan vaihto
|
||||||
|
function showIRedMailPasswordModal(email) {
|
||||||
|
document.getElementById('iredmail-pw-email-label').textContent = email;
|
||||||
|
document.getElementById('iredmail-pw-new').value = '';
|
||||||
|
document.getElementById('iredmail-pw-confirm').value = '';
|
||||||
|
document.getElementById('iredmail-password-modal').style.display = 'flex';
|
||||||
|
document.getElementById('iredmail-password-modal').dataset.email = email;
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('btn-iredmail-pw-save').addEventListener('click', async () => {
|
||||||
|
const pw1 = document.getElementById('iredmail-pw-new').value;
|
||||||
|
const pw2 = document.getElementById('iredmail-pw-confirm').value;
|
||||||
|
if (!pw1 || pw1.length < 8) { alert('Salasana vähintään 8 merkkiä'); return; }
|
||||||
|
if (pw1 !== pw2) { alert('Salasanat eivät täsmää'); return; }
|
||||||
|
const email = document.getElementById('iredmail-password-modal').dataset.email;
|
||||||
|
try {
|
||||||
|
await apiCall('iredmail_user_update', 'POST', { email, password: pw1 });
|
||||||
|
document.getElementById('iredmail-password-modal').style.display = 'none';
|
||||||
|
alert('Salasana vaihdettu!');
|
||||||
|
} catch (e) { alert(e.message); }
|
||||||
|
});
|
||||||
|
|
||||||
|
// --- Aliakset ---
|
||||||
|
|
||||||
|
async function loadIRedMailAliases(domain) {
|
||||||
|
const tbody = document.getElementById('iredmail-alias-tbody');
|
||||||
|
try {
|
||||||
|
const aliases = await apiCall('iredmail_aliases&domain=' + encodeURIComponent(domain));
|
||||||
|
renderIRedMailAliases(aliases || []);
|
||||||
|
} catch (e) {
|
||||||
|
tbody.innerHTML = `<tr><td colspan="3" style="color:#e74c3c;text-align:center;">${esc(e.message)}</td></tr>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function renderIRedMailAliases(aliases) {
|
||||||
|
const tbody = document.getElementById('iredmail-alias-tbody');
|
||||||
|
if (aliases.length === 0) {
|
||||||
|
tbody.innerHTML = '<tr><td colspan="3" style="text-align:center;color:#aaa;">Ei aliaksia</td></tr>';
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
tbody.innerHTML = aliases.map(a => {
|
||||||
|
const alias = a.mail || a.address || a.alias || '';
|
||||||
|
const members = a.members || a.goto || a.accessPolicy || '';
|
||||||
|
const membersStr = Array.isArray(members) ? members.join(', ') : String(members);
|
||||||
|
return `<tr>
|
||||||
|
<td style="font-weight:500;">${esc(alias)}</td>
|
||||||
|
<td style="font-size:0.85rem;color:#666;">${esc(membersStr)}</td>
|
||||||
|
<td>
|
||||||
|
<button onclick="deleteIRedMailAlias('${esc(alias)}')" class="btn-danger" style="font-size:0.75rem;padding:2px 6px;">Poista</button>
|
||||||
|
</td>
|
||||||
|
</tr>`;
|
||||||
|
}).join('');
|
||||||
|
}
|
||||||
|
|
||||||
|
document.getElementById('btn-iredmail-add-alias').addEventListener('click', () => {
|
||||||
|
document.getElementById('iredmail-alias-local').value = '';
|
||||||
|
document.getElementById('iredmail-alias-members').value = '';
|
||||||
|
document.getElementById('iredmail-alias-modal').style.display = 'flex';
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('btn-iredmail-alias-save').addEventListener('click', async () => {
|
||||||
|
const local = document.getElementById('iredmail-alias-local').value.trim();
|
||||||
|
if (!local) { alert('Alias puuttuu'); return; }
|
||||||
|
const alias = local + '@' + iredmailCurrentDomain;
|
||||||
|
const members = document.getElementById('iredmail-alias-members').value.trim();
|
||||||
|
try {
|
||||||
|
await apiCall('iredmail_alias_create', 'POST', {
|
||||||
|
alias,
|
||||||
|
cn: alias,
|
||||||
|
members: members,
|
||||||
|
});
|
||||||
|
document.getElementById('iredmail-alias-modal').style.display = 'none';
|
||||||
|
await loadIRedMailAliases(iredmailCurrentDomain);
|
||||||
|
} catch (e) { alert(e.message); }
|
||||||
|
});
|
||||||
|
|
||||||
|
async function deleteIRedMailAlias(alias) {
|
||||||
|
if (!confirm('Poistetaanko alias ' + alias + '?')) return;
|
||||||
|
try {
|
||||||
|
await apiCall('iredmail_alias_delete', 'POST', { alias });
|
||||||
|
await loadIRedMailAliases(iredmailCurrentDomain);
|
||||||
|
} catch (e) { alert(e.message); }
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- iRedMail asetukset ---
|
||||||
|
|
||||||
|
document.getElementById('btn-iredmail-settings').addEventListener('click', async () => {
|
||||||
|
try {
|
||||||
|
const cfg = await apiCall('iredmail_config');
|
||||||
|
document.getElementById('iredmail-cfg-url').value = cfg.url || '';
|
||||||
|
document.getElementById('iredmail-cfg-email').value = cfg.admin_email || '';
|
||||||
|
document.getElementById('iredmail-cfg-password').value = '';
|
||||||
|
document.getElementById('iredmail-cfg-password').placeholder = cfg.has_password ? 'Asetettu — jätä tyhjäksi jos ei muuteta' : 'Anna salasana';
|
||||||
|
document.getElementById('iredmail-cfg-status').innerHTML = '';
|
||||||
|
} catch (e) {
|
||||||
|
document.getElementById('iredmail-cfg-status').innerHTML = '<span style="color:#e74c3c;">' + esc(e.message) + '</span>';
|
||||||
|
}
|
||||||
|
document.getElementById('iredmail-config-modal').style.display = 'flex';
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('btn-iredmail-cfg-save').addEventListener('click', async () => {
|
||||||
|
const data = {
|
||||||
|
url: document.getElementById('iredmail-cfg-url').value.trim(),
|
||||||
|
admin_email: document.getElementById('iredmail-cfg-email').value.trim(),
|
||||||
|
};
|
||||||
|
const pw = document.getElementById('iredmail-cfg-password').value;
|
||||||
|
if (pw) data.password = pw;
|
||||||
|
try {
|
||||||
|
await apiCall('iredmail_config_save', 'POST', data);
|
||||||
|
document.getElementById('iredmail-cfg-status').innerHTML = '<span style="color:#27ae60;">Tallennettu!</span>';
|
||||||
|
} catch (e) {
|
||||||
|
document.getElementById('iredmail-cfg-status').innerHTML = '<span style="color:#e74c3c;">' + esc(e.message) + '</span>';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
document.getElementById('btn-iredmail-cfg-test').addEventListener('click', async () => {
|
||||||
|
const statusEl = document.getElementById('iredmail-cfg-status');
|
||||||
|
statusEl.innerHTML = '<span style="color:#888;">Testataan...</span>';
|
||||||
|
try {
|
||||||
|
const result = await apiCall('iredmail_test', 'POST', {});
|
||||||
|
statusEl.innerHTML = '<span style="color:#27ae60;">✓ Yhteys OK! ' + (result.domains || 0) + ' domainia.</span>';
|
||||||
|
} catch (e) {
|
||||||
|
statusEl.innerHTML = '<span style="color:#e74c3c;">✗ ' + esc(e.message) + '</span>';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// Init — branding ensin, sitten auth (luo session-cookien), sitten captcha (käyttää samaa sessiota)
|
// Init — branding ensin, sitten auth (luo session-cookien), sitten captcha (käyttää samaa sessiota)
|
||||||
loadBranding().then(async () => {
|
loadBranding().then(async () => {
|
||||||
await checkAuth();
|
await checkAuth();
|
||||||
|
|||||||
@@ -2431,3 +2431,8 @@ span.empty {
|
|||||||
.integration-config-card {
|
.integration-config-card {
|
||||||
border-left: 3px solid var(--primary-color);
|
border-left: 3px solid var(--primary-color);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* iRedMail */
|
||||||
|
#iredmail-status { border-radius: 8px; transition: background 0.3s; }
|
||||||
|
.btn-danger { background: #e74c3c; color: #fff; border: none; border-radius: 4px; cursor: pointer; padding: 4px 10px; font-size: 0.82rem; }
|
||||||
|
.btn-danger:hover { background: #c0392b; }
|
||||||
|
|||||||
Reference in New Issue
Block a user