Saatavuuskyselyt: IP-organisaatio, siirrä API-tabiin + sähköpostien formatointi + tikettiviestivärit
- Lisää IP-organisaatio/ISP-kenttä saatavuuskyselyihin (ip-api.com haku) - Siirrä saatavuuskyselyt-taulukko Asiakkaat-tabista API-asetussivulle - Korjaa rivinvaihdot ja välilyönnit Zammad-sähköpostivastauksissa (white-space:pre-wrap) - Korjaa quoted thread: plain-text viestit muunnetaan HTML:ksi oikein - Tikettiviestiketjun värit selkeämmiksi (sininen=saapuva, vihreä=lähtevä) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
28
api.php
28
api.php
@@ -1227,10 +1227,21 @@ switch ($action) {
|
|||||||
if (!$exists) {
|
if (!$exists) {
|
||||||
$ip = getClientIp();
|
$ip = getClientIp();
|
||||||
$hostname = @gethostbyaddr($ip) ?: '';
|
$hostname = @gethostbyaddr($ip) ?: '';
|
||||||
if ($hostname === $ip) $hostname = ''; // gethostbyaddr palauttaa IP:n jos ei löydy
|
if ($hostname === $ip) $hostname = '';
|
||||||
|
// Hae IP-osoitteen organisaatio/ISP ip-api.com:sta
|
||||||
|
$org = '';
|
||||||
|
try {
|
||||||
|
$ipApiUrl = "http://ip-api.com/json/{$ip}?fields=org,isp,as";
|
||||||
|
$ctx = stream_context_create(['http' => ['timeout' => 3]]);
|
||||||
|
$ipJson = @file_get_contents($ipApiUrl, false, $ctx);
|
||||||
|
if ($ipJson) {
|
||||||
|
$ipData = json_decode($ipJson, true);
|
||||||
|
$org = $ipData['org'] ?? $ipData['isp'] ?? '';
|
||||||
|
}
|
||||||
|
} catch (\Throwable $e) { /* IP-haku ei saa kaataa API:a */ }
|
||||||
_dbExecute(
|
_dbExecute(
|
||||||
"INSERT INTO availability_queries (company_id, osoite, postinumero, kaupunki, saatavilla, ip_address, hostname, user_agent, referer, created_at)
|
"INSERT INTO availability_queries (company_id, osoite, postinumero, kaupunki, saatavilla, ip_address, hostname, org, user_agent, referer, created_at)
|
||||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
|
||||||
[
|
[
|
||||||
$matchedCompany['id'],
|
$matchedCompany['id'],
|
||||||
$rawOsoite,
|
$rawOsoite,
|
||||||
@@ -1239,6 +1250,7 @@ switch ($action) {
|
|||||||
$found ? 1 : 0,
|
$found ? 1 : 0,
|
||||||
$ip,
|
$ip,
|
||||||
$hostname,
|
$hostname,
|
||||||
|
$org,
|
||||||
substr($_SERVER['HTTP_USER_AGENT'] ?? '', 0, 500),
|
substr($_SERVER['HTTP_USER_AGENT'] ?? '', 0, 500),
|
||||||
substr($_SERVER['HTTP_REFERER'] ?? '', 0, 500),
|
substr($_SERVER['HTTP_REFERER'] ?? '', 0, 500),
|
||||||
date('Y-m-d H:i:s'),
|
date('Y-m-d H:i:s'),
|
||||||
@@ -1267,7 +1279,7 @@ switch ($action) {
|
|||||||
$total = (int)_dbFetchScalar("SELECT COUNT(*) FROM availability_queries WHERE company_id IN ($placeholders)", $userCompanyIds);
|
$total = (int)_dbFetchScalar("SELECT COUNT(*) FROM availability_queries WHERE company_id IN ($placeholders)", $userCompanyIds);
|
||||||
$params = array_merge($userCompanyIds, [$limit, $offset]);
|
$params = array_merge($userCompanyIds, [$limit, $offset]);
|
||||||
$rows = _dbFetchAll(
|
$rows = _dbFetchAll(
|
||||||
"SELECT aq.id, aq.company_id, c.nimi as company_nimi, aq.osoite, aq.postinumero, aq.kaupunki, aq.saatavilla, aq.ip_address, aq.hostname, aq.referer, aq.created_at
|
"SELECT aq.id, aq.company_id, c.nimi as company_nimi, aq.osoite, aq.postinumero, aq.kaupunki, aq.saatavilla, aq.ip_address, aq.hostname, aq.org, aq.referer, aq.created_at
|
||||||
FROM availability_queries aq LEFT JOIN companies c ON c.id = aq.company_id
|
FROM availability_queries aq LEFT JOIN companies c ON c.id = aq.company_id
|
||||||
WHERE aq.company_id IN ($placeholders) ORDER BY aq.created_at DESC LIMIT ? OFFSET ?",
|
WHERE aq.company_id IN ($placeholders) ORDER BY aq.created_at DESC LIMIT ? OFFSET ?",
|
||||||
$params
|
$params
|
||||||
@@ -5398,8 +5410,8 @@ switch ($action) {
|
|||||||
$to = !empty($input['to']) ? trim($input['to']) : ($ticket['from_email'] ?? '');
|
$to = !empty($input['to']) ? trim($input['to']) : ($ticket['from_email'] ?? '');
|
||||||
$cc = !empty($input['cc']) ? trim($input['cc']) : '';
|
$cc = !empty($input['cc']) ? trim($input['cc']) : '';
|
||||||
|
|
||||||
// Muunna uusi viesti HTML:ksi
|
// Muunna uusi viesti HTML:ksi (säilytä rivinvaihdot ja välilyönnit)
|
||||||
$newMsgHtml = nl2br(htmlspecialchars($body, ENT_QUOTES, 'UTF-8'));
|
$newMsgHtml = '<div style="white-space:pre-wrap;">' . htmlspecialchars($body, ENT_QUOTES, 'UTF-8') . '</div>';
|
||||||
|
|
||||||
// Rakenna viestiketju (quoted thread) vastaukseen
|
// Rakenna viestiketju (quoted thread) vastaukseen
|
||||||
$messages = _dbFetchAll(
|
$messages = _dbFetchAll(
|
||||||
@@ -5411,6 +5423,10 @@ switch ($action) {
|
|||||||
$sender = $msg['from_name'] ?: $msg['from_email'];
|
$sender = $msg['from_name'] ?: $msg['from_email'];
|
||||||
$date = date('d.m.Y H:i', strtotime($msg['timestamp']));
|
$date = date('d.m.Y H:i', strtotime($msg['timestamp']));
|
||||||
$msgBody = $msg['body'] ?: '';
|
$msgBody = $msg['body'] ?: '';
|
||||||
|
// Jos viesti on plain text (ei HTML-tageja), muunna HTML:ksi
|
||||||
|
if ($msgBody !== '' && strip_tags($msgBody) === $msgBody) {
|
||||||
|
$msgBody = '<div style="white-space:pre-wrap;">' . htmlspecialchars($msgBody, ENT_QUOTES, 'UTF-8') . '</div>';
|
||||||
|
}
|
||||||
$quotedThread .= '<br><div style="padding-left:0.5em;border-left:2px solid #ccc;color:#555;">'
|
$quotedThread .= '<br><div style="padding-left:0.5em;border-left:2px solid #ccc;color:#555;">'
|
||||||
. '<small><strong>' . htmlspecialchars($sender) . '</strong> — ' . $date . '</small><br>'
|
. '<small><strong>' . htmlspecialchars($sender) . '</strong> — ' . $date . '</small><br>'
|
||||||
. '<div>' . $msgBody . '</div>'
|
. '<div>' . $msgBody . '</div>'
|
||||||
|
|||||||
1
db.php
1
db.php
@@ -676,6 +676,7 @@ function initDatabase(): void {
|
|||||||
"ALTER TABLE tickets ADD COLUMN zammad_ticket_id INT DEFAULT NULL AFTER mailbox_id",
|
"ALTER TABLE tickets ADD COLUMN zammad_ticket_id INT DEFAULT NULL AFTER mailbox_id",
|
||||||
"ALTER TABLE ticket_messages ADD COLUMN zammad_article_id INT DEFAULT NULL AFTER message_id",
|
"ALTER TABLE ticket_messages ADD COLUMN zammad_article_id INT DEFAULT NULL AFTER message_id",
|
||||||
"ALTER TABLE availability_queries ADD COLUMN hostname VARCHAR(255) DEFAULT '' AFTER ip_address",
|
"ALTER TABLE availability_queries ADD COLUMN hostname VARCHAR(255) DEFAULT '' AFTER ip_address",
|
||||||
|
"ALTER TABLE availability_queries ADD COLUMN org VARCHAR(255) DEFAULT '' AFTER hostname",
|
||||||
];
|
];
|
||||||
foreach ($alters as $sql) {
|
foreach ($alters as $sql) {
|
||||||
try { $db->query($sql); } catch (\Throwable $e) { /* sarake on jo olemassa / jo ajettu */ }
|
try { $db->query($sql); } catch (\Throwable $e) { /* sarake on jo olemassa / jo ajettu */ }
|
||||||
|
|||||||
57
index.html
57
index.html
@@ -4,7 +4,7 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<title>Noxus HUB</title>
|
<title>Noxus HUB</title>
|
||||||
<link rel="stylesheet" href="style.css?v=20260313m">
|
<link rel="stylesheet" href="style.css?v=20260313n">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<!-- Login -->
|
<!-- Login -->
|
||||||
@@ -93,7 +93,6 @@
|
|||||||
<div class="sub-tab-bar" id="customers-sub-tab-bar">
|
<div class="sub-tab-bar" id="customers-sub-tab-bar">
|
||||||
<button class="sub-tab active" data-cust-subtab="customers-list">Asiakkaat</button>
|
<button class="sub-tab active" data-cust-subtab="customers-list">Asiakkaat</button>
|
||||||
<button class="sub-tab" data-cust-subtab="customers-archive">Arkisto</button>
|
<button class="sub-tab" data-cust-subtab="customers-archive">Arkisto</button>
|
||||||
<button class="sub-tab" data-cust-subtab="customers-availability">Saatavuuskyselyt</button>
|
|
||||||
</div>
|
</div>
|
||||||
<div id="subtab-customers-list" class="sub-tab-content active">
|
<div id="subtab-customers-list" class="sub-tab-content active">
|
||||||
<div class="main-container">
|
<div class="main-container">
|
||||||
@@ -191,35 +190,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div><!-- /subtab-customers-archive -->
|
</div><!-- /subtab-customers-archive -->
|
||||||
|
|
||||||
<div id="subtab-customers-availability" class="sub-tab-content">
|
|
||||||
<div class="main-container">
|
|
||||||
<div class="table-card" style="padding:1.5rem;">
|
|
||||||
<div style="display:flex;justify-content:space-between;align-items:center;margin-bottom:1rem;">
|
|
||||||
<div>
|
|
||||||
<h3 style="margin:0;color:#0f3460;">Saatavuuskyselyt</h3>
|
|
||||||
<p style="color:#888;font-size:0.85rem;margin:0.25rem 0 0;">Nettisivujen kautta tehdyt saatavuustarkistukset</p>
|
|
||||||
</div>
|
|
||||||
<span id="availability-query-count" style="color:#888;font-size:0.85rem;"></span>
|
|
||||||
</div>
|
|
||||||
<table id="availability-queries-table" class="data-table">
|
|
||||||
<thead>
|
|
||||||
<tr>
|
|
||||||
<th>Aika</th>
|
|
||||||
<th>Osoite</th>
|
|
||||||
<th>Postinumero</th>
|
|
||||||
<th>Kaupunki</th>
|
|
||||||
<th>Tulos</th>
|
|
||||||
<th>IP / Verkko</th>
|
|
||||||
<th>Lähde</th>
|
|
||||||
<th>Yritys</th>
|
|
||||||
</tr>
|
|
||||||
</thead>
|
|
||||||
<tbody></tbody>
|
|
||||||
</table>
|
|
||||||
<div id="availability-pagination" style="margin-top:1rem;display:flex;justify-content:center;gap:0.5rem;"></div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</div><!-- /subtab-customers-availability -->
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Tab: Liidit -->
|
<!-- Tab: Liidit -->
|
||||||
@@ -1556,6 +1526,29 @@
|
|||||||
<button class="btn-primary" id="btn-test-api">Testaa</button>
|
<button class="btn-primary" id="btn-test-api">Testaa</button>
|
||||||
</div>
|
</div>
|
||||||
<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>
|
<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>
|
||||||
|
|
||||||
|
<h3 style="color:#0f3460;margin:1.5rem 0 1rem;border-bottom:2px solid #f0f2f5;padding-bottom:0.5rem;">Saatavuuskyselyt</h3>
|
||||||
|
<p style="color:#888;font-size:0.85rem;margin-bottom:0.75rem;">Nettisivujen kautta tehdyt saatavuustarkistukset</p>
|
||||||
|
<span id="availability-query-count" style="color:#888;font-size:0.85rem;display:block;margin-bottom:0.5rem;"></span>
|
||||||
|
<div style="overflow-x:auto;">
|
||||||
|
<table id="availability-queries-table" class="data-table">
|
||||||
|
<thead>
|
||||||
|
<tr>
|
||||||
|
<th>Aika</th>
|
||||||
|
<th>Osoite</th>
|
||||||
|
<th>Postinumero</th>
|
||||||
|
<th>Kaupunki</th>
|
||||||
|
<th>Tulos</th>
|
||||||
|
<th>IP / Verkko</th>
|
||||||
|
<th>Organisaatio</th>
|
||||||
|
<th>Lähde</th>
|
||||||
|
<th>Yritys</th>
|
||||||
|
</tr>
|
||||||
|
</thead>
|
||||||
|
<tbody></tbody>
|
||||||
|
</table>
|
||||||
|
</div>
|
||||||
|
<div id="availability-pagination" style="margin-top:1rem;display:flex;justify-content:center;gap:0.5rem;"></div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Telegram-asetukset -->
|
<!-- Telegram-asetukset -->
|
||||||
@@ -2264,6 +2257,6 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script src="script.js?v=20260313m"></script>
|
<script src="script.js?v=20260313n"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
@@ -281,8 +281,6 @@ function switchToTab(target, subTab) {
|
|||||||
loadCustomers();
|
loadCustomers();
|
||||||
if (subTab === 'archive') {
|
if (subTab === 'archive') {
|
||||||
switchCustomerSubTab('customers-archive');
|
switchCustomerSubTab('customers-archive');
|
||||||
} else if (subTab === 'availability') {
|
|
||||||
switchCustomerSubTab('customers-availability');
|
|
||||||
} else {
|
} else {
|
||||||
switchCustomerSubTab('customers-list');
|
switchCustomerSubTab('customers-list');
|
||||||
}
|
}
|
||||||
@@ -2570,6 +2568,9 @@ async function loadSettings() {
|
|||||||
document.getElementById('settings-telegram-chat').value = config.telegram_chat_id || '';
|
document.getElementById('settings-telegram-chat').value = config.telegram_chat_id || '';
|
||||||
} catch (e) { console.error(e); }
|
} catch (e) { console.error(e); }
|
||||||
|
|
||||||
|
// Lataa saatavuuskyselyt
|
||||||
|
loadAvailabilityQueries();
|
||||||
|
|
||||||
// Näytä API-sivun kortit integraatioiden perusteella
|
// Näytä API-sivun kortit integraatioiden perusteella
|
||||||
try {
|
try {
|
||||||
const integs = await apiCall('integrations');
|
const integs = await apiCall('integrations');
|
||||||
@@ -3497,7 +3498,6 @@ function switchCustomerSubTab(target) {
|
|||||||
const content = document.getElementById('subtab-' + target);
|
const content = document.getElementById('subtab-' + target);
|
||||||
if (content) content.classList.add('active');
|
if (content) content.classList.add('active');
|
||||||
if (target === 'customers-archive') { loadArchive(); window.location.hash = 'customers/archive'; }
|
if (target === 'customers-archive') { loadArchive(); window.location.hash = 'customers/archive'; }
|
||||||
else if (target === 'customers-availability') { loadAvailabilityQueries(); window.location.hash = 'customers/availability'; }
|
|
||||||
else { window.location.hash = 'customers'; }
|
else { window.location.hash = 'customers'; }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3520,7 +3520,7 @@ async function loadAvailabilityQueries(page = 0) {
|
|||||||
countEl.textContent = `Yhteensä ${data.total} kyselyä`;
|
countEl.textContent = `Yhteensä ${data.total} kyselyä`;
|
||||||
|
|
||||||
if (data.queries.length === 0) {
|
if (data.queries.length === 0) {
|
||||||
tbody.innerHTML = '<tr><td colspan="8" style="text-align:center;color:#888;padding:2rem;">Ei vielä kyselyjä</td></tr>';
|
tbody.innerHTML = '<tr><td colspan="9" style="text-align:center;color:#888;padding:2rem;">Ei vielä kyselyjä</td></tr>';
|
||||||
} else {
|
} else {
|
||||||
tbody.innerHTML = data.queries.map(q => {
|
tbody.innerHTML = data.queries.map(q => {
|
||||||
const date = q.created_at ? q.created_at.replace('T', ' ').substring(0, 16) : '';
|
const date = q.created_at ? q.created_at.replace('T', ' ').substring(0, 16) : '';
|
||||||
@@ -3542,6 +3542,7 @@ async function loadAvailabilityQueries(page = 0) {
|
|||||||
<td>${esc(q.kaupunki)}</td>
|
<td>${esc(q.kaupunki)}</td>
|
||||||
<td>${badge}</td>
|
<td>${badge}</td>
|
||||||
<td style="font-size:0.8rem;">${ipInfo}</td>
|
<td style="font-size:0.8rem;">${ipInfo}</td>
|
||||||
|
<td style="font-size:0.8rem;">${esc(q.org || '')}</td>
|
||||||
<td style="font-size:0.8rem;color:#888;">${esc(source)}</td>
|
<td style="font-size:0.8rem;color:#888;">${esc(source)}</td>
|
||||||
<td style="font-size:0.8rem;color:#888;">${esc(q.company_nimi || q.company_id || '')}</td>
|
<td style="font-size:0.8rem;color:#888;">${esc(q.company_nimi || q.company_id || '')}</td>
|
||||||
</tr>`;
|
</tr>`;
|
||||||
|
|||||||
@@ -1432,13 +1432,13 @@ span.empty {
|
|||||||
}
|
}
|
||||||
|
|
||||||
.ticket-msg-in {
|
.ticket-msg-in {
|
||||||
background: #f0f7ff;
|
background: #e3f0fc;
|
||||||
border-left: 3px solid #3498db;
|
border-left: 4px solid #2980d9;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ticket-msg-out {
|
.ticket-msg-out {
|
||||||
background: #eafaf1;
|
background: #e0f5e4;
|
||||||
border-left: 3px solid #2ecc71;
|
border-left: 4px solid #27ae60;
|
||||||
}
|
}
|
||||||
|
|
||||||
.ticket-msg-note {
|
.ticket-msg-note {
|
||||||
|
|||||||
Reference in New Issue
Block a user