<?php
//
// Gauge Dashlet
//
// Copyright (c) 2013-2025 Nagios Enterprises, LLC.
//
// LICENSE:
//
// Except where explicitly superseded by other restrictions or licenses, permission
// is hereby granted to the end user of this software to use, modify and create
// derivative works or this software under the terms of the Nagios Software License, 
// which can be found online at:
//
// https://www.nagios.com/legal/licenses/
//

include_once(dirname(__FILE__) . '/../dashlethelper.inc.php');

gauges_enhanced_dashlet_init();

function gauges_enhanced_dashlet_init()
{
    $name = "gauges_enhanced";

    $args = array(
        DASHLET_NAME => $name,
        DASHLET_VERSION => "2.0.0",
        DASHLET_AUTHOR => "Nagios Enterprises, LLC",
        DASHLET_DESCRIPTION => _("Displays gauges."),
        DASHLET_FILTER_GROUPS => array("metrics"),
        DASHLET_COPYRIGHT => "Copyright (c) 2013-2025 Nagios Enterprises",
        DASHLET_HOMEPAGE => "https://www.nagios.com",
        DASHLET_FUNCTION => "gauges_enhanced_dashlet_func", // the function name to call
        DASHLET_TITLE => _("Gauge Dashlet"), // title used in the dashlet
        DASHLET_OUTBOARD_CLASS => "gauges_enhanced_outboardclass", // used when the dashlet is embedded in a non-dashboard page
        DASHLET_INBOARD_CLASS => "gauges_enhanced_inboardclass", // used when the dashlet is on a dashboard
        DASHLET_PREVIEW_CLASS => "gauges_enhanced_previewclass", // used in the "Available Dashlets screen of Nagios XI
        DASHLET_JS_FILE => "js/gauge.js", // optional Javascript file to cinlude
        DASHLET_WIDTH => "260", // default size of the dashlet when first placed on the dashboard
        DASHLET_HEIGHT => "290",
        DASHLET_OPACITY => "1", // transparency/opacity of the dashlet (0=invisible,1.0=visible)
        DASHLET_BACKGROUND => "", // background color of the dashlet (optional)
        DASHLET_ISCUSTOM => false // whether or not dashlet is custom-made
    );

    register_dashlet($name, $args);
    register_callback(CALLBACK_PAGE_HEAD, 'gauges_enhanced_component_head_include');
}

/**
 * @param string $cbtype
 * @param null   $args
 *
 * @return bool
 */
function gauges_enhanced_component_head_include($cbtype = '', $args = null)
{
    echo '
    <style>
        :root {
            --gauge-enhanced-foreground-color: var(--card-foreground, var(--foreground));
        }
        
        .gauge-enhanced-container {
            align-items: center;
            box-sizing: border-box;
            display: flex;
            height: 100%;
            justify-content: center;
            overflow: hidden;
            padding: 0;
            margin: 0;
            width: 100%;
        }
        .gauges_enhanced_dashlet {
            height: 100%;
        }
    </style>
    ';
    return true;
}

// Dashlet function
// This gets called at various points by Nagios XI.  The $mode argument will be different, depending on what XI is asking of the dashlet
/**
 * @param string $mode
 * @param string $id
 * @param null   $args
 *
 * @return string
 */
function gauges_enhanced_dashlet_func($mode = DASHLET_MODE_PREVIEW, $id = "", $args = null)
{
    $output = "";
    $imgbase = get_dashlet_url_base("gauges_enhanced") . "/images/"; // the relative URL base for the "images" subfolder for the dashlet

    switch ($mode) {

        // the dashlet is being configured
        // add optional form arguments (text boxes, dropdown lists, etc.) to capture data here
        case DASHLET_MODE_GETCONFIGHTML:
            if ($args == null) {
                $output = '<script type="text/javascript">load_gauge_hosts_enhanced();</script>';
                $output .= '<div class="popup-form-box"><label>' . _('Host') . '</label>
                            <div><select id="gauges_enhanced_form_name" class="form-control" name="host" onchange="getGaugesEnhancedJson()">
                                    <option selected></option>';
                $output .= '</select> <i class="fa fa-spinner fa-spin fa-14 hide host-loader" title="'._('Loading').'"></i></div></div>';
                $output .= '<div class="popup-form-box"><label>' . _('Services') . '</label>
                                <div id="gauges_enhanced_services">
                                    <select id="gauges_enhanced_form_services" class="form-control" name="service" onchange="getGaugesEnhancedServices()" disabled>
                                        <option selected></option>
                                    </select> <i class="fa fa-spinner fa-spin fa-14 hide service-loader" title="'._('Loading').'"></i>
                                    <div id="empty-services" class="hide">'._("No services found").'</div>
                                </div>
                            </div>';
                $output .= '<div class="popup-form-box"><label>' . _('Datasource') . '</label>
                                <div id="gauges_enhanced_datasource">
                                    <select id="gauges_enhanced_form_ds" class="form-control" name="ds" disabled>
                                        <option selected></option>
                                    </select> <i class="fa fa-spinner fa-spin fa-14 hide ds-loader" title="'._('Loading').'"></i>
                                    <div id="empty-ds" class="hide">'._("No data sources found").'</div>
                                </div>
                            </div>';
                $output .= '';
            }
            break;

        // for this example, we display the sample output whether we're on a dashboard or on a normal (non-dashboard) page
        case DASHLET_MODE_OUTBOARD:
        case DASHLET_MODE_INBOARD:

            $output = "";
            if (empty(grab_array_var($args, 'ds'))) {
                $output .= "ERROR: Missing Arguments- Data source was not provided";
                break;
            }
            $id = "gauges_" . random_string(6); // a random ID to assign to the <div> tag that wraps the output, so the sample dashlet can appear multiple times on the sample dashboard

            // ajax updater args
            $ajaxargs = is_array($args) ? $args : array();

            // build args for javascript as a safe JSON object
            $jargs = json_encode((object)$ajaxargs);

            // here we output some HTML that contains a <div> that gets updated via ajax every 5 seconds...

            $output .= '
			<div class="gauges_enhanced_dashlet" id="' . $id . '">
			<div class="infotable_title">'._('Gauge').'</div>
			' . get_throbber_html() . '
			</div>

			<script type="text/javascript">
			$(document).ready(function() {
			
				get_' . $id . '_content();
				
				function get_' . $id . '_content(height, width) {
                    var args = ' . $jargs . '; 

                    if (height == undefined) { var height = $("#' . $id . '").parent().height(); }
                    if (width == undefined) { var width = $("#' . $id . '").parent().width(); }

                    args.height = height;
                    args.width = width;

					$("#' . $id . '").each(function() {
						var optsarr = {
							"func": "get_gauges_enhanced_dashlet_html",
							"args": args
						}
						var opts = JSON.stringify(optsarr);
						get_ajax_data_innerHTML("getxicoreajax", opts, true, this);
				    });
				}

                // Re-build the content when we resize
                $("#' . $id . '").closest(".ui-resizable").on("resizestop", function(e, ui) {
                    var height = ui.size.height;
                    var width = ui.size.width;
                    get_' . $id . '_content(height, width);
                });

			});
			</script>
			';

            break;

        // dashlet is in "preview" mode
        // it is being shown either under the Admin menu, or under the "Available Dashlets" screen
        case DASHLET_MODE_PREVIEW:

            if(!is_neptune()) {
                $output = "<img src='" . $imgbase . "preview.png'>";
            } else if (get_theme() == 'neptunelight'){
                $output = "<img src='" . $imgbase . "gauge_neptune_light_preview.png'>";
            } else {
                $output = "<img src='" . $imgbase . "neptune_preview.png'>";
            }
            break;
    }

    return $output;
}

// This is the function that XI calls when the dashlet javascript makes an AJAX call.
// Note how the function name is prepended with 'xicore_ajax_', compared to the function name we used in the javascript code when producing the wrapper <div> tag above
/**
 * @param null $args
 *
 * @return string
 */
function xicore_ajax_get_gauges_enhanced_dashlet_html($args = null)
{
    $host = grab_array_var($args, 'host', '');
    $service = urldecode(grab_array_var($args, 'service', ''));
    $ds = grab_array_var($args, 'ds', '');

    // Dimensions passed by the dashlet resizer (optional)
    $height = intval(grab_array_var($args, 'height', 0));
    $width = intval(grab_array_var($args, 'width', 0));


    $id = "gauges_inner_" . random_string(6);
    $displayed_title = ($service == "_HOST_") ? $host : "$host - $service";
    $style = '';
    if ($height > 0 || $width > 0) {
        $h = $height > 0 ? "height: {$height}px;" : '';
        $w = $width > 0 ? "width: {$width}px;" : '';
        $style = " style='{$h}{$w}'";
    }
    $output = "<div class='infotable_title gauge-enhanced-container' style='padding: 0; margin: 0;' id='$id'$style></div>";

    $url_params = "host=$host&service=$service&ds=$ds";

    $output .= '<script type="text/javascript">
			$(document).ready(function(){
				myShinyEnhancedGauge_' . $id . '_url="' . get_dashlet_url_base("gauges_enhanced") . '/getdata.php?' . $url_params . '"
                
                $.ajax({"url": myShinyEnhancedGauge_' . $id . '_url, dataType: "json",
                    "success": function(result) {
                        window.myShinyEnhancedGauge_' . $id . ' = create_gauge_enhanced("' . $id . '", 1, result)
                        window.myShinyEnhancedGauge_' . $id . '.redraw(result["current"], 500)
                    }
                });

               setInterval(function() {  $.ajax({"url": myShinyEnhancedGauge_' . $id . '_url, dataType: "json",
                    "success": function(result) { 
                        window.myShinyEnhancedGauge_' . $id . '.redraw(result["current"], 500)
                    }
                }); }, ' . get_dashlet_refresh_rate(60, "perfdata_chart") . ');
			});
			</script>';

    return $output;
}

/**
 * @param $host
 * @param $service
 * @param $ds
 *
 * @return bool
 */
function gauges_enhanced_dashlet_gauge_exists($host, $service, $ds)
{

    $result = gauges_enhanced_get_datasources($host, $service, $ds);

    if (count($result) > 0)
        return true;
    return false;
}


/**
 * @return mixed|string
 */
function get_gauge_enhanced_json()
{
    $host = grab_request_var('host', '');
    $service = grab_request_var('service', '');
    $ds = grab_request_var('ds');

    if (empty($host) || empty($service) || empty($ds)) {
        return json_encode(gauges_enhanced_get_datasources($host, $service, $ds));
    }

    $result = gauges_enhanced_get_datasources($host, $service, $ds);
    foreach ($result as $services)
        foreach ($services as $service)
            foreach ($service as $ds)
                return json_encode($ds);
}

function gauges_enhanced_get_host_services()
{
    $host = grab_request_var('host', '');

    $backendargs = array(
        'orderby' => "host_name:a,service_description:a",
        'brevity' => 4);

    if (!empty($host)) {
        $services = array();
        $backendargs['host_name'] = $host;
        $objs = get_xml_service_status($backendargs);
        foreach ($objs->servicestatus as $o) {
            $services[] = strval($o->name);
        }
        return json_encode($services);
    } else {
        $hosts = array();
        $objs = get_xml_host_status($backendargs);
        foreach ($objs->hoststatus as $o) {
            $hosts[] = strval($o->name);
        }
        return json_encode($hosts);
    }
}

/**
 * @param null $host
 * @param null $service
 * @param null $ds
 *
 * @return array
 */
function gauges_enhanced_get_datasources($host = null, $service = null, $ds = null)
{
    $result = array();
    $backendargs = array();

    $backendargs["orderby"] = "host_name:a,service_description:a";
    if ($host)
        $backendargs["host_name"] = $host;
    if ($service)
        $backendargs["service_description"] = $service; // service

    $services = get_xml_service_status($backendargs);
    $hosts = get_xml_host_status($backendargs);

    if (!empty($services)) {
        foreach ($services->servicestatus as $status) {
            $status = (array)$status;
            $result[$status['host_name']][$status['name']] = gauges_enhanced_get_gauge_datasource($status, $ds);
            if (empty($result[$status['host_name']])) {
                unset($result[$status['host_name']]);
            }
        }
        if (empty($service) || $service == '_HOST_') {
            foreach ($hosts->hoststatus as $status) {
                $status = (array)$status;
                $result[$status['name']]['_HOST_'] = gauges_enhanced_get_gauge_datasource($status, $ds);
                if (empty($result[$status['name']])) {
                    unset($result[$status['name']]);
                }
            }
        }
    }
    return $result;
}

/**
 * @param $status
 * @param $ds_label
 *
 * @return array
 */
function gauges_enhanced_get_gauge_datasource($status, $ds_label)
{
    $ds = array();

    if (empty($status['performance_data'])) {
        return '';
    }

    $perfdata_datasources = str_getcsv($status['performance_data'], " ", "'", "\\");
    foreach ($perfdata_datasources as $perfdata_datasource) {

        $perfdata_s = explode('=', $perfdata_datasource);
        $perfdata_name = trim(str_replace("'", "", $perfdata_s[0]));

        // Strip bad char from key name and label (REMOVED for pnp convert function -JO)
        //$perfdata_name = str_replace('\\', '', $perfdata_name);
        //$perfdata_name = str_replace(' ', '_', $perfdata_name);
        $perfdata_name = pnp_convert_object_name($perfdata_name);
        if ($ds_label && $perfdata_name != $ds_label && $perfdata_name != pnp_convert_object_name($ds_label))
            continue;
        if (!isset($perfdata_s[1]))
            continue;
        
        //test=13; "test helo"=3; 

        $ds[$perfdata_name] = array();

        $perfdata = explode(';', $perfdata_s[1]);
        $current = preg_replace("/[^0-9.]/", "", grab_array_var($perfdata, 0, 0));

        $ds[$perfdata_name]['label'] = $perfdata_name;
        $ds[$perfdata_name]['current'] = round(floatval($current), 3);
        $ds[$perfdata_name]['uom'] = str_replace($current, '', $perfdata[0]);
        $ds[$perfdata_name]['warn'] = grab_array_var($perfdata, 1, 0);
        $ds[$perfdata_name]['crit'] = grab_array_var($perfdata, 2, 0);
        $ds[$perfdata_name]['min'] = floatval(grab_array_var($perfdata, 3, "0"));
        $ds[$perfdata_name]['max'] = floatval(grab_array_var($perfdata, 4, "0"));

        // Improved max value detection - percentage metrics should always be 0-100
        if ($ds[$perfdata_name]['uom'] == '%') {
            // Force max=100 for all percentage metrics regardless of performance data
            $ds[$perfdata_name]['max'] = 100;
        } else if ($ds[$perfdata_name]['max'] == 0) {
            // For non-percentage metrics, only calculate if max is missing
            if ($ds[$perfdata_name]['crit'] != 0 && $ds[$perfdata_name]['crit'] > 0) {
                $ds[$perfdata_name]['max'] = $ds[$perfdata_name]['crit'] * 1.1;
            } else {
                // For absolute values, use current value * 1.5 as sensible max
                // This prevents the arbitrary 100 default
                $ds[$perfdata_name]['max'] = max($ds[$perfdata_name]['current'] * 1.5, $ds[$perfdata_name]['current'] + 100);
            }
        }

        $ds[$perfdata_name]['max'] = round($ds[$perfdata_name]['max'], 1);

        // Final fallback
        if ($ds[$perfdata_name]['max'] == 0) {
            if ($ds[$perfdata_name]['uom'] == '%') {
                $ds[$perfdata_name]['max'] = 100;
            } else {
                $ds[$perfdata_name]['max'] = max($ds[$perfdata_name]['current'] * 2, 1);
            }
        }

        // add yellowZones & redZones
        if (!empty($ds[$perfdata_name]['warn'])) {
            if (strpos($ds[$perfdata_name]['warn'], ":") !== false) {
                // We are doing a range warning threshold
                list($end, $start) = explode(":", $ds[$perfdata_name]['warn']);
                $ds[$perfdata_name]['yellowZones'] = array(
                    array(
                        "from" => floatval($start),
                        "to" => floatval($end)
                    )
                );
            } else {
                // Standard warning threshold
                $ds[$perfdata_name]['yellowZones'] = array(
                    array(
                        "from" => floatval($ds[$perfdata_name]['warn']),
                        "to" => ($ds[$perfdata_name]['crit'] != 0) ? floatval($ds[$perfdata_name]['crit']) : $ds[$perfdata_name]['max'],
                    )
                );
            }
        }

        if (!empty($ds[$perfdata_name]['crit'])) {
            if (strpos($ds[$perfdata_name]['crit'], ":") !== false) {
                // We are doing a range warning threshold
                list($end, $start) = explode(":", $ds[$perfdata_name]['crit']);
                $ds[$perfdata_name]['redZones'] = array(
                    array(
                        "from" => floatval($start),
                        "to" => floatval($end)
                    )
                );
            } else {
                // Standard critical threshold
                $ds[$perfdata_name]['redZones'] = array(
                    array(
                        "from" => floatval($ds[$perfdata_name]['crit']),
                        "to" => $ds[$perfdata_name]['max'],
                    )
                );
            }
        }

        // Add greenZones only if both yellowZones and redZones exist
        if (!empty($ds[$perfdata_name]['yellowZones']) && !empty($ds[$perfdata_name]['redZones'])) {
            $ds[$perfdata_name]['greenZones'] = array(
                array(
                    "from" => $ds[$perfdata_name]['min'],
                    "to" => floatval($ds[$perfdata_name]['warn'])
                )
            );
        }
    }

    return $ds;
}

if (!function_exists('str_getcsv')) {

    /**
     * @param        $input
     * @param string $delimiter
     * @param string $enclosure
     * @param null   $escape
     * @param null   $eol
     *
     * @return array
     */
    function str_getcsv($input, $delimiter = ',', $enclosure = '"', $escape = null, $eol = null)
    {
        $temp = fopen("php://memory", "rw");
        fwrite($temp, $input);
        fseek($temp, 0);
        $r = array();
        while (($data = fgetcsv($temp, 4096, $delimiter, $enclosure)) !== false) {
            $r[] = $data;
        }
        fclose($temp);
        return $r;
    }
}
