diff --git a/api.php b/api.php
index 949447f..aef15d2 100644
--- a/api.php
+++ b/api.php
@@ -905,140 +905,190 @@ class ImapClient {
// ==================== IREDMAIL CLIENT ====================
class IRedMailClient {
- private string $baseUrl;
- private string $adminEmail;
- private string $adminPassword;
- private ?string $cookie = null;
+ private \PDO $pdo;
- 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,
+ public function __construct(string $host, string $dbName, string $dbUser, string $dbPassword, int $port = 3306) {
+ $dsn = "mysql:host={$host};port={$port};dbname={$dbName};charset=utf8mb4";
+ $this->pdo = new \PDO($dsn, $dbUser, $dbPassword, [
+ \PDO::ATTR_ERRMODE => \PDO::ERRMODE_EXCEPTION,
+ \PDO::ATTR_DEFAULT_FETCH_MODE => \PDO::FETCH_ASSOC,
+ \PDO::ATTR_TIMEOUT => 5,
]);
- $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 . ')');
- }
+ public function getDomains(): array {
+ $stmt = $this->pdo->query("SELECT d.domain, d.active, d.created,
+ (SELECT COUNT(*) FROM mailbox m WHERE m.domain = d.domain) AS mailboxes,
+ (SELECT COUNT(*) FROM alias a WHERE a.domain = d.domain AND a.address != a.goto AND a.address NOT IN (SELECT username FROM mailbox WHERE domain = d.domain)) AS aliases,
+ d.settings
+ FROM domain d WHERE d.domain != 'localhost' ORDER BY d.domain");
+ return $stmt->fetchAll();
+ }
- // 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 createDomain(string $domain, array $opts = []): void {
+ $stmt = $this->pdo->prepare("INSERT INTO domain (domain, transport, settings, created, active) VALUES (?, 'dovecot', ?, NOW(), 1)");
+ $settings = '';
+ if (!empty($opts['quota'])) $settings = 'default_user_quota:' . intval($opts['quota']) . ';';
+ $stmt->execute([$domain, $settings]);
+ }
+
+ public function deleteDomain(string $domain): void {
+ $this->pdo->beginTransaction();
+ try {
+ $this->pdo->prepare("DELETE FROM alias WHERE domain = ?")->execute([$domain]);
+ $this->pdo->prepare("DELETE FROM forwardings WHERE domain = ?")->execute([$domain]);
+ $this->pdo->prepare("DELETE FROM mailbox WHERE domain = ?")->execute([$domain]);
+ $this->pdo->prepare("DELETE FROM domain WHERE domain = ?")->execute([$domain]);
+ $this->pdo->commit();
+ } catch (\Throwable $e) {
+ $this->pdo->rollBack();
+ throw $e;
}
}
- 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));
+ $stmt = $this->pdo->prepare("SELECT username AS email, name AS cn, quota AS mailQuota, active,
+ CASE WHEN active = 1 THEN 'active' ELSE 'disabled' END AS accountStatus
+ FROM mailbox WHERE domain = ? ORDER BY username");
+ $stmt->execute([$domain]);
+ return $stmt->fetchAll();
}
- public function getUser(string $email): array {
- return $this->request('GET', 'user/' . urlencode($email));
+
+ public function createUser(string $email, string $password, array $opts = []): void {
+ $parts = explode('@', $email, 2);
+ if (count($parts) !== 2) throw new \RuntimeException('Virheellinen sähköpostiosoite');
+ [$local, $domain] = $parts;
+
+ // Tarkista onko domain olemassa
+ $stmt = $this->pdo->prepare("SELECT domain FROM domain WHERE domain = ?");
+ $stmt->execute([$domain]);
+ if (!$stmt->fetch()) throw new \RuntimeException("Domain {$domain} ei ole olemassa");
+
+ // Tarkista duplikaatti
+ $stmt = $this->pdo->prepare("SELECT username FROM mailbox WHERE username = ?");
+ $stmt->execute([$email]);
+ if ($stmt->fetch()) throw new \RuntimeException("Tili {$email} on jo olemassa");
+
+ $hash = $this->hashPassword($password);
+ $quota = intval($opts['mailQuota'] ?? 1024) * 1048576; // MB → bytes
+ $name = $opts['cn'] ?? '';
+ $maildir = $domain . '/' . $local . '/';
+
+ $this->pdo->beginTransaction();
+ try {
+ $stmt = $this->pdo->prepare("INSERT INTO mailbox (username, password, name, storagebasedirectory, storagenode, maildir, quota, domain, active, enablesmtp, enablepop3, enableimap, enabledeliver, enablelda, created) VALUES (?, ?, ?, '/var/vmail', 'vmail1', ?, ?, ?, 1, 1, 1, 1, 1, 1, NOW())");
+ $stmt->execute([$email, $hash, $name, $maildir, $quota, $domain]);
+
+ // Lisää myös alias-rivi (iRedMail vaatii)
+ $stmt = $this->pdo->prepare("INSERT INTO alias (address, goto, domain, active) VALUES (?, ?, ?, 1) ON DUPLICATE KEY UPDATE goto = VALUES(goto)");
+ $stmt->execute([$email, $email, $domain]);
+
+ // Lisää forwardings-rivi
+ $stmt = $this->pdo->prepare("INSERT INTO forwardings (address, forwarding, domain, dest_domain, is_forwarding, active) VALUES (?, ?, ?, ?, 0, 1) ON DUPLICATE KEY UPDATE forwarding = VALUES(forwarding)");
+ $stmt->execute([$email, $email, $domain, $domain]);
+
+ $this->pdo->commit();
+ } catch (\Throwable $e) {
+ $this->pdo->rollBack();
+ throw $e;
+ }
}
- 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): void {
+ $sets = [];
+ $params = [];
+ if (!empty($opts['password'])) {
+ $sets[] = 'password = ?';
+ $params[] = $this->hashPassword($opts['password']);
+ $sets[] = 'passwordlastchange = NOW()';
+ }
+ if (isset($opts['cn'])) {
+ $sets[] = 'name = ?';
+ $params[] = $opts['cn'];
+ }
+ if (isset($opts['mailQuota'])) {
+ $sets[] = 'quota = ?';
+ $params[] = intval($opts['mailQuota']) * 1048576;
+ }
+ if (isset($opts['accountStatus'])) {
+ $sets[] = 'active = ?';
+ $params[] = $opts['accountStatus'] === 'disabled' ? 0 : 1;
+ }
+ if (empty($sets)) return;
+ $sets[] = 'modified = NOW()';
+ $params[] = $email;
+ $sql = "UPDATE mailbox SET " . implode(', ', $sets) . " WHERE username = ?";
+ $this->pdo->prepare($sql)->execute($params);
}
- 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 deleteUser(string $email): void {
+ $domain = substr($email, strpos($email, '@') + 1);
+ $this->pdo->beginTransaction();
+ try {
+ $this->pdo->prepare("DELETE FROM mailbox WHERE username = ?")->execute([$email]);
+ $this->pdo->prepare("DELETE FROM alias WHERE address = ?")->execute([$email]);
+ $this->pdo->prepare("DELETE FROM forwardings WHERE address = ? OR forwarding = ?")->execute([$email, $email]);
+ $this->pdo->commit();
+ } catch (\Throwable $e) {
+ $this->pdo->rollBack();
+ throw $e;
+ }
}
+
public function getAliases(string $domain): array {
- return $this->request('GET', 'aliases/' . urlencode($domain));
+ // Aliakset = alias-rivit jotka eivät ole mailbox-käyttäjien omia
+ $stmt = $this->pdo->prepare("SELECT a.address, a.goto, a.domain, a.active
+ FROM alias a WHERE a.domain = ?
+ AND a.address NOT IN (SELECT username FROM mailbox WHERE domain = ?)
+ ORDER BY a.address");
+ $stmt->execute([$domain, $domain]);
+ $results = $stmt->fetchAll();
+ return array_map(function($a) {
+ return [
+ 'address' => $a['address'],
+ 'goto' => $a['goto'],
+ 'members' => $a['goto'],
+ 'active' => $a['active'],
+ ];
+ }, $results);
}
- public function createAlias(string $alias, array $opts = []): array {
- return $this->request('POST', 'alias/' . urlencode($alias), $opts);
+
+ public function createAlias(string $alias, string $goto): void {
+ $domain = substr($alias, strpos($alias, '@') + 1);
+ $stmt = $this->pdo->prepare("INSERT INTO alias (address, goto, domain, active, created) VALUES (?, ?, ?, 1, NOW()) ON DUPLICATE KEY UPDATE goto = VALUES(goto), modified = NOW()");
+ $stmt->execute([$alias, $goto, $domain]);
}
- public function deleteAlias(string $alias): array {
- return $this->request('DELETE', 'alias/' . urlencode($alias));
+
+ public function deleteAlias(string $alias): void {
+ $this->pdo->prepare("DELETE FROM alias WHERE address = ?")->execute([$alias]);
}
+
public function testConnection(): array {
$domains = $this->getDomains();
- return ['ok' => true, 'domains' => count($domains['_data'] ?? [])];
+ return ['ok' => true, 'domains' => count($domains)];
+ }
+
+ private function hashPassword(string $password): string {
+ // SSHA512 — iRedMail default
+ $salt = random_bytes(8);
+ $hash = hash('sha512', $password . $salt, true);
+ return '{SSHA512}' . base64_encode($hash . $salt);
}
}
-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.');
+function getIRedMailClient(string $companyId): IRedMailClient {
+ $integ = dbGetIntegration($companyId, 'iredmail');
+ if (!$integ || !$integ['enabled']) throw new \RuntimeException('iRedMail-integraatio ei ole käytössä');
+ $cfg = $integ['config'] ?? [];
+ $host = $cfg['db_host'] ?? '';
+ $dbName = $cfg['db_name'] ?? 'vmail';
+ $dbUser = $cfg['db_user'] ?? '';
+ $dbPassword = $cfg['db_password'] ?? '';
+ $port = intval($cfg['db_port'] ?? 3306);
+ if (!$host || !$dbUser || !$dbPassword) {
+ throw new \RuntimeException('iRedMail-tietokanta-asetukset puuttuvat. Aseta host, käyttäjä ja salasana.');
}
- return new IRedMailClient($url, $email, $pw);
+ return new IRedMailClient($host, $dbName, $dbUser, $dbPassword, $port);
}
// ==================== TICKETS HELPER ====================
@@ -5613,6 +5663,7 @@ switch ($action) {
if ($integ['config']) {
$cfg = is_string($integ['config']) ? json_decode($integ['config'], true) : $integ['config'];
if (isset($cfg['token'])) $cfg['token'] = str_repeat('*', 8);
+ if (isset($cfg['db_password'])) $cfg['db_password'] = str_repeat('*', 8);
$integ['config'] = $cfg;
}
}
@@ -5632,12 +5683,17 @@ switch ($action) {
if ($config === null || (is_array($config) && empty($config))) {
$config = ($old && isset($old['config'])) ? (is_string($old['config']) ? json_decode($old['config'], true) : $old['config']) : [];
} else {
- // Jos token on maskattua (********), säilytä vanha
+ // Jos token/password on maskattua (********), säilytä vanha
if (isset($config['token']) && preg_match('/^\*+$/', $config['token'])) {
if ($old && isset($old['config']['token'])) {
$config['token'] = is_string($old['config']) ? json_decode($old['config'], true)['token'] : $old['config']['token'];
}
}
+ if (isset($config['db_password']) && preg_match('/^\*+$/', $config['db_password'])) {
+ if ($old && isset($old['config']['db_password'])) {
+ $config['db_password'] = is_string($old['config']) ? json_decode($old['config'], true)['db_password'] : $old['config']['db_password'];
+ }
+ }
}
dbSaveIntegration($companyId, $type, $enabled, $config);
@@ -5657,6 +5713,10 @@ switch ($action) {
$z = new ZammadClient($cfg['url'], $cfg['token']);
$result = $z->testConnection();
echo json_encode($result);
+ } elseif ($type === 'iredmail') {
+ $client = getIRedMailClient($companyId);
+ $result = $client->testConnection();
+ echo json_encode($result);
} else {
echo json_encode(['error' => 'Tuntematon tyyppi']);
}
@@ -6040,33 +6100,12 @@ switch ($action) {
// ==================== 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':
+ $companyId = requireCompany();
requireSuperAdmin();
if ($method !== 'POST') break;
try {
- $client = getIRedMailClient();
+ $client = getIRedMailClient($companyId);
$result = $client->testConnection();
echo json_encode($result);
} catch (\Throwable $e) {
@@ -6076,11 +6115,11 @@ switch ($action) {
break;
case 'iredmail_domains':
+ $companyId = requireCompany();
requireSuperAdmin();
try {
- $client = getIRedMailClient();
- $result = $client->getDomains();
- echo json_encode($result['_data'] ?? []);
+ $client = getIRedMailClient($companyId);
+ echo json_encode($client->getDomains());
} catch (\Throwable $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
@@ -6088,17 +6127,17 @@ switch ($action) {
break;
case 'iredmail_domain_create':
+ $companyId = requireCompany();
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 = getIRedMailClient($companyId);
$opts = [];
- if (!empty($input['cn'])) $opts['cn'] = $input['cn'];
if (isset($input['quota'])) $opts['quota'] = intval($input['quota']);
- $result = $client->createDomain($domain, $opts);
+ $client->createDomain($domain, $opts);
echo json_encode(['ok' => true]);
} catch (\Throwable $e) {
http_response_code(500);
@@ -6107,13 +6146,14 @@ switch ($action) {
break;
case 'iredmail_domain_delete':
+ $companyId = requireCompany();
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 = getIRedMailClient($companyId);
$client->deleteDomain($domain);
echo json_encode(['ok' => true]);
} catch (\Throwable $e) {
@@ -6123,13 +6163,13 @@ switch ($action) {
break;
case 'iredmail_users':
+ $companyId = requireCompany();
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'] ?? []);
+ $client = getIRedMailClient($companyId);
+ echo json_encode($client->getUsers($domain));
} catch (\Throwable $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
@@ -6137,6 +6177,7 @@ switch ($action) {
break;
case 'iredmail_user_create':
+ $companyId = requireCompany();
requireSuperAdmin();
if ($method !== 'POST') break;
$input = json_decode(file_get_contents('php://input'), true);
@@ -6144,7 +6185,7 @@ switch ($action) {
$password = $input['password'] ?? '';
if (!$email || !$password) { http_response_code(400); echo json_encode(['error' => 'Sähköposti ja salasana vaaditaan']); break; }
try {
- $client = getIRedMailClient();
+ $client = getIRedMailClient($companyId);
$opts = [];
if (!empty($input['cn'])) $opts['cn'] = $input['cn'];
if (isset($input['mailQuota'])) $opts['mailQuota'] = intval($input['mailQuota']);
@@ -6157,13 +6198,14 @@ switch ($action) {
break;
case 'iredmail_user_update':
+ $companyId = requireCompany();
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 = getIRedMailClient($companyId);
$opts = [];
if (!empty($input['password'])) $opts['password'] = $input['password'];
if (!empty($input['cn'])) $opts['cn'] = $input['cn'];
@@ -6178,13 +6220,14 @@ switch ($action) {
break;
case 'iredmail_user_delete':
+ $companyId = requireCompany();
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 = getIRedMailClient($companyId);
$client->deleteUser($email);
echo json_encode(['ok' => true]);
} catch (\Throwable $e) {
@@ -6194,13 +6237,13 @@ switch ($action) {
break;
case 'iredmail_aliases':
+ $companyId = requireCompany();
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'] ?? []);
+ $client = getIRedMailClient($companyId);
+ echo json_encode($client->getAliases($domain));
} catch (\Throwable $e) {
http_response_code(500);
echo json_encode(['error' => $e->getMessage()]);
@@ -6208,17 +6251,16 @@ switch ($action) {
break;
case 'iredmail_alias_create':
+ $companyId = requireCompany();
requireSuperAdmin();
if ($method !== 'POST') break;
$input = json_decode(file_get_contents('php://input'), true);
$alias = trim($input['alias'] ?? '');
+ $members = trim($input['members'] ?? '');
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);
+ $client = getIRedMailClient($companyId);
+ $client->createAlias($alias, $members);
echo json_encode(['ok' => true]);
} catch (\Throwable $e) {
http_response_code(500);
@@ -6227,13 +6269,14 @@ switch ($action) {
break;
case 'iredmail_alias_delete':
+ $companyId = requireCompany();
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 = getIRedMailClient($companyId);
$client->deleteAlias($alias);
echo json_encode(['ok' => true]);
} catch (\Throwable $e) {
diff --git a/index.html b/index.html
index 3df35ca..f9ff678 100644
--- a/index.html
+++ b/index.html
@@ -1520,7 +1520,6 @@
Sähköpostinhallinta (iRedMail)
-
@@ -1601,34 +1600,6 @@
-
-
-
-
-
-
-
-
-
-
-
-
+
+
📮 iRedMail (Sähköposti)
+
Yhdistä iRedMail-sähköpostipalvelimen tietokantaan (vmail). Hallinta-moduulissa voit hallita domaineja, tilejä ja aliaksia.
+
+
+
+
@@ -2001,6 +2004,9 @@
+
diff --git a/script.js b/script.js
index 47d1dcc..28be95d 100644
--- a/script.js
+++ b/script.js
@@ -2767,10 +2767,22 @@ async function loadSettings() {
const zammadInteg = integs.find(i => i.type === 'zammad');
const zammadEnabled = zammadInteg?.enabled;
// Saatavuus-API kortti näkyy aina (perus API-asetukset)
+ const iredmailInteg = integs.find(i => i.type === 'iredmail');
+ const iredmailEnabled = iredmailInteg?.enabled;
const teleCard = document.getElementById('settings-telegram-card');
const zammadCard = document.getElementById('settings-zammad-card');
+ const iredmailCard = document.getElementById('settings-iredmail-card');
if (teleCard) teleCard.style.display = telegramEnabled ? '' : 'none';
if (zammadCard) zammadCard.style.display = zammadEnabled ? '' : 'none';
+ if (iredmailCard) iredmailCard.style.display = iredmailEnabled ? '' : 'none';
+ // Lataa iRedMail-asetukset korttiin
+ if (iredmailEnabled && iredmailInteg?.config) {
+ document.getElementById('company-iredmail-host').value = iredmailInteg.config.db_host || '';
+ document.getElementById('company-iredmail-dbname').value = iredmailInteg.config.db_name || 'vmail';
+ document.getElementById('company-iredmail-port').value = iredmailInteg.config.db_port || '3306';
+ document.getElementById('company-iredmail-user').value = iredmailInteg.config.db_user || '';
+ document.getElementById('company-iredmail-password').value = iredmailInteg.config.db_password || '';
+ }
// Lataa Zammad-asetukset korttiin
if (zammadEnabled && zammadInteg?.config) {
document.getElementById('company-zammad-url').value = zammadInteg.config.url || '';
@@ -3025,7 +3037,7 @@ async function loadCompanyIntegrations() {
try {
const integrations = await apiCall('integrations');
// Aseta vain checkboxit — konfiguraatio ladataan API-tabissa
- ['zammad', 'saatavuus_api', 'telegram'].forEach(type => {
+ ['zammad', 'saatavuus_api', 'telegram', 'iredmail'].forEach(type => {
const integ = integrations.find(i => i.type === type);
const cb = document.querySelector(`#integrations-checkboxes input[data-integration="${type}"]`);
if (cb) cb.checked = integ?.enabled || false;
@@ -3082,12 +3094,62 @@ document.querySelector('#integrations-checkboxes input[data-integration="saatavu
document.querySelector('#integrations-checkboxes input[data-integration="telegram"]')?.addEventListener('change', async function() {
try {
await saveSimpleIntegration('telegram', this.checked);
- // Päivitä API-sivun kortti
const card = document.getElementById('settings-telegram-card');
if (card) card.style.display = this.checked ? '' : 'none';
} catch (e) { console.error(e); }
});
+// iRedMail checkbox toggle
+document.querySelector('#integrations-checkboxes input[data-integration="iredmail"]')?.addEventListener('change', async function() {
+ try {
+ await saveSimpleIntegration('iredmail', this.checked);
+ const card = document.getElementById('settings-iredmail-card');
+ if (card) card.style.display = this.checked ? '' : 'none';
+ } catch (e) { console.error(e); }
+});
+
+// iRedMail tallenna
+async function saveCompanyIRedMail() {
+ const config = {
+ db_host: document.getElementById('company-iredmail-host').value.trim(),
+ db_name: document.getElementById('company-iredmail-dbname').value.trim() || 'vmail',
+ db_port: parseInt(document.getElementById('company-iredmail-port').value) || 3306,
+ db_user: document.getElementById('company-iredmail-user').value.trim(),
+ db_password: document.getElementById('company-iredmail-password').value,
+ };
+ await apiCall('integration_save', 'POST', { type: 'iredmail', enabled: true, config });
+}
+
+document.getElementById('btn-company-iredmail-save')?.addEventListener('click', async () => {
+ const result = document.getElementById('company-iredmail-result');
+ try {
+ await saveCompanyIRedMail();
+ result.style.display = 'block';
+ result.style.background = '#d4edda';
+ result.textContent = '✅ Tallennettu!';
+ } catch (e) {
+ result.style.display = 'block';
+ result.style.background = '#f8d7da';
+ result.textContent = '❌ ' + e.message;
+ }
+});
+
+document.getElementById('btn-company-iredmail-test')?.addEventListener('click', async () => {
+ const result = document.getElementById('company-iredmail-result');
+ result.style.display = 'block';
+ result.style.background = '#f8f9fb';
+ result.textContent = 'Testataan...';
+ try {
+ await saveCompanyIRedMail();
+ const res = await apiCall('integration_test', 'POST', { type: 'iredmail' });
+ result.style.background = '#d4edda';
+ result.textContent = `✅ Yhteys OK! ${res.domains || 0} domainia.`;
+ } catch (e) {
+ result.style.background = '#f8d7da';
+ result.textContent = '❌ ' + e.message;
+ }
+});
+
// Lataa ryhmät
document.getElementById('btn-company-zammad-groups')?.addEventListener('click', async () => {
// Tallenna ensin URL ja token
@@ -6742,9 +6804,9 @@ function applyModules(modules, hasIntegrations) {
if (mod === 'settings') {
const showSettings = enabled.includes(mod) && isAdminUser && (isSuperAdmin || hasIntegrations === true);
tabBtn.style.display = showSettings ? '' : 'none';
- // hallinta: vain superadmineille + pitää olla moduulina päällä
+ // hallinta: adminille/superadminille + pitää olla moduulina päällä
} else if (mod === 'hallinta') {
- tabBtn.style.display = (enabled.includes(mod) && isSuperAdmin) ? '' : 'none';
+ tabBtn.style.display = (enabled.includes(mod) && isAdminUser) ? '' : 'none';
} else {
tabBtn.style.display = enabled.includes(mod) ? '' : 'none';
}
@@ -7089,48 +7151,6 @@ async function deleteIRedMailAlias(alias) {
} 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 = '' + esc(e.message) + '';
- }
- 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 = 'Tallennettu!';
- } catch (e) {
- document.getElementById('iredmail-cfg-status').innerHTML = '' + esc(e.message) + '';
- }
-});
-
-document.getElementById('btn-iredmail-cfg-test').addEventListener('click', async () => {
- const statusEl = document.getElementById('iredmail-cfg-status');
- statusEl.innerHTML = 'Testataan...';
- try {
- const result = await apiCall('iredmail_test', 'POST', {});
- statusEl.innerHTML = '✓ Yhteys OK! ' + (result.domains || 0) + ' domainia.';
- } catch (e) {
- statusEl.innerHTML = '✗ ' + esc(e.message) + '';
- }
-});
-
// Init — branding ensin, sitten auth (luo session-cookien), sitten captcha (käyttää samaa sessiota)
loadBranding().then(async () => {
await checkAuth();