<?php
# Author: Michael Langevin
/**
 * MIT License
 * 
 * Copyright (c) 2025 Michael Langevin
 * 
 * Permission is hereby granted, free of charge, to any person obtaining a copy
 * of this software and associated documentation files (the "Software"), to deal
 * in the Software without restriction, including without limitation the rights
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 * copies of the Software, and to permit persons to whom the Software is
 * furnished to do so, subject to the following conditions:
 * 
 * The above copyright notice and this permission notice shall be included in all
 * copies or substantial portions of the Software.
 * 
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 * SOFTWARE.
 */

// INSERT API KEY and SERVER on LINE 57 and 58 
// INSERT API KEY and SERVER on LINE 57 and 58 
// INSERT API KEY and SERVER on LINE 57 and 58 
// INSERT API KEY and SERVER on LINE 57 and 58 
// INSERT API KEY and SERVER on LINE 57 and 58 

require_once(dirname(__FILE__) . '/../../common.inc.php');

pre_init();
init_session();
grab_request_vars();
check_prereqs();
check_authentication(false);
check_nagios_session_protector();

$mode = grab_request_var('mode', '');
$container = grab_request_var('container', 'cryptograph_container');

// sanitize container for safe HTML id usage
$container = preg_replace('/[^a-zA-Z0-9_\-]/', '_', $container);

// --- Nagios XI API key and base URL ---
// INSERT YOUR API KEY AND SERVER IP HERE
$apiKey = "YOUR_API_KEY";
$baseUrl = "http://(YOUR_SERVER_IP)/nagiosxi/api/v1/objects";
//
//

if ($mode === "getcontent") {

    // safely inject PHP values into JS using json_encode
    $js_apiKey = json_encode($apiKey);
    $js_baseUrl = json_encode($baseUrl);
    $js_container = json_encode($container);

    echo "<div id='{$container}_wrapper' style='
        font-family:Arial, sans-serif;
        padding:10px;
        background:#111;
        color:#fff;
        border-radius:6px;
        text-align:center;
        width:100%;
        height:100%;
        box-sizing:border-box;
        display:flex;
        flex-direction:column;
    '>
        <h3 style='margin:0 0 10px 0;'>Crypto vs Nagios Alerts</h3>

        <div style='margin-bottom:6px;'>
            <button onclick=\"loadCryptoData_{$container}('warning')\" style='margin-right:6px; padding:4px 8px; background:#222; color:#ffcc00; border:none; border-radius:4px; cursor:pointer;'>⚠️ Warning</button>
            <button onclick=\"loadCryptoData_{$container}('critical')\" style='padding:4px 8px; background:#222; color:#ff4444; border:none; border-radius:4px; cursor:pointer;'>🔥 Critical</button>
        </div>

        <div style='margin-bottom:10px;'>
            <button onclick=\"setCrypto_{$container}('BTC')\" style='margin-right:6px; padding:4px 8px; background:#333; color:#fff; border:none; border-radius:4px;'>BTC</button>
            <button onclick=\"setCrypto_{$container}('ETH')\" style='margin-right:6px; padding:4px 8px; background:#333; color:#fff; border:none; border-radius:4px;'>ETH</button>
            <button onclick=\"setCrypto_{$container}('DOGE')\" style='padding:4px 8px; background:#333; color:#fff; border:none; border-radius:4px;'>DOGE</button>
        </div>

        <div id='{$container}_indicators' style='display:flex; justify-content:center; gap:20px; margin-bottom:10px;'>
            <div style='background:#222; padding:8px 12px; border-radius:6px;'>💰 <b id='{$container}_price'>--</b> <span style='color:#888;'>USD</span></div>
            <div style='background:#222; padding:8px 12px; border-radius:6px;'>🚨 <b id='{$container}_alerts'>--</b> <span style='color:#888;'>Active Alerts</span></div>
        </div>

        <div id='{$container}_chart_container' style='flex:1; position:relative; width:100%; min-height:220px;'>
            <canvas id='{$container}_chart' style='width:100%; height:100%;'></canvas>
        </div>

        <div id='{$container}_status' style='font-size:12px; color:#aaa; margin-top:8px;'>Fetching data...</div>
    </div>";

    // include Chart.js
    echo "<script src='https://cdn.jsdelivr.net/npm/chart.js'></script>";

    // JS block
    echo "
<script type='text/javascript'>
// injected safe values
const _dashlet_apiKey = {$js_apiKey};
const _dashlet_baseUrl = {$js_baseUrl};
const _dashlet_container = {$js_container};

let cryptoChart_" . $container . " = null;
let currentSymbol_" . $container . " = 'BTC';
let currentAlertType_" . $container . " = 'critical'; // default view

function setCrypto_" . $container . "(symbol) {
    currentSymbol_" . $container . " = symbol;
    loadCryptoData_" . $container . "(currentAlertType_" . $container . ");
}

async function fetchJson_" . $container . "(url) {
    const res = await fetch(url, { credentials: 'same-origin' });
    if (!res.ok) throw new Error('HTTP ' + res.status + ' for ' + url);
    const text = await res.text();
    // detect HTML (login page, error page) vs JSON
    if (text.trim().startsWith('<')) throw new Error('HTML returned instead of JSON: ' + url);
    try {
        return JSON.parse(text);
    } catch (e) {
        throw new Error('Invalid JSON from ' + url + ': ' + e.message);
    }
}

async function loadCryptoData_" . $container . "(alertType = 'warning') {
    currentAlertType_" . $container . " = alertType;
    const status = document.getElementById(_dashlet_container + '_status');
    const ctx = document.getElementById(_dashlet_container + '_chart').getContext('2d');
    const priceDisplay = document.getElementById(_dashlet_container + '_price');
    const alertsDisplay = document.getElementById(_dashlet_container + '_alerts');

    status.innerHTML = 'Loading ' + currentSymbol_" . $container . " + ' and ' + alertType + ' alerts...';

    try {
        // --- 1) Crypto prices (30 days) ---
        const cryptoRes = await fetch('https://min-api.cryptocompare.com/data/v2/histoday?fsym=' + encodeURIComponent(currentSymbol_" . $container . ") + '&tsym=USD&limit=30');
        if (!cryptoRes.ok) throw new Error('Crypto API HTTP ' + cryptoRes.status);
        const cryptoObj = await cryptoRes.json();
        if (!cryptoObj?.Data?.Data) throw new Error('Invalid crypto API response');
        const cryptoData = cryptoObj.Data.Data;
        const prices = cryptoData.map(p => (typeof p.close === 'number' ? p.close : Number(p.close || 0)));
        // use ISO YYYY-MM-DD keys for alignment
        const datesIso = cryptoData.map(p => {
            const d = new Date(p.time * 1000);
            return d.toISOString().split('T')[0];
        });
        priceDisplay.innerText = (prices[prices.length - 1] || 0).toFixed(2);

        // --- 2) Nagios service alerts ---
        const stateCode = alertType === 'warning' ? 1 : 2;
        const svcUrl = _dashlet_baseUrl + '/servicestatus?apikey=' + encodeURIComponent(_dashlet_apiKey) + '&current_state=' + encodeURIComponent(stateCode) + '&records=5000';
        const svcJson = await fetchJson_" . $container . "(svcUrl);
        const services = svcJson?.servicestatus || [];

        // keep total active services count
        const totalActive = services.length;
        alertsDisplay.innerText = totalActive;

        // --- 3) Build first-appearance counts by date (YYYY-MM-DD) ---
        const alertCounts = {};
        datesIso.forEach(d => alertCounts[d] = 0);

        services.forEach(svc => {
            // prefer last_state_change; fallback to last_time_* fields if needed
            let firstDate = null;
            if (svc.last_state_change && svc.last_state_change.length >= 10) {
                firstDate = svc.last_state_change.substring(0,10);
            } else if (svc.last_time_critical && svc.last_time_critical.length >= 10) {
                firstDate = svc.last_time_critical.substring(0,10);
            } else if (svc.status_update_time && svc.status_update_time.length >= 10) {
                firstDate = svc.status_update_time.substring(0,10);
            }
            if (firstDate && alertCounts[firstDate] !== undefined) alertCounts[firstDate]++;
        });

        const alertSeries = datesIso.map(d => alertCounts[d] || 0);

        // --- 4) Create tooltip extra info mapping (services by first date) ---
        const servicesByDay = {};
        datesIso.forEach(d => servicesByDay[d] = []);
        services.forEach(svc => {
            let firstDate = null;
            if (svc.last_state_change && svc.last_state_change.length >= 10) firstDate = svc.last_state_change.substring(0,10);
            else if (svc.last_time_critical && svc.last_time_critical.length >= 10) firstDate = svc.last_time_critical.substring(0,10);
            else if (svc.status_update_time && svc.status_update_time.length >= 10) firstDate = svc.status_update_time.substring(0,10);
            if (firstDate && servicesByDay[firstDate] !== undefined) {
                // push a short label: host - service
                servicesByDay[firstDate].push((svc.host_name||'') + ' :: ' + (svc.service_description||svc.display_name||''));
            }
        });

        // --- 5) Render Chart ---
        if (cryptoChart_" . $container . ") cryptoChart_" . $container . ".destroy();

        cryptoChart_" . $container . " = new Chart(ctx, {
            type: 'line',
            data: {
                labels: datesIso.map(d => (new Date(d)).toLocaleDateString()),
                datasets: [
                    {
                        label: currentSymbol_" . $container . " + ' / USD',
                        data: prices,
                        borderColor: '#00ff99',
                        backgroundColor: 'rgba(0,255,153,0.12)',
                        tension: 0.2,
                        fill: true,
                        yAxisID: 'y1'
                    },
                    {
                        label: 'First-Time ' + alertType.toUpperCase() + ' Alerts',
                        data: alertSeries,
                        borderColor: alertType === 'warning' ? '#ffcc00' : '#ff4444',
                        borderDash: [5,5],
                        tension: 0.2,
                        yAxisID: 'y2'
                    }
                ]
            },
            options: {
                responsive: true,
                maintainAspectRatio: false,
                interaction: { mode: 'index', intersect: false },
                plugins: {
                    legend: { labels: { color: '#fff' } },
                    tooltip: {
                        callbacks: {
                            title: function(titleItems) {
                                // title contains label (localized date)
                                return titleItems[0].label || '';
                            },
                            afterBody: function(tooltipItems) {
                                // show which services started that day
                                // tooltipItems[0].dataIndex matches our iso dates index
                                if (!tooltipItems || !tooltipItems.length) return '';
                                const idx = tooltipItems[0].dataIndex;
                                const iso = datesIso[idx];
                                const list = servicesByDay[iso] || [];
                                if (list.length === 0) return 'No first-time alerts';
                                // show up to 8 entries then indicate more
                                const maxShow = 8;
                                const show = list.slice(0, maxShow).map(s => '• ' + s).join('\\n');
                                const extra = list.length > maxShow ? '\\n…+' + (list.length - maxShow) + ' more' : '';
                                return show + extra;
                            },
                            label: function(context) {
                                return context.dataset.label + ': ' + context.parsed.y;
                            }
                        }
                    }
                },
                scales: {
                    x: { ticks: { color: '#aaa' }, grid: { color: '#222' } },
                    y1: { ticks: { color: '#00ff99' }, grid: { color: '#222' } },
                    y2: { position: 'right', ticks: { color: alertType === 'warning' ? '#ffcc00' : '#ff4444' }, grid: { drawOnChartArea: false } }
                }
            }
        });

        status.innerText = '✅ Updated: ' + new Date().toLocaleTimeString() + ' — ' + totalActive + ' active ' + alertType.toUpperCase() + ' service alerts.';
    } catch (err) {
        console.error(err);
        status.innerText = '⚠️ Error loading data: ' + err.message;
        // keep counters readable
        try { document.getElementById(_dashlet_container + '_price').innerText = '--'; } catch(e){}
        try { document.getElementById(_dashlet_container + '_alerts').innerText = '0'; } catch(e){}
    }
}

// resize & initial load
window.addEventListener('resize', () => {
    if (cryptoChart_" . $container . ") cryptoChart_" . $container . ".resize();
});
loadCryptoData_" . $container . "('critical');
// auto-refresh every 60s
setInterval(() => loadCryptoData_" . $container . "(currentAlertType_" . $container . "), 60000);

</script>
";
} // end getcontent
?>
