diff --git a/script.js b/script.js
index 6973bdf..b21b4f0 100644
--- a/script.js
+++ b/script.js
@@ -329,6 +329,10 @@ function switchToTab(target, subTab, extra) {
if (target === 'users') loadUsers();
if (target === 'settings') loadSettings();
if (target === 'companies') loadCompaniesTab();
+ if (target === 'hallinta') {
+ const hallintaSubMap = { email: 'hallinta-email' };
+ switchHallintaSubTab(hallintaSubMap[subTab] || 'hallinta-email');
+ }
}
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
const activeTab = document.querySelector('.tab.active');
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 `
+ | ${esc(domain)} |
+ ${esc(String(users))} |
+ ${esc(String(aliases))} |
+ ${esc(String(quota))} |
+
+
+ |
+
`;
+ }).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 = `
| ${esc(e.message)} |
`;
+ }
+}
+
+function renderIRedMailUsers(users) {
+ const tbody = document.getElementById('iredmail-user-tbody');
+ if (users.length === 0) {
+ tbody.innerHTML = '
| Ei tilejä |
';
+ 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' ? '
Ei käytössä' : '
Aktiivinen';
+ return `
+ | ${esc(email)} |
+ ${esc(name)} |
+ ${esc(String(quota))} |
+ ${status} |
+
+
+
+ |
+
`;
+ }).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 = `
| ${esc(e.message)} |
`;
+ }
+}
+
+function renderIRedMailAliases(aliases) {
+ const tbody = document.getElementById('iredmail-alias-tbody');
+ if (aliases.length === 0) {
+ tbody.innerHTML = '
| Ei aliaksia |
';
+ 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 `
+ | ${esc(alias)} |
+ ${esc(membersStr)} |
+
+
+ |
+
`;
+ }).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 = '
' + 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();
diff --git a/style.css b/style.css
index 0173145..048f8c4 100644
--- a/style.css
+++ b/style.css
@@ -2431,3 +2431,8 @@ span.empty {
.integration-config-card {
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; }