Integraatiot yritystasolla: kaikki kolme tyyppiä (Zammad, Saatavuus-API, Telegram) latautuvat ja tallentuvat oikein

- loadCompanyIntegrations() asettaa kaikkien checkboxien tilan
- Saatavuus-API ja Telegram checkboxit tallentavat tilan heti muutoksessa
- API-sivun kortit näkyvät/piiloutuvat integraatiotilan mukaan

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-03-12 19:39:39 +02:00
parent 46c8bbc22a
commit 1ab669a490
3 changed files with 207 additions and 204 deletions

View File

@@ -12,8 +12,8 @@
<!-- E letter inside -->
<text x="20" y="28" text-anchor="middle" font-family="Arial, Helvetica, sans-serif" font-weight="800" font-size="24" fill="white" letter-spacing="-1">E</text>
<!-- Signal arcs -->
<path d="M34 12 Q42 20 34 28" fill="none" stroke="#2e86c1" stroke-width="2" stroke-linecap="round" opacity="0.6"/>
<path d="M38 8 Q48 20 38 32" fill="none" stroke="#2e86c1" stroke-width="1.5" stroke-linecap="round" opacity="0.35"/>
<path d="M34 12 Q42 20 34 28" fill="none" stroke="#1a5276" stroke-width="2" stroke-linecap="round" opacity="0.7"/>
<path d="M38 8 Q48 20 38 32" fill="none" stroke="#1a5276" stroke-width="1.5" stroke-linecap="round" opacity="0.4"/>
</g>
<!-- EMPOR text -->
<text x="56" y="33" font-family="Arial, Helvetica, sans-serif" font-weight="800" font-size="28" fill="#1a5276" letter-spacing="2">EMPOR</text>

Before

Width:  |  Height:  |  Size: 1.1 KiB

After

Width:  |  Height:  |  Size: 1.1 KiB

View File

@@ -1471,7 +1471,7 @@
<!-- Tab: Asetukset (vain admin) -->
<div class="tab-content" id="tab-content-settings">
<div class="main-container">
<div class="table-card" style="padding:1.5rem;">
<div class="table-card" id="settings-saatavuus-api-card" style="padding:1.5rem;display:none;">
<h3 style="color:#0f3460;margin-bottom:1rem;border-bottom:2px solid #f0f2f5;padding-bottom:0.5rem;"><span id="api-company-name"></span>Saatavuus-API</h3>
<p style="color:#666;font-size:0.85rem;margin-bottom:1rem;">Julkinen API jolla verkkosivusto voi tarkistaa palvelun saatavuuden osoitteessa. Palauttaa vain osoite + nopeus - ei asiakastietoja.</p>
<div class="form-grid" style="max-width:600px;">
@@ -1517,45 +1517,8 @@
<pre id="test-api-result" style="margin-top:0.75rem;background:#f8f9fb;padding:1rem;border-radius:8px;font-size:0.85rem;display:none;overflow-x:auto;"></pre>
</div>
<!-- Integraatiot -->
<div class="table-card" style="padding:1.5rem;margin-top:1rem;">
<h3 style="color:#0f3460;margin-bottom:0.5rem;border-bottom:2px solid #f0f2f5;padding-bottom:0.5rem;">Integraatiot</h3>
<p style="color:#666;font-size:0.85rem;margin-bottom:1rem;">Ota käyttöön ja hallitse ulkoisia integraatioita moduuleittain.</p>
<div id="integrations-list"></div>
</div>
<!-- Zammad-konfiguraatio (piilotettu kunnes käytössä) -->
<div class="table-card integration-config-card" id="zammad-config-card" style="padding:1.5rem;margin-top:1rem;display:none;">
<h3 style="color:#0f3460;margin-bottom:0.5rem;border-bottom:2px solid #f0f2f5;padding-bottom:0.5rem;">
<span style="margin-right:0.5rem;">📧</span>Zammad — Asetukset
</h3>
<p style="color:#666;font-size:0.85rem;margin-bottom:1rem;">Synkronoi tiketit Zammad-helpdeskin kautta. O365-sähköpostit kulkevat Zammadin kautta.</p>
<div class="form-grid" style="max-width:600px;">
<div class="form-group full-width">
<label>Zammad URL</label>
<input type="text" id="zammad-url" placeholder="https://desk.yritys.fi" style="font-family:monospace;">
</div>
<div class="form-group full-width">
<label>API Token</label>
<input type="password" id="zammad-token" placeholder="Token..." style="font-family:monospace;">
</div>
<div class="form-group full-width">
<label>Synkronoitavat ryhmät</label>
<div id="zammad-groups-list" style="margin-bottom:0.5rem;color:#888;font-size:0.85rem;">Tallenna ensin URL ja token, sitten valitse ryhmät.</div>
</div>
<div class="form-group full-width" style="display:flex;gap:0.5rem;flex-wrap:wrap;">
<button class="btn-primary" id="btn-save-zammad">Tallenna</button>
<button class="btn-secondary" id="btn-test-zammad">Testaa yhteys</button>
<button class="btn-secondary" id="btn-load-zammad-groups">Lataa ryhmät</button>
<button class="btn-primary" id="btn-sync-zammad" style="background:#28a745;">▶ Synkronoi nyt</button>
</div>
</div>
<div id="zammad-test-result" style="margin-top:0.75rem;display:none;padding:1rem;border-radius:8px;font-size:0.85rem;font-family:monospace;"></div>
<div id="zammad-sync-result" style="margin-top:0.75rem;display:none;padding:1rem;border-radius:8px;font-size:0.85rem;"></div>
</div>
<!-- Telegram-asetukset -->
<div class="table-card" style="padding:1.5rem;margin-top:1rem;">
<div class="table-card" id="settings-telegram-card" style="padding:1.5rem;margin-top:1rem;display:none;">
<h3 style="color:#0f3460;margin-bottom:0.5rem;border-bottom:2px solid #f0f2f5;padding-bottom:0.5rem;">Telegram-hälytykset</h3>
<p style="color:#666;font-size:0.85rem;margin-bottom:1rem;">URGENT-prioriteetin tiketit lähettävät hälytyksen Telegram-bottiin.</p>
<div class="form-grid" style="max-width:500px;">
@@ -1677,6 +1640,46 @@
</label>
</div>
</div>
<!-- Integraatiot -->
<div style="margin-bottom:1.5rem;">
<h4 style="color:#0f3460;margin-bottom:0.5rem;font-size:0.95rem;">Integraatiot</h4>
<p style="color:#888;font-size:0.82rem;margin-bottom:0.75rem;">Ota käyttöön ulkoiset integraatiot tälle yritykselle.</p>
<div id="integrations-checkboxes" style="display:flex;flex-direction:column;gap:0.4rem;">
<label style="display:flex;align-items:center;gap:0.5rem;font-size:0.9rem;cursor:pointer;">
<input type="checkbox" data-integration="zammad"> 📧 Zammad (O365-sähköposti)
</label>
<label style="display:flex;align-items:center;gap:0.5rem;font-size:0.9rem;cursor:pointer;">
<input type="checkbox" data-integration="saatavuus_api"> 🌐 Saatavuus-API
</label>
<label style="display:flex;align-items:center;gap:0.5rem;font-size:0.9rem;cursor:pointer;">
<input type="checkbox" data-integration="telegram"> 🤖 Telegram-hälytykset
</label>
</div>
</div>
<!-- Zammad-asetukset (näkyy kun Zammad on valittu) -->
<div id="company-zammad-config" style="display:none;margin-bottom:1.5rem;padding:1rem;background:#f8f9fb;border-radius:10px;border-left:3px solid var(--primary-color);">
<h4 style="color:#0f3460;margin-bottom:0.5rem;font-size:0.95rem;">📧 Zammad-asetukset</h4>
<div class="form-grid" style="max-width:500px;">
<div class="form-group full-width">
<label>Zammad URL</label>
<input type="text" id="company-zammad-url" placeholder="https://desk.yritys.fi" style="font-family:monospace;">
</div>
<div class="form-group full-width">
<label>API Token</label>
<input type="password" id="company-zammad-token" placeholder="Token..." style="font-family:monospace;">
</div>
<div class="form-group full-width">
<label>Synkronoitavat ryhmät</label>
<div id="company-zammad-groups" style="color:#888;font-size:0.85rem;">Tallenna ensin, sitten valitse ryhmät.</div>
</div>
<div class="form-group full-width" style="display:flex;gap:0.5rem;flex-wrap:wrap;">
<button type="button" class="btn-secondary" id="btn-company-zammad-groups">Lataa ryhmät</button>
<button type="button" class="btn-secondary" id="btn-company-zammad-test">Testaa yhteys</button>
<button type="button" class="btn-primary" id="btn-company-zammad-sync" style="background:#28a745;">▶ Synkronoi</button>
</div>
</div>
<div id="company-zammad-result" style="margin-top:0.75rem;display:none;padding:0.75rem;border-radius:8px;font-size:0.85rem;"></div>
</div>
<div class="form-group" style="margin-top:1rem;">
<label style="font-weight:600;font-size:0.9rem;">Sallitut IP-osoitteet</label>
<textarea id="company-edit-allowed-ips" rows="3" style="font-family:monospace;font-size:0.85rem;" placeholder="192.168.1.100&#10;10.0.0.0/8"></textarea>

326
script.js
View File

@@ -2361,9 +2361,16 @@ async function loadSettings() {
document.getElementById('settings-telegram-chat').value = config.telegram_chat_id || '';
} catch (e) { console.error(e); }
// Integraatiot
loadIntegrations();
loadZammadConfig();
// Näytä API-sivun kortit integraatioiden perusteella
try {
const integs = await apiCall('integrations');
const saatavuusEnabled = integs.find(i => i.type === 'saatavuus_api')?.enabled;
const telegramEnabled = integs.find(i => i.type === 'telegram')?.enabled;
const saatCard = document.getElementById('settings-saatavuus-api-card');
const teleCard = document.getElementById('settings-telegram-card');
if (saatCard) saatCard.style.display = saatavuusEnabled ? '' : 'none';
if (teleCard) teleCard.style.display = telegramEnabled ? '' : 'none';
} catch (e) { console.error(e); }
// Vastauspohjat
loadTemplates();
@@ -2439,168 +2446,11 @@ document.getElementById('btn-test-api').addEventListener('click', async () => {
const INTEGRATION_TYPES = {
zammad: { name: 'Zammad', icon: '📧', desc: 'Synkronoi tiketit Zammad-helpdeskistä (O365-sähköpostit)' },
saatavuus_api: { name: 'Saatavuus-API', icon: '🌐', desc: 'Julkinen API saatavuustarkistukseen verkkosivuilla' },
telegram: { name: 'Telegram-hälytykset', icon: '🤖', desc: 'URGENT-tikettien hälytykset Telegram-bottiin' },
};
async function loadIntegrations() {
const container = document.getElementById('integrations-list');
if (!container) return;
try {
const integrations = await apiCall('integrations');
renderIntegrations(integrations);
} catch (e) {
console.error('loadIntegrations:', e);
}
}
function renderIntegrations(integrations) {
const container = document.getElementById('integrations-list');
const enabledMap = {};
integrations.forEach(i => { enabledMap[i.type] = i.enabled; });
let html = '';
for (const [type, meta] of Object.entries(INTEGRATION_TYPES)) {
const enabled = enabledMap[type] || false;
html += `
<div class="integration-item" data-type="${type}">
<label class="integration-toggle">
<input type="checkbox" class="integration-enabled" data-type="${type}" ${enabled ? 'checked' : ''}>
<span class="integration-icon">${meta.icon}</span>
<span class="integration-info">
<strong>${esc(meta.name)}</strong>
<small>${esc(meta.desc)}</small>
</span>
</label>
</div>`;
}
container.innerHTML = html;
// Toggle-napit
container.querySelectorAll('.integration-enabled').forEach(cb => {
cb.addEventListener('change', async () => {
const type = cb.dataset.type;
const configCard = document.getElementById(type + '-config-card');
if (configCard) configCard.style.display = cb.checked ? '' : 'none';
// Tallenna toggle-tila
try {
const existing = await apiCall('integrations');
const old = existing.find(i => i.type === type);
const config = old?.config || {};
await apiCall('integration_save', 'POST', { type, enabled: cb.checked, config });
} catch (e) { console.error(e); }
});
});
// Näytä config-kortit käytössä oleville
integrations.forEach(i => {
const card = document.getElementById(i.type + '-config-card');
if (card) card.style.display = i.enabled ? '' : 'none';
});
}
async function loadZammadConfig() {
try {
const integrations = await apiCall('integrations');
const zammad = integrations.find(i => i.type === 'zammad');
if (zammad && zammad.config) {
document.getElementById('zammad-url').value = zammad.config.url || '';
document.getElementById('zammad-token').value = zammad.config.token || '';
// Renderöi ryhmät jos tallennettu
if (zammad.config.group_ids && zammad.config.group_names) {
renderZammadGroupCheckboxes(
zammad.config.group_names.map((name, i) => ({
id: zammad.config.group_ids[i],
name: name,
})),
zammad.config.group_ids
);
}
}
} catch (e) { console.error(e); }
}
function renderZammadGroupCheckboxes(groups, selectedIds = []) {
const container = document.getElementById('zammad-groups-list');
if (!groups.length) { container.innerHTML = '<span style="color:#888;">Ei ryhmiä.</span>'; return; }
container.innerHTML = groups.map(g => `
<label style="display:flex;align-items:center;gap:0.4rem;margin-bottom:0.3rem;cursor:pointer;">
<input type="checkbox" class="zammad-group-cb" value="${g.id}" data-name="${esc(g.name)}" ${selectedIds.includes(g.id) || selectedIds.includes(String(g.id)) ? 'checked' : ''}>
${esc(g.name)}
</label>
`).join('');
}
// Zammad — Tallenna
document.getElementById('btn-save-zammad')?.addEventListener('click', async () => {
const url = document.getElementById('zammad-url').value.trim();
const token = document.getElementById('zammad-token').value.trim();
if (!url || !token) { alert('URL ja token vaaditaan'); return; }
const groupCbs = document.querySelectorAll('.zammad-group-cb:checked');
const groupIds = Array.from(groupCbs).map(cb => cb.value);
const groupNames = Array.from(groupCbs).map(cb => cb.dataset.name);
try {
await apiCall('integration_save', 'POST', {
type: 'zammad',
enabled: document.querySelector('.integration-enabled[data-type="zammad"]')?.checked || false,
config: { url, token, group_ids: groupIds, group_names: groupNames },
});
alert('Zammad-asetukset tallennettu!');
} catch (e) { alert('Virhe: ' + e.message); }
});
// Zammad — Testaa yhteys
document.getElementById('btn-test-zammad')?.addEventListener('click', async () => {
const result = document.getElementById('zammad-test-result');
result.style.display = 'block';
result.style.background = '#f8f9fb';
result.textContent = 'Testataan yhteyttä...';
try {
const res = await apiCall('integration_test', 'POST', { type: 'zammad' });
result.style.background = '#d4edda';
result.textContent = `✅ Yhteys OK! Käyttäjä: ${res.user}, Ryhmiä: ${res.groups}`;
} catch (e) {
result.style.background = '#f8d7da';
result.textContent = '❌ ' + e.message;
}
});
// Zammad — Lataa ryhmät
document.getElementById('btn-load-zammad-groups')?.addEventListener('click', async () => {
try {
const groups = await apiCall('zammad_groups');
const activeGroups = groups.filter(g => g.active);
// Hae tallennetut valitut ryhmät
const integrations = await apiCall('integrations');
const zammad = integrations.find(i => i.type === 'zammad');
const selectedIds = zammad?.config?.group_ids || [];
renderZammadGroupCheckboxes(activeGroups, selectedIds);
} catch (e) { alert('Virhe: ' + e.message); }
});
// Zammad — Synkronoi nyt
document.getElementById('btn-sync-zammad')?.addEventListener('click', async () => {
const result = document.getElementById('zammad-sync-result');
result.style.display = 'block';
result.style.background = '#f8f9fb';
result.innerHTML = '⏳ Synkronoidaan...';
try {
const res = await apiCall('zammad_sync', 'POST', {});
result.style.background = '#d4edda';
result.innerHTML = `✅ Synkronointi valmis!<br>
Tikettejä löytyi: <strong>${res.tickets_found}</strong><br>
Uusia tikettejä: <strong>${res.created}</strong><br>
Päivitettyjä: <strong>${res.updated}</strong><br>
Uusia viestejä: <strong>${res.messages_added}</strong>`;
// Päivitä tikettilista jos ollaan tukivälilehdellä
if (typeof loadTickets === 'function') loadTickets();
} catch (e) {
result.style.background = '#f8d7da';
result.innerHTML = '❌ ' + e.message;
}
});
// (Integraatiot hallitaan nyt Yritykset-välilehdellä)
// ==================== MODALS ====================
@@ -2735,12 +2585,162 @@ async function showCompanyDetail(id) {
// Vaihda aktiivinen yritys jotta API-kutsut kohdistuvat oikein
await apiCall('company_switch', 'POST', { company_id: id });
// Integraatiot — lataa tila
loadCompanyIntegrations();
// Lataa postilaatikot
loadMailboxes();
// Lataa käyttäjäoikeudet
loadCompanyUsers(id);
}
// ==================== YRITYKSEN INTEGRAATIOT ====================
async function loadCompanyIntegrations() {
try {
const integrations = await apiCall('integrations');
// Aseta kaikkien integraatioiden checkboxit
['zammad', 'saatavuus_api', 'telegram'].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;
});
// Zammad-config näkyvyys
const zammad = integrations.find(i => i.type === 'zammad');
toggleCompanyZammadConfig(zammad?.enabled || false);
// Zammad-asetukset
if (zammad?.config) {
document.getElementById('company-zammad-url').value = zammad.config.url || '';
document.getElementById('company-zammad-token').value = zammad.config.token || '';
if (zammad.config.group_ids && zammad.config.group_names) {
renderCompanyZammadGroups(
zammad.config.group_names.map((name, i) => ({ id: zammad.config.group_ids[i], name })),
zammad.config.group_ids
);
}
} else {
document.getElementById('company-zammad-url').value = '';
document.getElementById('company-zammad-token').value = '';
document.getElementById('company-zammad-groups').innerHTML = '<span style="color:#888;">Tallenna ensin, sitten valitse ryhmät.</span>';
}
} catch (e) { console.error('loadCompanyIntegrations:', e); }
}
function toggleCompanyZammadConfig(show) {
const card = document.getElementById('company-zammad-config');
if (card) card.style.display = show ? '' : 'none';
}
function renderCompanyZammadGroups(groups, selectedIds = []) {
const container = document.getElementById('company-zammad-groups');
if (!groups.length) { container.innerHTML = '<span style="color:#888;">Ei ryhmiä.</span>'; return; }
container.innerHTML = groups.map(g => `
<label style="display:flex;align-items:center;gap:0.4rem;margin-bottom:0.3rem;cursor:pointer;">
<input type="checkbox" class="company-zammad-group-cb" value="${g.id}" data-name="${esc(g.name)}" ${selectedIds.includes(g.id) || selectedIds.includes(String(g.id)) ? 'checked' : ''}>
${esc(g.name)}
</label>
`).join('');
}
async function saveCompanyZammad() {
const url = document.getElementById('company-zammad-url').value.trim();
const token = document.getElementById('company-zammad-token').value.trim();
const enabled = document.querySelector('#integrations-checkboxes input[data-integration="zammad"]')?.checked || false;
const groupCbs = document.querySelectorAll('.company-zammad-group-cb:checked');
const groupIds = Array.from(groupCbs).map(cb => cb.value);
const groupNames = Array.from(groupCbs).map(cb => cb.dataset.name);
await apiCall('integration_save', 'POST', {
type: 'zammad',
enabled,
config: { url, token, group_ids: groupIds, group_names: groupNames },
});
}
// Integraatio-toggle apufunktio (Saatavuus-API & Telegram)
async function saveSimpleIntegration(type, enabled) {
await apiCall('integration_save', 'POST', { type, enabled, config: {} });
}
// Zammad checkbox toggle
document.querySelector('#integrations-checkboxes input[data-integration="zammad"]')?.addEventListener('change', async function() {
toggleCompanyZammadConfig(this.checked);
try { await saveCompanyZammad(); } catch (e) { console.error(e); }
});
// Saatavuus-API checkbox toggle
document.querySelector('#integrations-checkboxes input[data-integration="saatavuus_api"]')?.addEventListener('change', async function() {
try {
await saveSimpleIntegration('saatavuus_api', this.checked);
// Päivitä API-sivun kortti
const card = document.getElementById('settings-saatavuus-api-card');
if (card) card.style.display = this.checked ? '' : 'none';
} catch (e) { console.error(e); }
});
// Telegram checkbox toggle
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); }
});
// Lataa ryhmät
document.getElementById('btn-company-zammad-groups')?.addEventListener('click', async () => {
// Tallenna ensin URL ja token
try {
await saveCompanyZammad();
const groups = await apiCall('zammad_groups');
const activeGroups = groups.filter(g => g.active);
const integrations = await apiCall('integrations');
const zammad = integrations.find(i => i.type === 'zammad');
renderCompanyZammadGroups(activeGroups, zammad?.config?.group_ids || []);
} catch (e) {
alert('Virhe: ' + e.message);
}
});
// Testaa yhteys
document.getElementById('btn-company-zammad-test')?.addEventListener('click', async () => {
const result = document.getElementById('company-zammad-result');
result.style.display = 'block';
result.style.background = '#f8f9fb';
result.textContent = 'Testataan...';
try {
await saveCompanyZammad();
const res = await apiCall('integration_test', 'POST', { type: 'zammad' });
result.style.background = '#d4edda';
result.textContent = `✅ Yhteys OK! Käyttäjä: ${res.user}, Ryhmiä: ${res.groups}`;
} catch (e) {
result.style.background = '#f8d7da';
result.textContent = '❌ ' + e.message;
}
});
// Synkronoi
document.getElementById('btn-company-zammad-sync')?.addEventListener('click', async () => {
const result = document.getElementById('company-zammad-result');
result.style.display = 'block';
result.style.background = '#f8f9fb';
result.innerHTML = '⏳ Synkronoidaan...';
try {
await saveCompanyZammad();
const res = await apiCall('zammad_sync', 'POST', {});
result.style.background = '#d4edda';
result.innerHTML = `✅ Synkronointi valmis!<br>Tikettejä: <strong>${res.tickets_found}</strong> | Uusia: <strong>${res.created}</strong> | Päivitetty: <strong>${res.updated}</strong> | Viestejä: <strong>${res.messages_added}</strong>`;
} catch (e) {
result.style.background = '#f8d7da';
result.innerHTML = '❌ ' + e.message;
}
});
document.getElementById('btn-company-back').addEventListener('click', () => {
// Vaihda takaisin alkuperäiseen yritykseen
if (currentCompany) apiCall('company_switch', 'POST', { company_id: currentCompany.id });