IPP Software Navigation Tools IPP Links Communication Pan-STARRS Links

Changeset 42972


Ignore:
Timestamp:
Mar 19, 2026, 5:23:27 PM (7 weeks ago)
Author:
cclin33
Message:

update function.php

Location:
trunk/ippMonitor
Files:
1 added
7 edited

Legend:

Unmodified
Added
Removed
  • trunk/ippMonitor/INSTALL

    r42834 r42972  
    4545# czartool_plots -> /export/ipp113.0/ipp/czartool_plots/
    4646# --usePDO selects a modern PHP/mysql interface
    47 psconfigure --htdocs /var/www/localhost/htdocs --wwwbin /var/www/localhost/wwwbin --pltdir /var/www/localhost/htdocs/ippMonitor --site ~/ippconfig/ippmonitor.config --usePDO
     47#psconfigure --htdocs /var/www/localhost/htdocs --wwwbin /var/www/localhost/wwwbin --pltdir /var/www/localhost/htdocs/ippMonitor --site ~/ippconfig/ippmonitor.config --usePDO
     48# for ippm@grens1
     49psconfigure --htdocs /var/www/html --wwwbin /var/www/html/wwwbin --pltdir /var/www/html/ippMonitor --site ~/ippconfig/ippmonitor.config --usePDO
    4850
    4951# note that modern php requires the use of PDO, the older MDB2 and DB mysql interfaces have been deprecated
  • trunk/ippMonitor/czartool/czarpoll.pl

    r42931 r42972  
    2626use czartool::DayMetrics;
    2727use czartool::MetricsIndex;
     28
     29# --- Logging setup ---
     30use IO::Handle;
     31my $log_file = "/tmp/czarpoll.log";
     32open(my $LOG, '>>', $log_file) or die "Cannot open log file $log_file: $!";
     33$LOG->autoflush(1);
     34select($LOG);  # Redirect all print statements to the log file
     35open(STDERR, '>&', $LOG) or die "Cannot redirect STDERR to log file: $!";
    2836
    2937my $period = 60;
     
    149157        my @labels = @{$pantasks->getLabels($server,'gpc1')};
    150158        push (@allLabels, @labels);
     159        #print "[$server gpc1] labels: ", join(", ", @labels), "\n";
    151160
    152161        my @labels2 = @{$pantasks->getLabels($server,'gpc2')};
    153162        push (@allLabels2, @labels2);
     163        #print "[$server gpc2] labels: ", join(", ", @labels2), "\n";
    154164    }
    155165    #The way gpc2 pantasks are set up is different from gpc1. Therefore, the same label can appear in both stdscience and distribution.
     
    309319    while (1) {
    310320
     321        # --- Truncate log file at start of each iteration ---
     322        close($LOG);
     323        open($LOG, '>', $log_file) or die "Cannot reopen log file $log_file: $!";
     324        $LOG->autoflush(1);
     325        select($LOG); # Redirect STDERR (error messages) to the same log file
     326        open(STDERR, '>&', $LOG) or die "Cannot redirect STDERR to log file: $!";
     327
     328        print "\n======================== CZARPOLL ITERATION START ========================\n";
     329        print scalar(localtime) . "\n";
     330        print "==========================================================================\n";
     331
    311332        my $iterationStartTime = time();
    312333 
     
    447468
    448469        print "--------------------------------------------------------------------------\n";
     470        print "Iteration complete at " . scalar(localtime) . "\n";
     471        print "==========================================================================\n";
    449472        if ($iterationTime < $period) {
    450473            print "* Going to sleep\n";
  • trunk/ippMonitor/czartool/extra_labels.dat

    r42794 r42972  
    22## Priorities for the labels must be added to the priorities mySQL table for the relevant telescope, if desired.
    33## PV3.Pole.Reprocess.20180510 gpc1
    4 ps_ud_QUB gpc1
     4## ps_ud_QUB gpc1
    55## update.ipp138.fix gpc1
    66## update.ipp138.fix gpc2
  • trunk/ippMonitor/raw/czartool_labels.php

    r42923 r42972  
    2525menu($myMenu, 'Czartool on '.$lastUpdateTime, 'ipp.css', $ID['link'], $ID['proj']);
    2626
    27 $stages=array(
    28         "chip",
    29         "cam",
    30         "fake",
    31         "warp",
    32         "stack",
    33         "staticsky",
    34         "fullforce",
    35         "diff",
    36         "dist",
    37         "pub",
    38         );
    39 
    40 $getFailures = array(
    41         "chip"      => "failedChipProcessedImfile.php",
    42         "cam"       => "failedCamProcessedExp.php",
    43         "fake"      => "failedFakeProcessedImfile.php",
    44         "warp"      => "failedWarpSkyfiles.php",
    45         "stack"     => "failedStackSkyfile.php",
    46         "staticsky" => "failedStaticsky.php",
    47         "fullforce" => "failedFullForceSkyfiles.php",
    48         "diff"      => "failedDiffSkyfile.php",
    49         "dist"      => "NONE",
    50         "pub"       => "publishDone.php",
    51 );           
    52 
    53 $mainTable = array(
    54         "chip"      => "chipRun",
    55         "cam"       => "camRun",
    56         "fake"      => "fakeRun",
    57         "warp"      => "warpRun",
    58         "stack"     => "stackRun",
    59         "staticsky" => "staticskyRun",
    60         "fullforce" => "fullForceRun",
    61         "diff"      => "diffRun",
    62         "dist"      => "distRun",
    63         "pub"       => "publishRun",
    64 );           
    65 
    66 $addSearchState = array(
    67         "chip"      => 0,
    68         "cam"       => 0,
    69         "fake"      => 1,
    70         "warp"      => 0,
    71         "stack"     => 0,
    72         "staticsky" => 0,
    73         "fullforce" => 0,
    74         "diff"      => 0,
    75         "dist"      => 0,
    76         "pub"       => 0,
    77 );           
     27$stages = [
     28    "chip",
     29    "cam",
     30    "fake",
     31    "warp",
     32    "stack",
     33    "staticsky",
     34    "fullforce",
     35    "diff",
     36    "dist",
     37    "pub",
     38];
     39
     40$getFailures = [
     41    "chip"      => "failedChipProcessedImfile.php",
     42    "cam"       => "failedCamProcessedExp.php",
     43    "fake"      => "failedFakeProcessedImfile.php",
     44    "warp"      => "failedWarpSkyfiles.php",
     45    "stack"     => "failedStackSkyfile.php",
     46    "staticsky" => "failedStaticsky.php",
     47    "fullforce" => "failedFullForceSkyfiles.php",
     48    "diff"      => "failedDiffSkyfile.php",
     49    "dist"      => "NONE",
     50    "pub"       => "publishDone.php",
     51];
     52
     53$mainTable = [
     54    "chip"      => "chipRun",
     55    "cam"       => "camRun",
     56    "fake"      => "fakeRun",
     57    "warp"      => "warpRun",
     58    "stack"     => "stackRun",
     59    "staticsky" => "staticskyRun",
     60    "fullforce" => "fullForceRun",
     61    "diff"      => "diffRun",
     62    "dist"      => "distRun",
     63    "pub"       => "publishRun",
     64];
     65
     66$addSearchState = [
     67    "chip"      => 0,
     68    "cam"       => 0,
     69    "fake"      => 1,
     70    "warp"      => 0,
     71    "stack"     => 0,
     72    "staticsky" => 0,
     73    "fullforce" => 0,
     74    "diff"      => 0,
     75    "dist"      => 0,
     76    "pub"       => 0,
     77];
    7878
    7979$pass = $ID['pass'];
     
    159159
    160160// The Update mode of czartool is obsolete. selectedMode is hardcoded to stdscience above.
    161 $isUpdate = ($selectedMode == "update");
     161// $isUpdate = ($selectedMode == "update");
    162162$isUpdate = 0;
    163163
     
    211211echo $table;
    212212createTableTitle("Status and links", 2);
    213 echo "<tr><td>ippMonitor Notes </td>
    214           <td> PHP: v.$phpversion
    215   &nbsp|&nbsp <a href=\"$svnlink/browser/trunk/ippMonitor\" target=new><font color=\"blue\">SVN: v.$svnversion</font></a>
    216 
    217   &nbsp|&nbsp <a href=\"http://ippmonitor.ipp.ifa.hawaii.edu/ippMonitor/czartool_labels.php?pass=$pass&proj=$proj\"><font color=\"blue\">online (ipp113) </font></a>,
    218        &nbsp <a href=\"http://ipp117.ipp.ifa.hawaii.edu/ippMonitor/czartool_labels.php?pass=$pass&proj=$proj\"><font color=\"blue\">dev (ipp117) </font></a>
    219        version </td></tr>";
    220 echo "<tr><td>Pan-STARRS Data Store</td>
    221           <td><a href=\"https://svn.ifa.hawaii.edu/\" target=new><font color=\"blue\">Home</font></a>
    222   &nbsp|&nbsp <a href=\"https://svn.ifa.hawaii.edu/summit/ds/gpc1/\" target=new><font color=\"blue\">GPC1</font></a>
    223   &nbsp|&nbsp <a href=\"https://svn.ifa.hawaii.edu/conductorb/ds/gpc2/\" target=new><font color=\"blue\">GPC2</font></a>
    224   &nbsp|&nbsp <a href=\"http://ipp.ifa.hawaii.edu/skycoverage.20201110\" target=new><font color=\"blue\">Sky Coverage</font></a></td></tr>";
    225 echo "<tr><td>Observations Metrics</td>
    226           <td><a href=\"https://ps1wiki.ifa.hawaii.edu/data/metrics/index.html\" target=new><font color = \"blue\"> PS1</font></a>
    227   &nbsp|&nbsp <a href=\"https://ps1wiki.ifa.hawaii.edu/data/ps2metrics/index.html\"  target=new><font color = \"blue\"> PS2</font></a>
    228   &nbsp|&nbsp <a href=\"http://ippmonitor.ipp.ifa.hawaii.edu/ippMonitor/ippMetrics/index.html\" target=new><font color=\"blue\">IPP</font></a></td></tr>";
    229 echo "<tr><td>Postage Stamp Server</td>
    230           <td><a href=\"http://pstamp.ipp.ifa.hawaii.edu/pshome.php\" target=new><font color=\"blue\">Home</font></a>
    231   &nbsp|&nbsp <a href=\"http://pstamp.ipp.ifa.hawaii.edu/status.php\" target=new><font color=\"blue\">Status</font></a>
    232   &nbsp|&nbsp <a href=\"http://pstamp.ipp.ifa.hawaii.edu/dsroot.php\" target=new><font color=\"blue\">Data Store</font></a></td></tr>";
    233 echo "<tr><td>IPP Cluster Ganglia Monitor</td>
    234           <td><a href=\"$ganglia/?r=hour&s=descending&hc=6&c=IPP%2520Production\" target=new> <font color=\"blue\">Load_reports</font></a>
    235   &nbsp|&nbsp <a href=\"$ganglia/?m=cpu_report&r=hour&s=by%2520name&c=IPP+Production&h=&sh=1&hc=8&z=small\" target=new> <font color=\"blue\">CPU_reports</font></a>
    236   &nbsp|&nbsp <a href=\"$ganglia/?m=mem_report&r=hour&s=by%2520name&c=IPP+Production&h=&sh=1&hc=8&z=small\" target=new> <font color=\"blue\">Memory_reports</font></a>
    237   </td></tr>";
    238 echo "<tr><td>Documentations for IPP</td>
    239           <td><a href=\"$conflink/wiki/spaces/IPPCZAR/pages/679903282/Czarblog+Roll\" target=new><font color=\"blue\">Czarblog</font></a>
    240   &nbsp|&nbsp <a href=\"$conflink/wiki/spaces/IPPCZAR/pages/678634250/Czar+Guidelines\" target=new><font color=\"blue\">Czar guidelines</font></a>
    241   &nbsp|&nbsp <a href=\"$conflink/browse/IPP-86\" target=new><font color=\"blue\">ippMonitor Jira</font></a>
    242   &nbsp|&nbsp <a href=\"$conflink/wiki/spaces/IKB/pages/678298859/IPP+Hardware+Status\" target=new><font color=\"blue\">Hardware</font></a>
    243   &nbsp|&nbsp <a href=\"$svnlink/\" target=new><font color=\"blue\">trac</font></a>
    244   </td></tr>";
    245 echo "<tr><td>Current nightly science status</td>
    246           <td>".getNightlyScienceStatus($czardb, $proj)."</td></tr>";
    247 echo "<tr><td>Czardb last update time</td>
    248           <td> $hsttime1 (HST), $utctime1 (UTC), $mjdday (MJD)</td></tr>";
     213
     214$html = <<<HTML
     215<tr>
     216  <td>ippMonitor Notes</td>
     217  <td>
     218    PHP: v.$phpversion &nbsp|&nbsp
     219    <a href="$svnlink/browser/trunk/ippMonitor"                                                     target="_blank" style="color: blue; text-decoration: none;">SVN: v.$svnversion</a> &nbsp|&nbsp
     220    <a href="./czartool_labels.php?pass=$pass&proj=$proj"                                           target="_blank" style="color: blue; text-decoration: none;">online (grens1)</a> &nbsp|&nbsp
     221    <a href="http://ipp117.ipp.ifa.hawaii.edu/ippMonitor/czartool_labels.php?pass=$pass&proj=$proj" target="_blank" style="color: blue; text-decoration: none;">dev (ipp117)</a> version
     222  </td>
     223</tr>
     224<tr>
     225  <td>Pan-STARRS Data Store</td>
     226  <td>
     227    <a href="https://svn.ifa.hawaii.edu/"                    target="_blank" style="color: blue; text-decoration: none;">Home</a> &nbsp|&nbsp
     228    <a href="https://svn.ifa.hawaii.edu/summit/ds/gpc1/"     target="_blank" style="color: blue; text-decoration: none;">GPC1</a> &nbsp|&nbsp
     229    <a href="https://svn.ifa.hawaii.edu/conductorb/ds/gpc2/" target="_blank" style="color: blue; text-decoration: none;">GPC2</a> &nbsp|&nbsp
     230    <a href="http://ipp.ifa.hawaii.edu/skycoverage.20201110" target="_blank" style="color: blue; text-decoration: none;">Sky Coverage</a>
     231  </td>
     232</tr>
     233<tr>
     234  <td>Observations Metrics</td>
     235  <td>
     236    <a href="https://ps1wiki.ifa.hawaii.edu/data/metrics/index.html"    target="_blank" style="color: blue; text-decoration: none;">PS1</a> &nbsp|&nbsp
     237    <a href="https://ps1wiki.ifa.hawaii.edu/data/ps2metrics/index.html" target="_blank" style="color: blue; text-decoration: none;">PS2</a> &nbsp|&nbsp
     238    <a href="./ippMetrics/index.html"                                   target="_blank" style="color: blue; text-decoration: none;">IPP</a>
     239  </td>
     240</tr>
     241<tr>
     242  <td>Postage Stamp Server</td>
     243  <td>
     244    <a href="http://pstamp.ipp.ifa.hawaii.edu/pshome.php" target="_blank" style="color: blue; text-decoration: none;">Home</a> &nbsp|&nbsp
     245    <a href="http://pstamp.ipp.ifa.hawaii.edu/status.php" target="_blank" style="color: blue; text-decoration: none;">Status</a> &nbsp|&nbsp
     246    <a href="http://pstamp.ipp.ifa.hawaii.edu/dsroot.php" target="_blank" style="color: blue; text-decoration: none;">Data Store</a>
     247  </td>
     248</tr>
     249<tr>
     250  <td>IPP Cluster Ganglia Monitor</td>
     251  <td>
     252    <a href="$ganglia/?r=hour&s=descending&hc=6&c=IPP%2520Production"                           target="_blank" style="color: blue; text-decoration: none;">Load_reports</a> &nbsp|&nbsp
     253    <a href="$ganglia/?m=cpu_report&r=hour&s=by%2520name&c=IPP+Production&h=&sh=1&hc=8&z=small" target="_blank" style="color: blue; text-decoration: none;">CPU_reports</a> &nbsp|&nbsp
     254    <a href="$ganglia/?m=mem_report&r=hour&s=by%2520name&c=IPP+Production&h=&sh=1&hc=8&z=small" target="_blank" style="color: blue; text-decoration: none;">Memory_reports</a>
     255  </td>
     256</tr>
     257<tr>
     258  <td>Documentations for IPP</td>
     259  <td>
     260    <a href="$conflink/wiki/spaces/IPPCZAR/pages/679903282/Czarblog+Roll"                    target="_blank" style="color: blue; text-decoration: none;">Czarblog</a> &nbsp|&nbsp
     261    <a href="$conflink/wiki/spaces/IPPCZAR/pages/678634250/Czar+Guidelines"                  target="_blank" style="color: blue; text-decoration: none;">Czar guidelines</a> &nbsp|&nbsp
     262    <a href="$conflink/issues/?jql=project+%3D+%22IPP%22+AND+component+%3D+%22ippMonitor%22" target="_blank" style="color: blue; text-decoration: none;">ippMonitor Jira</a> &nbsp|&nbsp
     263    <a href="$conflink/wiki/spaces/IKB/pages/678298859/IPP+Hardware+Status"                  target="_blank" style="color: blue; text-decoration: none;">Hardware</a> &nbsp|&nbsp
     264    <a href="$svnlink/"                                                                      target="_blank" style="color: blue; text-decoration: none;">trac</a>
     265  </td>
     266</tr>
     267
     268HTML;
     269echo $html;
     270echo "<tr><td>Current nightly science status</td> <td>".getNightlyScienceStatus($czardb, $proj)."</td></tr>";
     271echo "<tr><td>Czardb last update time</td> <td>$hsttime1 (HST), $utctime1 (UTC), $mjdday (MJD)</td></tr>";
    249272echo "</table><br>";
    250273
    251 
    252274createLabelsTable($pass, $proj, $czardb, $selectedMode, $labels, $distLabelsExist, $pubLabelsExist, $stages, "new", $selectedLabel, $selectedStage, $plotType);
    253 if ($selectedMode == "update") {echo "<br>"; createPStampDataTable();}
    254 
    255 # XXX EAM : 2017.09.17 : debugging slowness
    256 # CCL : 2019.11.09 : debugging slowness
    257 createSummitDataTable2($projectdb, $pass, $proj, $debug); // this is slow because of the join between summitExp and rawExp, but not the major one
     275
     276createSummitDataTable($projectdb, $pass, $proj, $debug);
    258277
    259278createChunkDataTable($projectdb, $debug);  // this is another part for slowness during nightly processing
    260279
    261 // createDatesTable($czardb, $proj); what is this for?
     280// Postage stamp requests list
     281// createPStampDataTable();
     282
     283// Current dates assigned for selected pantasks servers
     284// createDatesTable($czardb, $proj);
    262285
    263286// Database status
     
    266289    $start = microtime(true);
    267290    echo "<tr>";
    268     createTableTitle("Database status (update fix log <a href=\"http://svn.pan-starrs.ifa.hawaii.edu/trac/ipp/wiki/Replication_Issues\"><font color=\"blue\">here</a>)", 3);
     291    createTableTitle("Database status (update fix log <a href=\"http://svn.pan-starrs.ifa.hawaii.edu/trac/ipp/wiki/Replication_Issues\" target=_blank><font color=\"blue\">here</a>)", 3);
    269292     
    270293    echo "<tr>";
     
    324347    showPingStatus($HOST_NAGIOS, $LABEL_NAGIOS);
    325348    showPingStatus($HOST_SVN, $LABEL_SVN);
    326     showPingStatus($HOST_CONFLUENCE, $LABEL_CONFLUENCE);
     349    //showPingStatus($HOST_CONFLUENCE, $LABEL_CONFLUENCE);
    327350    showPingStatus($HOST_MAILMAN, $LABEL_MAILMAN);
    328351
     
    339362
    340363#2nd table column empty contnets
    341 echo "<td style=height:200px;width: 10px;text-align:top;\"><br>";
     364//echo "<td style=height:200px;width: 10px;text-align:top;\"><br>";
    342365
    343366#3rd table column for plots
    344367echo "<td style=height:200px;width:600px;text-align:top;\"><br>";
    345 # plots
     368
     369// processing times series plots
    346370if ($selectedStage == "all_stages" && $selectedLabel == "all") {
    347371    // Use old method with gnuplots as the rate query is slow with all stages and labels
     
    351375
    352376    // Uncomment for dev version with Google Charts
    353     // echo Gettimeseries($czardb, $selectedMode, $selectedLabel, $proj, $selectedStage);
    354     //echo Getrate($czardb, $selectedMode, $selectedLabel, $proj, $selectedStage);
     377    # echo Gettimeseries($czardb, $selectedMode, $selectedLabel, $proj, $selectedStage);
     378    # echo Getrate($czardb, $selectedMode, $selectedLabel, $proj, $selectedStage);
    355379} else {
    356380    // Use Google Charts for specific stage and label
     
    359383}
    360384
    361 // Helper function to generate image tags for plots
     385require_once 'functions.php'; 
     386// data quality for current day
     387echo getDataQuality($projectdb, 'today', $proj);
     388
     389// storage timeseries
     390echo getSpaces($czardb);
     391
     392// storage plot
     393echo getHosts($czardb);
     394
     395
     396# timer for main page end
     397echo "<tr><th colspan=3>";
     398echo "overall loading in " . round((microtime(true) - $mainstart), 3) . " seconds.";
     399echo "</th></tr>";
     400echo "</table>";
     401
     402menu_end();
     403
     404
     405/////////////////////////////////////////////////////////////////////////////
     406//                                                                         //
     407//                               Functions                                 //
     408//                                                                         //
     409/////////////////////////////////////////////////////////////////////////////
     410
     411###########################################################################
     412#
     413# Display images
     414# Helper function to generate image tags for plots
     415#
     416###########################################################################
    362417function displayImage($type, $mode, $label, $proj, $stage, $plottype) {
    363418    $src = "czartool_getplot.php?mode=" . urlencode($mode) .
     
    370425}
    371426
    372 # data quality timeseries
    373 echo getDataQuality2($projectdb); // new version of data quality plots
    374 
    375 # storage timeseries
    376 echo getSpaces($czardb);
    377 # storage plot
    378 echo getHosts($czardb);
    379 
    380 # timer for main page end
    381 echo "<tr><th colspan=3>";
    382 echo "overall loading in " . round((microtime(true) - $mainstart), 3) . " seconds.";
    383 echo "</th></tr>";
    384 echo "</table>";
    385 
    386 menu_end();
    387 
    388 
    389 /////////////////////////////////////////////////////////////////////////////
    390 //                                                                         //
    391 //                               Functions                                 //
    392 //                                                                         //
    393 /////////////////////////////////////////////////////////////////////////////
    394427
    395428###########################################################################
     
    399432###########################################################################
    400433function createPStampDataTable() {
    401 
    402     $sql = "SELECT label, COUNT(job_id) AS 'Unfinished Jobs', IFNULL(priority,10000) AS priority FROM pstampRequest JOIN pstampJob USING(req_id) LEFT JOIN Label USING(label) WHERE pstampJob.state ='run' and pstampRequest.state ='run' GROUP by label";
    403 
    404     $pstampdb = DB::connect("mysql://ippuser:ippuser@ipp113/ippRequestServer");
    405     $qry = $pstampdb->query($sql);
    406     if (dberror($qry)) {
     434    // timer start
     435    $start = microtime(true);
     436
     437    $dsn = "mysql:host=ipp113;dbname=ippRequestServer;charset=utf8mb4";
     438    $pdo = new PDO($dsn, 'ippuser', 'ippuser', [
     439        PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION
     440    ]);
     441
     442    // SQL statement
     443    $sql = "
     444        SELECT label,
     445               COUNT(job_id) AS `Unfinished Jobs`,
     446               IFNULL(priority,10000) AS priority
     447        FROM pstampRequest
     448        JOIN pstampJob USING(req_id)
     449        LEFT JOIN Label USING(label)
     450        WHERE pstampJob.state ='run'
     451          AND pstampRequest.state ='run'
     452        GROUP BY label
     453    ";
     454   
     455    try {
     456        // Execute the query
     457        $stmt = $pdo->query($sql);
     458        $rows = $stmt->fetchAll(PDO::FETCH_NUM);
     459
     460    } catch (PDOException $e) {
    407461        echo "<b>error reading pstamp table</b><br>\n";
    408         echo "<br><small><b> table query : $sql </b></small><br>\n";
     462        echo "<br><small><b> table query : " . htmlspecialchars($sql) . " </b></small><br>\n";
    409463        menu_end();
     464        return; // stop executing the rest of the function
    410465    }
    411466
     
    414469    echo $table;
    415470
    416     #echo "<table width=\"$width\" align=\"middle\" border=\"1\" cellspacing=\"0\" cellpadding=\"4\" style=\"font-size:80%\">\n";
    417471    echo "<tr>";
    418472    createTableTitle("Postage stamp requests", 3);
     
    423477
    424478    // list the results
    425     while ($qry->fetchInto($row)) {
     479    foreach ($rows as $row) {
    426480        echo "<tr>";
    427         echo "<td>$row[0]</td>";
    428         echo "<td>$row[1]</td>";
    429         echo "<td>$row[2]</td>";
     481        echo "<td>" . htmlspecialchars($row[0]) . "</td>";
     482        echo "<td>" . htmlspecialchars($row[1]) . "</td>";
     483        echo "<td>" . htmlspecialchars($row[2]) . "</td>";
    430484        echo "</tr>";
    431485    }
    432     echo "</table>\n";
    433 }
    434 
     486   
     487    // End timer
     488    echo "<tr><th colspan=10>";
     489    $total_time = round(microtime(true) - $start, 3);
     490    echo "<center>loading in $total_time seconds<br></center>";
     491    echo "</table><br>";
     492}
    435493
    436494###########################################################################
     
    448506    $mjdDay  = getMJD();
    449507    $expname = "o{$mjdDay}%";
    450 
    451508    $date = gmdate("Y-m-d");
    452     $sql = "SELECT MIN(exp_id) FROM rawExp WHERE exp_name like '$expname' ";
    453     if ($debug) {echo "$sql<br>";}
    454     $qry = $projectdb->query($sql);
    455 
    456     if (dberror($qry)) {
     509
     510    $sql = "SELECT MIN(exp_id) FROM rawExp WHERE exp_name LIKE :expname";
     511    if ($debug) {
     512        echo "$sql<br>";
     513    }
     514   
     515    try {
     516        // Prepare and execute
     517        $stmt = $projectdb->prepare($sql);
     518        $stmt->execute([':expname' => $expname]);
     519   
     520        // Fetch the single row
     521        $row = $stmt->fetch(PDO::FETCH_NUM);
     522   
     523        if ($row === false || $row[0] === null) {
     524            echo "*** WARNING: No data for tonight **\n";
     525            $refExpID = 0;
     526        } else {
     527            $refExpID = $row[0];
     528        }
     529    } catch (PDOException $e) {
    457530        echo "*** WARNING: No data for tonight **\n";
    458531        $refExpID = 0;
    459     } else {
    460         $qry->fetchInto($refExpID);
    461         $refExpID = isset($refExpID[0]) ? $refExpID[0] : 0;
    462     }
     532   
     533        // (optional) debug
     534        if ($debug) {
     535            echo "DB error: " . $e->getMessage() . "<br>";
     536        }
     537    }
     538
    463539    // Print the result of the query
    464     if ($debug) {echo "The first exp_id of tonight is: $refExpID<br>";}
    465 
    466     #do a query of raw/chip/cam/warp counts for this night, split by chunk.
    467     #we need to do a subquery to find the total number of skycells in warpSkyfile, as well as the number of those with a bad quality
    468     $query = "SELECT suba.chunk,suba.Nvis1,suba.Nvis2,suba.Nvis3,suba.Nvis4,suba.Ncamgood,suba.Ncambad,subb.Nwarpgood,subb.Nwarpbad FROM ";
    469     $query .= "    (SELECT substr(comment, 1, position(' ' in comment)) AS chunk,count(if(rawExp.comment LIKE '%visit 1%',1,NULL)) AS Nvis1,";
    470     $query .= "    count(if(rawExp.comment LIKE '%visit 2%',1,NULL)) AS Nvis2,count(if(rawExp.comment LIKE '%visit 3%',1,NULL)) AS Nvis3,";
    471     $query .= "    count(if(rawExp.comment LIKE '%visit 4%',1,NULL)) AS Nvis4, count(if(camProcessedExp.sigma_ra <= 5 AND camProcessedExp.sigma_dec <= 5 AND camProcessedExp.quality = 0,1,NULL)) AS Ncamgood,";
    472     $query .= "    count(if(camProcessedExp.fwhm_major > 12 OR camProcessedExp.sigma_ra > 5 OR camProcessedExp.sigma_dec > 5 OR camProcessedExp.quality > 0,1,NULL)) AS Ncambad";
    473     $query .= "    FROM rawExp LEFT JOIN chipRun USING (exp_id) LEFT JOIN camRun USING (chip_id) JOIN camProcessedExp USING (cam_id) LEFT JOIN fakeRun USING (cam_id) LEFT JOIN warpRun USING (fake_id)";
    474     $query .= "    WHERE exp_name like '$expname' AND exp_type = 'OBJECT' AND rawExp.comment like '%visit%'";
    475     $query .= "    AND (obs_mode LIKE '%SS%' OR obs_mode LIKE '%BRIGHT%') AND obs_mode NOT LIKE 'ENGINEERING' AND obs_mode NOT LIKE 'MANUAL'";
    476     $query .= "    GROUP BY chunk ORDER BY dateobs) as suba ";
    477     $query .= "LEFT JOIN";
    478     $query .= "    (SELECT subc.chunk, count(if(subc.Nwarpskycellbad < subc.Nwarpskycell,1,NULL)) AS Nwarpgood, count(if(subc.Nwarpskycellbad = subc.Nwarpskycell AND subc.Nwarpskycell > 0,1,NULL)) AS Nwarpbad";
    479     $query .= "    FROM ";
    480     $query .= "      (SELECT substr(comment, 1, position(' ' in comment)) AS chunk,count(warpSkyfile.quality) AS Nwarpskycell,count(if(warpSkyfile.quality > 0,1,NULL)) AS Nwarpskycellbad";
    481     $query .= "      FROM rawExp LEFT JOIN chipRun USING (exp_id) LEFT JOIN camRun USING (chip_id) LEFT JOIN fakeRun USING (cam_id) LEFT JOIN warpRun USING (fake_id) JOIN warpSkyfile USING (warp_id)";
    482     $query .= "      WHERE exp_name like '$expname' AND exp_type = 'OBJECT' AND rawExp.comment like '%visit%'";
    483     $query .= "      AND (obs_mode LIKE '%SS%' OR obs_mode LIKE '%BRIGHT%') AND obs_mode NOT LIKE 'ENGINEERING' AND obs_mode NOT LIKE 'MANUAL'";
    484     $query .= "      GROUP BY exp_name) as subc";
    485     $query .= "    GROUP BY chunk) AS subb ";
    486     $query .= "ON suba.chunk=subb.chunk";
    487  
    488     if ($debug) {echo "$query<br>";}
    489     $qry = $projectdb->query($query);
    490 
    491     if (dberror($qry)) {
     540    if ($debug) {
     541        echo "The first exp_id of tonight is: $refExpID<br>";
     542    }
     543
     544    // Build the query (same SQL, but parameterized for $expname)
     545    $query = "SELECT suba.chunk,suba.Nvis1,suba.Nvis2,suba.Nvis3,suba.Nvis4,
     546                     suba.Ncamgood,suba.Ncambad,subb.Nwarpgood,subb.Nwarpbad
     547              FROM (
     548                  SELECT substr(comment, 1, position(' ' in comment)) AS chunk,
     549                         count(if(rawExp.comment LIKE '%visit 1%',1,NULL)) AS Nvis1,
     550                         count(if(rawExp.comment LIKE '%visit 2%',1,NULL)) AS Nvis2,
     551                         count(if(rawExp.comment LIKE '%visit 3%',1,NULL)) AS Nvis3,
     552                         count(if(rawExp.comment LIKE '%visit 4%',1,NULL)) AS Nvis4,
     553                         count(if(camProcessedExp.sigma_ra <= 5
     554                                  AND camProcessedExp.sigma_dec <= 5
     555                                  AND camProcessedExp.quality = 0,1,NULL)) AS Ncamgood,
     556                         count(if(camProcessedExp.fwhm_major > 12
     557                                  OR camProcessedExp.sigma_ra > 5
     558                                  OR camProcessedExp.sigma_dec > 5
     559                                  OR camProcessedExp.quality > 0,1,NULL)) AS Ncambad
     560                  FROM rawExp
     561                       LEFT JOIN chipRun USING (exp_id)
     562                       LEFT JOIN camRun USING (chip_id)
     563                       JOIN camProcessedExp USING (cam_id)
     564                       LEFT JOIN fakeRun USING (cam_id)
     565                       LEFT JOIN warpRun USING (fake_id)
     566                  WHERE exp_name LIKE :expname
     567                    AND exp_type = 'OBJECT'
     568                    AND rawExp.comment LIKE '%visit%'
     569                    AND (obs_mode LIKE '%SS%' OR obs_mode LIKE '%BRIGHT%')
     570                    AND obs_mode NOT LIKE 'ENGINEERING'
     571                    AND obs_mode NOT LIKE 'MANUAL'
     572                  GROUP BY chunk ORDER BY dateobs
     573              ) as suba
     574              LEFT JOIN (
     575                  SELECT subc.chunk,
     576                         count(if(subc.Nwarpskycellbad < subc.Nwarpskycell,1,NULL)) AS Nwarpgood,
     577                         count(if(subc.Nwarpskycellbad = subc.Nwarpskycell
     578                                  AND subc.Nwarpskycell > 0,1,NULL)) AS Nwarpbad
     579                  FROM (
     580                      SELECT substr(comment, 1, position(' ' in comment)) AS chunk,
     581                             count(warpSkyfile.quality) AS Nwarpskycell,
     582                             count(if(warpSkyfile.quality > 0,1,NULL)) AS Nwarpskycellbad
     583                      FROM rawExp
     584                           LEFT JOIN chipRun USING (exp_id)
     585                           LEFT JOIN camRun USING (chip_id)
     586                           LEFT JOIN fakeRun USING (cam_id)
     587                           LEFT JOIN warpRun USING (fake_id)
     588                           JOIN warpSkyfile USING (warp_id)
     589                      WHERE exp_name LIKE :expname
     590                        AND exp_type = 'OBJECT'
     591                        AND rawExp.comment LIKE '%visit%'
     592                        AND (obs_mode LIKE '%SS%' OR obs_mode LIKE '%BRIGHT%')
     593                        AND obs_mode NOT LIKE 'ENGINEERING'
     594                        AND obs_mode NOT LIKE 'MANUAL'
     595                      GROUP BY exp_name
     596                  ) as subc
     597                  GROUP BY chunk
     598              ) AS subb
     599              ON suba.chunk=subb.chunk";
     600   
     601    if ($debug) { echo "$query<br>"; }
     602   
     603    try {
     604        $stmt = $projectdb->prepare($query);
     605        $stmt->execute([':expname' => $expname]);
     606    } catch (PDOException $e) {
    492607        echo "<b>error querying SQL table</b><br>\n";
    493608        echo "<br><small><b> table query : $query </b></small><br>\n";
    494609        menu_end();
    495     }
    496 
     610        exit;
     611    }
     612   
    497613    // set up the table
    498614    global $table;
     
    512628    createTableColumnHeader("wsd_pub");
    513629    createTableColumnHeader("Completion");
    514 
     630   
    515631    $msg = "No science images taken since $date";
    516 
     632   
     633    $badcamflag  = 0;
     634    $badwarpflag = 0;
     635    $baddiffflag = 0;
    517636    // list the results
    518     while ($qry->fetchInto($row)) {
    519         $Nvis1 = 0;
    520         $Nvis2 = 0;
    521         $Nvis3 = 0;
    522         $Nvis4 = 0;
    523         $Ncamgood = 0;
    524         $Ncambad = 0;
    525         $Nwarpgood = 0;
    526         $Nwarpbad = 0;
     637    while ($row = $stmt->fetch(PDO::FETCH_ASSOC)) {
     638        $chunkname = $row['chunk']    ?? '';
     639        $Nvis1     = $row['Nvis1']     ?? 0;
     640        $Nvis2     = $row['Nvis2']     ?? 0;
     641        $Nvis3     = $row['Nvis3']     ?? 0;
     642        $Nvis4     = $row['Nvis4']     ?? 0;
     643        $Ncamgood  = $row['Ncamgood']  ?? 0;
     644        $Ncambad   = $row['Ncambad']   ?? 0;
     645        $Nwarpgood = $row['Nwarpgood'] ?? 0;
     646        $Nwarpbad  = $row['Nwarpbad']  ?? 0;
     647
     648        if($Ncambad  >= 1) $badcamflag  = 1;
     649        if($Nwarpbad >= 1) $badwarpflag = 1;
     650
     651        $query1 = "SELECT suba.chunk,
     652                          SUM(IF(suba.Nvisits = 2,1,NULL)) as totvis2,
     653                          SUM(IF(suba.Nvisits = 3 OR suba.Nvisits = 4,2,NULL)) as totvis34
     654                   FROM (
     655                       SELECT substr(comment, 1, position(' ' in comment)) AS chunk,
     656                              count(distinct(comment)) AS Nvisits
     657                       FROM rawExp
     658                            LEFT JOIN chipRun USING (exp_id)
     659                            LEFT JOIN camRun USING (chip_id)
     660                       WHERE exp_name LIKE :expname
     661                         AND exp_type = 'OBJECT'
     662                         AND rawExp.comment LIKE '%visit%'
     663                         AND (obs_mode LIKE '%SS%' OR obs_mode LIKE '%BRIGHT%')
     664                         AND obs_mode NOT LIKE 'ENGINEERING'
     665                         AND obs_mode NOT LIKE 'MANUAL'
     666                         AND substr(comment,1,position(' ' in comment)) LIKE :chunkname
     667                       GROUP BY object
     668                   ) as suba
     669                   GROUP BY chunk";
     670       
     671        $stmt1 = $projectdb->prepare($query1);
     672        $stmt1->execute([':expname'=>$expname, ':chunkname'=>"$chunkname%"]);
     673        $row1 = $stmt1->fetch(PDO::FETCH_ASSOC);
    527674        $NexpWWdiff = 0;
    528         $Npubwarps = 0;
    529 
    530         $chunkname = $row[0];
    531         if($row[1] > 0) $Nvis1 = $row[1];
    532         if($row[2] > 0) $Nvis2 = $row[2];
    533         if($row[3] > 0) $Nvis3 = $row[3];
    534         if($row[4] > 0) $Nvis4 = $row[4];
    535         if($row[5] > 0) $Ncamgood = $row[5];
    536         if($row[6] > 0) $Ncambad = $row[6];
    537         if($row[7] > 0) $Nwarpgood = $row[7];
    538         if($row[8] > 0) $Nwarpbad = $row[8];
    539 
    540         #set flags if present
    541         if($Ncambad >= 1) $badcamflag = 1;
    542         if($Nwarpbad >= 1) $badwarpflag = 1;
    543 
    544         $query1 = "SELECT suba.chunk, SUM(IF(suba.Nvisits = 2,1,NULL)) as totvis2, SUM(IF(suba.Nvisits = 3 OR suba.Nvisits = 4,2,NULL)) as totvis34 FROM";
    545         $query1 .= "    (SELECT substr(comment, 1, position(' ' in comment)) AS chunk,count(distinct(comment)) AS Nvisits FROM";
    546         $query1 .= "    rawExp LEFT JOIN chipRun USING (exp_id) LEFT JOIN camRun USING (chip_id) ";
    547         $query1 .= "    WHERE exp_name like '$expname'";
    548         $query1 .= "    AND exp_type = 'OBJECT' AND rawExp.comment like '%visit%' AND (obs_mode LIKE '%SS%' OR obs_mode LIKE '%BRIGHT%') AND obs_mode NOT LIKE 'ENGINEERING' AND obs_mode NOT LIKE 'MANUAL'";
    549         $query1 .= "    AND substr(comment, 1, position(' ' in comment)) LIKE '$chunkname%' group by object) as suba group by chunk;";
    550 
    551         $qry1 = $projectdb->query($query1);
    552         while ($qry1->fetchInto($row)) {$NexpWWdiff = $row[1] + $row[2];}
    553 
    554         #find the unique diff_ids for each chunk, to be matched to the diffRun catalog
    555         $query2 = "     SELECT diff_id,warp1,stack1,warp2,stack2 FROM ";
    556         $query2 .= "           diffInputSkyfile JOIN warpRun ON (warp1=warp_id OR warp2=warp_id) JOIN fakeRun USING (fake_id) JOIN camRun USING (cam_id) JOIN camProcessedExp USING (cam_id) JOIN chipRun USING (chip_id) JOIN rawExp USING (exp_id)";
    557         $query2 .= "           WHERE exp_name like '$expname'";
    558         $query2 .= "           AND exp_type = 'OBJECT' AND rawExp.comment like '%visit%' AND (obs_mode LIKE '%SS%' OR obs_mode LIKE '%BRIGHT%')";
    559         $query2 .= "            AND obs_mode NOT LIKE 'ENGINEERING' AND obs_mode NOT LIKE 'MANUAL' AND substr(comment, 1, position(' ' in comment)) LIKE '$chunkname%' GROUP By diff_id";
    560         $qry2 = $projectdb->query($query2);
    561 
    562         $NWWdiffgood = 0;
    563         $NWWdiffbad = 0;
    564         $NWWdiffpub = 0;
    565 
    566         $NWSdiffgood = 0;
    567         $NWSdiffbad = 0;
    568         $NWSdiffpub = 0;
    569         $wrpcnt = array();
    570 
    571         while ($qry2->fetchInto($row2)) {
    572               #distinguish between WW and WS diffs based on stack2
    573               if ($row2[4] == 0) {
    574                   #find the total number of skycell and the number of skycell with a bad quality flag as well as the full publish state
    575                   $query3 = "     SELECT count(diffSkyfile.quality) AS Ndiff,count(if(diffSkyfile.quality > 0,1,NULL)) AS Ndiffbad,count(if(publishRun.state LIKE 'full',1,NULL)) AS Npub FROM ";
    576                   $query3 .= "           diffRun JOIN diffSkyfile USING (diff_id) LEFT JOIN publishRun ON (diffRun.diff_id = stage_id)";
    577                   $query3 .= "           WHERE diff_id='$row2[0]' GROUP BY diffRun.diff_id";
    578                   $qry3 = $projectdb->query($query3);
    579 
    580                   while ($qry3->fetchInto($row3)) {
    581                       #if all skycells have bad quality, this is a bad diff. If all the skycells are in a full state in publishRun, it is fully published
    582                       if ($row3[1]<$row3[0]) $NWWdiffgood ++;
    583                       if ($row3[1]==$row3[0]) $NWWdiffbad ++;
    584                       if ($row3[2]==$row3[0]) $NWWdiffpub ++;
    585 
    586                       #if this wwdiff is published, record its corresponding warp_ids to see how many unique ones there are
    587                       if ($row3[2]==$row3[0]) $wrpcnt{$row2[1]}++;  # record the presence of warp1
    588                       if ($row3[2]==$row3[0]) $wrpcnt{$row2[3]}++;  # record the presence of warp2
    589                   }
    590               }
    591 
    592               if ($row2[4] > 0) {
    593                   #find the total number of skycell and the number of skycell with a bad quality flag as well as the full publish state
    594                   $query3 = "     SELECT count(diffSkyfile.quality) AS Ndiff,count(if(diffSkyfile.quality > 0,1,NULL)) AS Ndiffbad,count(if(publishRun.state LIKE 'full',1,NULL)) AS Npub FROM ";
    595                   $query3 .= "           diffRun JOIN diffSkyfile USING (diff_id) LEFT JOIN publishRun ON (diffRun.diff_id = stage_id)";
    596                   $query3 .= "           WHERE diff_id='$row2[0]' GROUP BY diffRun.diff_id";
    597                   $qry3 = $projectdb->query($query3);
    598 
    599                   while ($qry3->fetchInto($row3)) {
    600                       #if all skycells have bad quality, this is a bad diff. If all the skycells are in a full state in publishRun, it is fully published
    601                       if ($row3[1]<$row3[0]) $NWSdiffgood ++;
    602                       if ($row3[1]==$row3[0]) $NWSdiffbad ++;
    603                       if ($row3[2]==$row3[0]) $NWSdiffpub ++;
    604                   }
    605               }
     675        if ($row1) {
     676            $NexpWWdiff = ($row1['totvis2'] ?? 0) + ($row1['totvis34'] ?? 0);
    606677        }
    607         #pull out the unique warp_ids
     678
     679        $query2 = "SELECT diff_id,warp1,stack1,warp2,stack2
     680           FROM diffInputSkyfile
     681                JOIN warpRun ON (warp1=warp_id OR warp2=warp_id)
     682                JOIN fakeRun USING (fake_id)
     683                JOIN camRun USING (cam_id)
     684                JOIN camProcessedExp USING (cam_id)
     685                JOIN chipRun USING (chip_id)
     686                JOIN rawExp USING (exp_id)
     687           WHERE exp_name LIKE :expname
     688             AND exp_type = 'OBJECT'
     689             AND rawExp.comment LIKE '%visit%'
     690             AND (obs_mode LIKE '%SS%' OR obs_mode LIKE '%BRIGHT%')
     691             AND obs_mode NOT LIKE 'ENGINEERING'
     692             AND obs_mode NOT LIKE 'MANUAL'
     693             AND substr(comment,1,position(' ' in comment)) LIKE :chunkname
     694           GROUP BY diff_id";
     695
     696        $stmt2 = $projectdb->prepare($query2);
     697        $stmt2->execute([':expname'=>$expname, ':chunkname'=>"$chunkname%"]);
     698       
     699        $NWWdiffgood = $NWWdiffbad = $NWWdiffpub = 0;
     700        $NWSdiffgood = $NWSdiffbad = $NWSdiffpub = 0;
     701        $wrpcnt = [];
     702       
     703        while ($row2 = $stmt2->fetch(PDO::FETCH_ASSOC)) {
     704            $diff_id = $row2['diff_id'];
     705            $warp1   = $row2['warp1'];
     706            $warp2   = $row2['warp2'];
     707            $stack2  = $row2['stack2'];
     708       
     709            // Build $query3 (same for WW and WS)
     710            $query3 = "SELECT count(diffSkyfile.quality) AS Ndiff,
     711                              count(if(diffSkyfile.quality > 0,1,NULL)) AS Ndiffbad,
     712                              count(if(publishRun.state LIKE 'full',1,NULL)) AS Npub
     713                       FROM diffRun
     714                            JOIN diffSkyfile USING (diff_id)
     715                            LEFT JOIN publishRun ON (diffRun.diff_id = stage_id)
     716                       WHERE diff_id=:diffid
     717                       GROUP BY diffRun.diff_id";
     718       
     719            $stmt3 = $projectdb->prepare($query3);
     720            $stmt3->execute([':diffid'=>$diff_id]);
     721            $row3 = $stmt3->fetch(PDO::FETCH_ASSOC);
     722            if (!$row3) continue;
     723       
     724            $Ndiff    = $row3['Ndiff']    ?? 0;
     725            $Ndiffbad = $row3['Ndiffbad'] ?? 0;
     726            $Npub     = $row3['Npub']     ?? 0;
     727       
     728            if ($stack2 == 0) {
     729                if ($Ndiffbad < $Ndiff) $NWWdiffgood++;
     730                if ($Ndiffbad == $Ndiff) $NWWdiffbad++;
     731                if ($Npub == $Ndiff) $NWWdiffpub++;
     732                if ($Npub == $Ndiff) {
     733                    $wrpcnt[$warp1] = ($wrpcnt[$warp1] ?? 0) + 1;
     734                    $wrpcnt[$warp2] = ($wrpcnt[$warp2] ?? 0) + 1;
     735                }
     736            } else {
     737                if ($Ndiffbad < $Ndiff) $NWSdiffgood++;
     738                if ($Ndiffbad == $Ndiff) $NWSdiffbad++;
     739                if ($Npub == $Ndiff) $NWSdiffpub++;
     740            }
     741        }
     742
     743        // Pull out the unique warp_ids
    608744        $Npubwarps = count($wrpcnt);
    609 
    610         #check for bad diffs
    611         if(($NWWdiffbad >= 1) or ($NWSdiffbad >= 1)) $baddiffflag = 1;
    612 
     745       
     746        // Check for bad diffs
     747        if (($NWWdiffbad >= 1) || ($NWSdiffbad >= 1)) {
     748            $baddiffflag = 1;
     749        }
     750       
     751        // Decide on completion text
    613752        if ($Nvis1 != $Nvis4) {
    614           if ($NWWdiffpub != $NexpWWdiff) $complete = "chunk incomplete, not done"; 
    615           if ($NWWdiffpub == $NexpWWdiff) $complete = "chunk incomplete, done"; 
     753            $complete = ($NWWdiffpub == $NexpWWdiff)
     754                ? "chunk incomplete, done"
     755                : "chunk incomplete, not done";
     756        } else {
     757            $complete = ($NWWdiffpub == $NexpWWdiff)
     758                ? "chunk complete, done"
     759                : "chunk complete, not done";
    616760        }
    617         if ($Nvis1 == $Nvis4) {
    618           if ($NWWdiffpub != $NexpWWdiff) $complete = "chunk complete, not done"; 
    619           if ($NWWdiffpub == $NexpWWdiff) $complete = "chunk complete, done"; 
    620 
    621         }
    622 
     761       
     762        // Now output the row
    623763        echo "<tr>";
    624 
    625         echo "<td>$chunkname</td>";
     764        echo "<td>" . htmlspecialchars($chunkname) . "</td>";
    626765        echo "<td>$Nvis1/$Nvis2/$Nvis3/$Nvis4</td>";
    627766        echo "<td>$Ncamgood/$Ncambad</td>";
     
    633772        echo "<td>$NWSdiffpub</td>";
    634773        echo "<td>$complete</td>";
    635 
    636774        echo "</tr>";
    637775    }
    638 
    639 
     776   
    640777    #-----------------------------------------
    641     #list bad quality cam exposures
     778    # list bad quality cam exposures
    642779    if ($badcamflag != 0) {
    643 
     780   
    644781        // set up another table
    645782        global $table;
     
    654791        createTableColumnHeader("sigma_dec");
    655792        createTableColumnHeader("comment");
    656 
    657          #query the bad cam exposures
    658         $query = "SELECT exp_name,cam_id, camProcessedExp.quality, camProcessedExp.sigma_ra, camProcessedExp.sigma_dec, comment";
    659         $query .= " FROM rawExp";
    660         $query .= " JOIN chipRun USING (exp_id)";
    661         $query .= " JOIN camRun USING (chip_id)";
    662         $query .= " JOIN camProcessedExp USING (cam_id)";
    663         //$query .= " WHERE dateobs LIKE '$date%' AND exp_id > $refExpID";
    664         $query .= " WHERE exp_name like '$expname'";
    665         $query .= " AND exp_type = 'OBJECT'";
    666         $query .= " AND (obs_mode LIKE '%SS%' OR obs_mode LIKE '%BRIGHT%') AND obs_mode NOT LIKE 'ENGINEERING' AND obs_mode NOT LIKE 'MANUAL'";
    667         $query .= " AND (camProcessedExp.fwhm_major > 12 OR camProcessedExp.quality > 0 OR camProcessedExp.sigma_ra > 5 OR camProcessedExp.sigma_dec > 5)";
    668         $query .= " ORDER BY comment";
    669 
    670         $qry1 = $projectdb->query($query);
    671         while ($qry1->fetchInto($row)) {
    672 
     793   
     794        // query the bad cam exposures
     795        $query = "
     796            SELECT exp_name,
     797                   cam_id,
     798                   camProcessedExp.quality,
     799                   camProcessedExp.sigma_ra,
     800                   camProcessedExp.sigma_dec,
     801                   comment
     802            FROM rawExp
     803            JOIN chipRun USING (exp_id)
     804            JOIN camRun USING (chip_id)
     805            JOIN camProcessedExp USING (cam_id)
     806            WHERE exp_name LIKE :expname
     807              AND exp_type = 'OBJECT'
     808              AND (obs_mode LIKE '%SS%' OR obs_mode LIKE '%BRIGHT%')
     809              AND obs_mode NOT LIKE 'ENGINEERING'
     810              AND obs_mode NOT LIKE 'MANUAL'
     811              AND (
     812                camProcessedExp.fwhm_major > 12 OR
     813                camProcessedExp.quality > 0 OR
     814                camProcessedExp.sigma_ra > 5 OR
     815                camProcessedExp.sigma_dec > 5
     816              )
     817            ORDER BY comment";
     818   
     819        if ($debug) { echo "$query<br>"; }
     820        // prepare & execute
     821        $stmt = $projectdb->prepare($query);
     822        $stmt->execute([':expname' => $expname]);
     823   
     824        // output rows
     825        while ($row = $stmt->fetch(PDO::FETCH_NUM)) {
    673826            echo "<tr>";
    674 
    675             echo "<td>$row[0]</td>";
    676             echo "<td>$row[1]</td>";
    677             echo "<td>$row[2]</td>";
    678             echo "<td>$row[3]</td>";
    679             echo "<td>$row[4]</td>";
    680             echo "<td>$row[5]</td>";
    681 
     827            echo "<td>" . htmlspecialchars($row[0]) . "</td>"; // exp_name
     828            echo "<td>" . htmlspecialchars($row[1]) . "</td>"; // cam_id
     829            echo "<td>" . htmlspecialchars($row[2]) . "</td>"; // quality
     830            echo "<td>" . htmlspecialchars($row[3]) . "</td>"; // sigma_ra
     831            echo "<td>" . htmlspecialchars($row[4]) . "</td>"; // sigma_dec
     832            echo "<td>" . htmlspecialchars($row[5]) . "</td>"; // comment
    682833            echo "</tr>";
    683834        }
     
    685836
    686837    #-----------------------------------------
    687     #list bad quality warp exposures
     838    # list bad quality warp exposures
    688839    if ($badwarpflag != 0) {
    689 
     840   
    690841        // set up another table
    691842        global $table;
     
    697848        createTableColumnHeader("warp_id");
    698849        createTableColumnHeader("comment");
    699 
    700         #query the bad warps
    701         $query = "SELECT exp_name,warp_id,count(warpSkyfile.quality) AS Nwarpskycell,count(if(warpSkyfile.quality > 0,1,NULL)) AS Nwarpskycellbad, comment";
    702         $query .= "      FROM rawExp LEFT JOIN chipRun USING (exp_id) LEFT JOIN camRun USING (chip_id) LEFT JOIN fakeRun USING (cam_id) LEFT JOIN warpRun USING (fake_id) JOIN warpSkyfile USING (warp_id)";
    703         $query .= "      WHERE exp_name like '$expname'";
    704         $query .= "      AND (obs_mode LIKE '%SS%' OR obs_mode LIKE '%BRIGHT%') AND obs_mode NOT LIKE 'ENGINEERING' AND obs_mode NOT LIKE 'MANUAL'";
    705         $query .= "      GROUP BY exp_name ORDER BY comment";
    706 
    707         $qry1 = $projectdb->query($query);
    708         while ($qry1->fetchInto($row)) {
    709 
     850   
     851        // query the bad warps
     852        $query = "
     853            SELECT exp_name,
     854                   warp_id,
     855                   COUNT(warpSkyfile.quality) AS Nwarpskycell,
     856                   COUNT(IF(warpSkyfile.quality > 0,1,NULL)) AS Nwarpskycellbad,
     857                   comment
     858            FROM rawExp
     859            LEFT JOIN chipRun USING (exp_id)
     860            LEFT JOIN camRun USING (chip_id)
     861            LEFT JOIN fakeRun USING (cam_id)
     862            LEFT JOIN warpRun USING (fake_id)
     863            JOIN warpSkyfile USING (warp_id)
     864            WHERE exp_name LIKE :expname
     865              AND (obs_mode LIKE '%SS%' OR obs_mode LIKE '%BRIGHT%')
     866              AND obs_mode NOT LIKE 'ENGINEERING'
     867              AND obs_mode NOT LIKE 'MANUAL'
     868            GROUP BY exp_name
     869            ORDER BY comment";
     870   
     871        // prepare and execute
     872        $stmt = $projectdb->prepare($query);
     873        $stmt->execute([':expname' => $expname]);
     874   
     875        // loop through rows
     876        while ($row = $stmt->fetch(PDO::FETCH_NUM)) {
    710877            echo "<tr>";
    711 
    712             echo "<td>$row[0]</td>";
    713             echo "<td>$row[1]</td>";
    714             echo "<td>$row[2]</td>";
    715 
     878            echo "<td>" . htmlspecialchars($row[0]) . "</td>";  // exp_name
     879            echo "<td>" . htmlspecialchars($row[1]) . "</td>";  // warp_id
     880            echo "<td>" . htmlspecialchars($row[4]) . "</td>";  // comment
    716881            echo "</tr>";
    717882        }
    718883    }
    719884
    720     #-----------------------------------------
    721     #list bad quality diff exposures
    722885    if ($baddiffflag != 0) {
    723886        // set up another table
     
    734897        createTableColumnHeader("stack2");
    735898        createTableColumnHeader("comment");
    736 
    737         #query the bad diffs
    738         $query = "     SELECT diff_id,warp1,stack1,warp2,stack2,exp_name,comment FROM ";
    739         $query .= "           diffInputSkyfile JOIN warpRun ON (warp1=warp_id OR warp2=warp_id) JOIN fakeRun USING (fake_id) JOIN camRun USING (cam_id) JOIN camProcessedExp USING (cam_id) JOIN chipRun USING (chip_id) JOIN rawExp USING (exp_id)";
    740         $query .= "           WHERE exp_name like '$expname'";
    741         $query .= "           AND (obs_mode LIKE '%SS%' OR obs_mode LIKE '%BRIGHT%') AND obs_mode NOT LIKE 'ENGINEERING' AND obs_mode NOT LIKE 'MANUAL'";
    742         $query .= "           GROUP By diff_id";
    743         if ($debug) {echo "$query<br>";}
    744 
    745         $qry1 = $projectdb->query($query);
    746         while ($qry1->fetchInto($row)) {
    747             #distinguish between WW and WS diffs based on stack2
    748             if ($row[4] == 0) {
    749                   #find the total number of skycell and the number of skycell with a bad quality flag as well as the full publish state
    750                   $query3 = "     SELECT count(diffSkyfile.quality) AS Ndiff,count(if(diffSkyfile.quality > 0,1,NULL)) AS Ndiffbad,count(if(publishRun.state LIKE 'full',1,NULL)) AS Npub FROM ";
    751                   $query3 .= "           diffRun JOIN diffSkyfile USING (diff_id) LEFT JOIN publishRun ON (diffRun.diff_id = stage_id)";
    752                   $query3 .= "           WHERE diff_id='$row[0]' GROUP BY diffRun.diff_id";
    753                   $qry3 = $projectdb->query($query3);
    754 
    755                   while ($qry3->fetchInto($row3)) {
    756                       #if all skycells have bad quality, this is a bad diff. If all the skycells are in a full state in publishRun, it is fully published
    757                       if ($row3[1]==$row3[0]) {
    758                           echo "<tr>";
    759 
    760                           echo "<td>$row[5]</td>";
    761                           echo "<td>$row[0]</td>";
    762                           echo "<td>$row[1]</td>";
    763                           echo "<td>$row[2]</td>";
    764                           echo "<td>$row[3]</td>";
    765                           echo "<td>$row[4]</td>";
    766                           echo "<td>$row[6]</td>";
    767 
    768                           echo "</tr>";
    769                       }
    770                   }
    771             }
    772 
    773             if ($row[4] > 0) {
    774                   #find the total number of skycell and the number of skycell with a bad quality flag as well as the full publish state
    775                   $query3 = "     SELECT count(diffSkyfile.quality) AS Ndiff,count(if(diffSkyfile.quality > 0,1,NULL)) AS Ndiffbad,count(if(publishRun.state LIKE 'full',1,NULL)) AS Npub FROM ";
    776                   $query3 .= "           diffRun JOIN diffSkyfile USING (diff_id) LEFT JOIN publishRun ON (diffRun.diff_id = stage_id)";
    777                   $query3 .= "           WHERE diff_id='$row[0]' GROUP BY diffRun.diff_id";
    778                   $qry3 = $projectdb->query($query3);
    779 
    780                   while ($qry3->fetchInto($row3)) {
    781                       #if all skycells have bad quality, this is a bad diff. If all the skycells are in a full state in publishRun, it is fully published
    782                       if ($row3[1]==$row3[0]) {
    783                           echo "<tr>";
    784 
    785                           echo "<td>$row[5]</td>";
    786                           echo "<td>$row[0]</td>";
    787                           echo "<td>$row[1]</td>";
    788                           echo "<td>$row[2]</td>";
    789                           echo "<td>$row[3]</td>";
    790                           echo "<td>$row[4]</td>";
    791                           echo "<td>$row[6]</td>";
    792 
    793                           echo "</tr>";
    794                        }
    795                   }
     899   
     900        // main query with a placeholder for exp_name
     901        $query = "
     902            SELECT diff_id, warp1, stack1, warp2, stack2, exp_name, comment
     903            FROM diffInputSkyfile
     904            JOIN warpRun ON (warp1=warp_id OR warp2=warp_id)
     905            JOIN fakeRun USING (fake_id)
     906            JOIN camRun USING (cam_id)
     907            JOIN camProcessedExp USING (cam_id)
     908            JOIN chipRun USING (chip_id)
     909            JOIN rawExp USING (exp_id)
     910            WHERE exp_name LIKE :expname
     911            AND (obs_mode LIKE '%SS%' OR obs_mode LIKE '%BRIGHT%')
     912            AND obs_mode NOT LIKE 'ENGINEERING'
     913            AND obs_mode NOT LIKE 'MANUAL'
     914            GROUP BY diff_id
     915        ";
     916   
     917        if ($debug) { echo "$query<br>"; }
     918   
     919        // prepare & execute main query
     920        $stmt = $projectdb->prepare($query);
     921        $stmt->execute([':expname' => $expname]);
     922   
     923        // prepare secondary query once, reuse it
     924        $query3 = "
     925            SELECT
     926                COUNT(diffSkyfile.quality) AS Ndiff,
     927                COUNT(IF(diffSkyfile.quality > 0,1,NULL)) AS Ndiffbad,
     928                COUNT(IF(publishRun.state LIKE 'full',1,NULL)) AS Npub
     929            FROM diffRun
     930            JOIN diffSkyfile USING (diff_id)
     931            LEFT JOIN publishRun ON (diffRun.diff_id = stage_id)
     932            WHERE diff_id = :diffid
     933            GROUP BY diffRun.diff_id
     934        ";
     935
     936        if ($debug) { echo "$query3<br>"; }
     937
     938        $stmt3 = $projectdb->prepare($query3);
     939   
     940        // iterate through main query results
     941        while ($row = $stmt->fetch(PDO::FETCH_NUM)) {
     942   
     943            // run the nested query regardless of warp2==0 or >0
     944            $stmt3->execute([':diffid' => $row[0]]);
     945   
     946            while ($row3 = $stmt3->fetch(PDO::FETCH_NUM)) {
     947                // if all skycells have bad quality
     948                if ($row3[1] == $row3[0]) {
     949                    echo "<tr>";
     950                    echo "<td>" . htmlspecialchars($row[5]) . "</td>"; // exp_name
     951                    echo "<td>" . htmlspecialchars($row[0]) . "</td>"; // diff_id
     952                    echo "<td>" . htmlspecialchars($row[1]) . "</td>"; // warp1
     953                    echo "<td>" . htmlspecialchars($row[2]) . "</td>"; // stack1
     954                    echo "<td>" . htmlspecialchars($row[3]) . "</td>"; // warp2
     955                    echo "<td>" . htmlspecialchars($row[4]) . "</td>"; // stack2
     956                    echo "<td>" . htmlspecialchars($row[6]) . "</td>"; // comment
     957                    echo "</tr>";
     958                }
    796959            }
    797960        }
     
    800963    #-----------------------------------------
    801964    #check for overridden exposures
    802     $query = "SELECT COUNT(comment), comment";
    803     $query .= " FROM rawExp";
    804     $query .= " JOIN chipRun USING (exp_id)";
    805     $query .= " JOIN camRun USING (chip_id)";
    806     $query .= " WHERE exp_name like '$expname'";
    807     $query .= " AND exp_type = 'OBJECT'";
    808     $query .= " AND (obs_mode LIKE '%SS%' OR obs_mode LIKE '%BRIGHT%') AND obs_mode NOT LIKE 'ENGINEERING' AND obs_mode NOT LIKE 'MANUAL'";
    809     $query .= " GROUP BY comment";
    810 
    811     $qry1 = $projectdb->query($query);
    812     $doHeader = 1;
    813 
    814     while ($qry1->fetchInto($row)) {
     965    // first query: count per comment
     966    $query1 = "
     967        SELECT COUNT(comment) AS cnt, comment
     968        FROM rawExp
     969        JOIN chipRun USING (exp_id)
     970        JOIN camRun USING (chip_id)
     971        WHERE exp_name LIKE :expname
     972          AND exp_type = 'OBJECT'
     973          AND (obs_mode LIKE '%SS%' OR obs_mode LIKE '%BRIGHT%')
     974          AND obs_mode NOT LIKE 'ENGINEERING'
     975          AND obs_mode NOT LIKE 'MANUAL'
     976        GROUP BY comment
     977    ";
     978    $stmt1 = $projectdb->prepare($query1);
     979    $stmt1->execute([':expname' => $expname]);
     980
     981    if ($debug) { echo "$query1<br>"; }
     982   
     983    $doHeader = 1;
     984   
     985    // loop over comments with duplicates
     986    while ($row = $stmt1->fetch(PDO::FETCH_NUM)) {
    815987        if ($row[0] > 1) {
    816             $query = "SELECT exp_name,dateobs, comment";
    817             $query .= " FROM rawExp";
    818             $query .= " JOIN chipRun USING (exp_id)";
    819             $query .= " JOIN camRun USING (chip_id)";
    820             $query .= " WHERE exp_name like '$expname'";
    821             $query .= " AND comment LIKE '$row[1]'";
    822             $query .= " AND exp_type = 'OBJECT'";
    823             $query .= " AND (obs_mode LIKE '%SS%' OR obs_mode LIKE '%BRIGHT%') AND obs_mode NOT LIKE 'ENGINEERING' AND obs_mode NOT LIKE 'MANUAL'";
    824             $query .= " ORDER BY dateobs DESC";
    825             $qry2 = $projectdb->query($query);
     988            // second query: get exposures with this comment
     989            $query2 = "
     990                SELECT exp_name, dateobs, comment
     991                FROM rawExp
     992                JOIN chipRun USING (exp_id)
     993                JOIN camRun USING (chip_id)
     994                WHERE exp_name LIKE :expname
     995                  AND comment LIKE :comment
     996                  AND exp_type = 'OBJECT'
     997                  AND (obs_mode LIKE '%SS%' OR obs_mode LIKE '%BRIGHT%')
     998                  AND obs_mode NOT LIKE 'ENGINEERING'
     999                  AND obs_mode NOT LIKE 'MANUAL'
     1000                ORDER BY dateobs DESC
     1001            ";
     1002            $stmt2 = $projectdb->prepare($query2);
     1003            $stmt2->execute([
     1004                ':expname' => $expname,
     1005                ':comment' => $row[1]
     1006            ]);
     1007   
     1008            if ($debug) { echo "$query2<br>"; }
    8261009
    8271010            $doskip = 0;
    828             while ($qry2->fetchInto($row2)) {
    829                if ($doskip) {
     1011            while ($row2 = $stmt2->fetch(PDO::FETCH_NUM)) {
     1012                if ($doskip) {
    8301013                    if ($doHeader) {
    831                        // set up another table
    832                        global $table;
    833                        echo $table;
    834                        echo "<tr>";
    835                        createTableTitle("Overridden exposures", 3);
    836                        echo "<tr>";
    837                        createTableColumnHeader("exp_name");
    838                        createTableColumnHeader("dateobs");
    839                        createTableColumnHeader("comment");
    840                        $doHeader = 0;
     1014                        // set up another table
     1015                        global $table;
     1016                        echo $table;
     1017                        echo "<tr>";
     1018                        createTableTitle("Overridden exposures", 3);
     1019                        echo "<tr>";
     1020                        createTableColumnHeader("exp_name");
     1021                        createTableColumnHeader("dateobs");
     1022                        createTableColumnHeader("comment");
     1023                        $doHeader = 0;
    8411024                    }
     1025   
    8421026                    echo "<tr>";
    843 
    844                     echo "<td>$row2[0]</td>";
    845                     echo "<td>$row2[1]</td>";
    846                     echo "<td>$row2[2]</td>";
    847 
     1027                    echo "<td>" . htmlspecialchars($row2[0]) . "</td>";
     1028                    echo "<td>" . htmlspecialchars($row2[1]) . "</td>";
     1029                    echo "<td>" . htmlspecialchars($row2[2]) . "</td>";
    8481030                    echo "</tr>";
    8491031                }
    850                 $doskip = 1;
     1032                $doskip = 1; // skip the first row of each comment group
    8511033            }
    8521034        }
    8531035    }
     1036   
    8541037    if (!$doHeader) { print "\n"; }
    855 
    8561038
    8571039    // End timer
     
    8701052#
    8711053###########################################################################
    872 function getLabels($db, $proj, $server) {
    873 
    874     # order by descending priority as set in project database
    875     $sql = "SELECT label FROM science_labels WHERE server LIKE '$server' AND telescope LIKE '$proj' ORDER BY priority DESC, label";
    876     if ($debug) {echo "$sql<br>";}
    877 
    878     $qry = $db->query($sql);
    879     if (dberror($qry)) {echo "<b>error with $sql </b><br>\n";}
    880     while ($qry->fetchInto($row)) {
    881 
    882         $labels[] = $row[0];
    883     }
    884 
    885     return $labels;
     1054function getLabels(PDO $db, string $proj, string $server, bool $useLike = false, bool $debug = false): array {
     1055    // Use equality unless $useLike is true (then add wildcards)
     1056    if ($useLike) {
     1057        $sql = "SELECT label FROM science_labels
     1058                WHERE server LIKE :server AND telescope LIKE :proj
     1059                ORDER BY priority DESC, label";
     1060        // add wildcards only if caller didn't already include them
     1061        $serverParam = (strpos($server, '%') === false && strpos($server, '_') === false) ? "%{$server}%" : $server;
     1062        $projParam   = (strpos($proj, '%') === false && strpos($proj, '_') === false)   ? "%{$proj}%"   : $proj;
     1063    } else {
     1064        $sql = "SELECT label FROM science_labels
     1065                WHERE server = :server AND telescope = :proj
     1066                ORDER BY priority DESC, label";
     1067        $serverParam = $server;
     1068        $projParam   = $proj;
     1069    }
     1070
     1071    if ($debug) { echo htmlspecialchars($sql) . "<br>\n"; }
     1072
     1073    try {
     1074        $stmt = $db->prepare($sql);
     1075        $stmt->execute([':server' => $serverParam, ':proj' => $projParam]);
     1076
     1077        // fetch all values from the first (and only) column
     1078        $labels = $stmt->fetchAll(PDO::FETCH_COLUMN);
     1079        return $labels ?: []; // ensure an array is always returned
     1080    } catch (PDOException $e) {
     1081        // handle/log error in a way that suits your app
     1082        if ($debug) {
     1083            echo "<b>DB error:</b> " . htmlspecialchars($e->getMessage()) . "<br>\n";
     1084        }
     1085        return []; // fail gracefully
     1086    }
    8861087}
    8871088
     
    9821183
    9831184        foreach ($stages as &$stage) {
     1185          global $addSearchState; // add this line if inside a function
    9841186          // Note: Do not include the state for chip, cam, and warp so that both new and update faults are listed
    985       $myTable    = $mainTable[$stage];
    986       $myFailures = $getFailures[$stage];
     1187          $myTable    = $mainTable[$stage];
     1188          $myFailures = $getFailures[$stage];
    9871189          # echo "myTable: $myTable";
    9881190
    989       if ("$myFailures" == "NONE") {
     1191          if ("$myFailures" == "NONE") {
    9901192            $link = $defaultlink;
    9911193          } else {
    9921194            $link = $myFailures . "?pass=" . $pass . "&proj=" . $proj . "&" . $myTable . ".label=" . $thisLabel;
    993         if ($addSearchState[$stage]) {
    994               // Note: Do not include the state for chip, cam, and warp so that both new and update faults are listed
    995               $link .= "&" . $myTable . ".state=" . $searchState;
    996             }
     1195              if ($addSearchState[$stage]) {
     1196                // Note: Do not include the state for chip, cam, and warp so that both new and update faults are listed
     1197                $link .= "&" . $myTable . ".state=" . $searchState;
     1198              }
    9971199          }
    998       getStateAndFaults($db, $thisLabel, $proj, $selectedState, $stage, $str, $anyFaults);
    999       //echo "getStateAndFaults $db, $thisLabel, $proj, $selectedState, $stage, $str, $anyFaults<br>";
    1000 
    1001       $cellcolor = 0;
    1002       if ("$stage" == "dist") {
    1003         $cellcolor = (!$isUpdate && !$distributing) ? "gray" : 0;
    1004           }
    1005           if ("$stage" == "pub") {
    1006         $cellcolor = (!$isUpdate && !$publishing) ? "gray" : 0;
    1007           }
    1008           createFormattedTableCell($anyFaults, $link, $str, $anyFaults, $cellcolor);
     1200          getStateAndFaults($db, $thisLabel, $proj, $selectedState, $stage, $str, $anyFaults);
     1201
     1202          $cellcolor = 0;
     1203          if ("$stage" == "dist") {
     1204            $cellcolor = (!$isUpdate && !$distributing) ? "gray" : 0;
     1205              }
     1206              if ("$stage" == "pub") {
     1207            $cellcolor = (!$isUpdate && !$publishing) ? "gray" : 0;
     1208              }
     1209              createFormattedTableCell($anyFaults, $link, $str, $anyFaults, $cellcolor);
    10091210        }
    10101211        echo "</tr>\n";
     
    10991300#
    11001301###########################################################################
    1101 function getNightlyScienceStatus($db, $proj) {
     1302function getNightlyScienceStatus_old($db, $proj) {
    11021303
    11031304    $sql = "SELECT status FROM science_nightlyscience WHERE telescope LIKE '$proj'";
     
    11091310
    11101311    return $row[0];
     1312}
     1313
     1314function getNightlyScienceStatus(PDO $db, $proj, $debug = false) {
     1315    $sql = "SELECT status FROM science_nightlyscience WHERE telescope LIKE :proj";
     1316    $stmt = $db->prepare($sql);
     1317    $stmt->execute([':proj' => "%$proj%"]); // add wildcards
     1318    $row = $stmt->fetch(PDO::FETCH_ASSOC);
     1319    return $row ? $row['status'] : '';
    11111320}
    11121321
     
    11321341###########################################################################
    11331342function getRevertStatus($db, $proj, $stage) {
    1134 
     1343    // Note: The $db parameter is now a PDO object.
     1344
     1345    // Use a prepared statement to prevent SQL injection.
     1346    $sql = "SELECT reverting FROM science_reverts WHERE stage LIKE ? AND telescope LIKE ?";
     1347
     1348    try {
     1349        $stmt = $db->prepare($sql);
     1350        $stmt->execute([$stage, $proj]);
     1351
     1352        // Fetch a single column from the next row
     1353        return $stmt->fetchColumn();
     1354
     1355    } catch (PDOException $e) {
     1356        // Handle database errors
     1357        error_log("Database error in getRevertStatus: " . $e->getMessage());
     1358        return null; // Return a safe default value
     1359    }
     1360}
     1361
     1362###########################################################################
     1363#
     1364# Returns state and fault-count (if new) as a string
     1365#
     1366###########################################################################
     1367function getStateAndFaults($db, $label, $proj, $state, $stage, &$str, &$anyFaults) {
    11351368    $anyFaults = false;
    11361369
    1137     $sql = "SELECT reverting FROM science_reverts WHERE stage LIKE '$stage' AND telescope LIKE '$proj'";
    1138     if ($debug) {echo "$sql<br>";}
    1139 
    1140     $qry = $db->query($sql);
    1141     if (dberror($qry)) {echo "<b>error with $sql </b><br>\n";}
    1142     $qry->fetchInto($row);
    1143 
    1144     return $row[0];
    1145 }
    1146 
    1147 ###########################################################################
    1148 #
    1149 # Returns state and fault-count (if new) as a string
    1150 #
    1151 ###########################################################################
    1152 function getStateAndFaults($db, $label, $proj, $state, $stage, &$str, &$anyFaults) {
    1153 
    1154     $anyFaults = false;
    1155     $sql = "SELECT pending, faults FROM $stage WHERE label LIKE '$label' AND dbname LIKE '$proj' ORDER BY timestamp DESC LIMIT 1";
    1156     if ($debug) {echo "$sql<br>";}
    1157 
    1158     $qry = $db->query($sql);
    1159     if (dberror($qry)) {echo "<b>error with $sql </b><br>\n";}
    1160     $qry->fetchInto($row);
    1161 
    1162     $pending = $row[0];
    1163     $faults = $row[1];
    1164 
    1165     if ($pending == 0)
    1166         $str = "";
    1167     else
    1168         $str = "$pending";
    1169 
    1170     if ($state == "new") {
    1171 
    1172         if ($faults > 0) {
    1173 
    1174             $str = $str."(".$faults.")";
    1175             $anyFaults = true;
     1370    // Use a prepared statement to prevent SQL injection.
     1371    $sql = "SELECT pending, faults FROM $stage WHERE label LIKE ? AND dbname LIKE ? ORDER BY timestamp DESC LIMIT 1";
     1372
     1373    try {
     1374        $stmt = $db->prepare($sql);
     1375        $stmt->execute([$label, $proj]);
     1376
     1377        // Fetch the result as a numerically indexed array
     1378        $row = $stmt->fetch(PDO::FETCH_NUM);
     1379
     1380        if ($row) {
     1381            $pending = $row[0];
     1382            $faults = $row[1];
     1383
     1384            if ($pending == 0) {
     1385                $str = "";
     1386            } else {
     1387                $str = "$pending";
     1388            }
     1389
     1390            if ($state == "new" && $faults > 0) {
     1391                $str .= "($faults)";
     1392                $anyFaults = true;
     1393            }
     1394        } else {
     1395            // Handle case where no row is found
     1396            $pending = null;
     1397            $faults = null;
    11761398        }
     1399
     1400    } catch (PDOException $e) {
     1401        // Handle database errors
     1402        error_log("Database error in getStateAndFaults: " . $e->getMessage());
     1403        $pending = null;
     1404        $faults = null;
     1405        // Optionally, you can set a default message for $str and $anyFaults
    11771406    }
    11781407}
     
    12101439}
    12111440
    1212 
    12131441###########################################################################
    12141442#
     
    12171445###########################################################################
    12181446function createServerDateRow($db, $proj, $server) {
    1219 
    1220     $sql = "SELECT date FROM science_server_dates WHERE server LIKE '$server' AND telescope LIKE '$proj' ORDER BY date";
    1221     if ($debug) {echo "$sql<br>";}
    1222 
    1223     $qry = $db->query($sql);
    1224     if (dberror($qry)) {echo "<b>error with $sql </b><br>\n";}
    1225     $dates = "";
    1226     $firstIn = true;
    1227     while ($qry->fetchInto($row)) {
    1228         if ($firstIn) {
    1229             $dates = $row[0];
    1230             $firstIn = false;
     1447    // Note: The $db parameter is now a PDO object.
     1448
     1449    // Use a prepared statement to prevent SQL injection.
     1450    $sql = "SELECT date FROM science_server_dates WHERE server LIKE ? AND telescope LIKE ? ORDER BY date";
     1451
     1452    try {
     1453        $stmt = $db->prepare($sql);
     1454        $stmt->execute([$server, $proj]);
     1455
     1456        $dates = "";
     1457        $firstIn = true;
     1458        while ($row = $stmt->fetch(PDO::FETCH_NUM)) {
     1459            if ($firstIn) {
     1460                $dates = $row[0];
     1461                $firstIn = false;
     1462            } else {
     1463                $dates .= ", " . $row[0];
     1464            }
    12311465        }
    1232         else $dates = $dates . ", " . $row[0];
     1466    } catch (PDOException $e) {
     1467        // Handle database errors
     1468        error_log("Database error in createServerDateRow: " . $e->getMessage());
     1469        $dates = "DB ERROR";
    12331470    }
    12341471
     
    12371474    echo "<td>$dates</td>";
    12381475    echo "</tr>";
    1239 
    12401476}
    12411477
     
    13331569#
    13341570###########################################################################
    1335 function getServerStatus($db,$proj, $server, &$alive, &$running, &$host) {
    1336 
    1337 #    $sql = "SELECT alive, running FROM science_servers WHERE server LIKE '$server' AND telescope LIKE '$proj' ORDER BY timestamp DESC LIMIT 1";
    1338     $sql = "SELECT alive, running, host FROM live_servers WHERE server LIKE '$server' AND telescope LIKE '$proj' ORDER BY timestamp DESC LIMIT 1";
    1339     if($debug){echo "$sql<br>";}
    1340 
    1341     $qry = $db->query($sql);
    1342     if (dberror($qry)) {echo "<b>error with $sql </b><br>\n";}
    1343     $qry->fetchInto($row);
    1344 
    1345     $alive = $row[0];
    1346     $running = $row[1];
    1347     $host = $row[2];
     1571
     1572function getServerStatus($db, $proj, $server, &$alive, &$running, &$host) {
     1573    // Note: The $db parameter is now expected to be a PDO object.
     1574
     1575    // Use a prepared statement to prevent SQL injection.
     1576    $sql = "SELECT alive, running, host FROM live_servers WHERE server LIKE ? AND telescope LIKE ? ORDER BY timestamp DESC LIMIT 1";
     1577
     1578    try {
     1579        $stmt = $db->prepare($sql);
     1580
     1581        // Bind parameters and execute the query
     1582        $stmt->execute([$server, $proj]);
     1583       
     1584        // Fetch the result as a numerically indexed array, matching the old behavior
     1585        $row = $stmt->fetch(PDO::FETCH_NUM);
     1586
     1587        if ($row) {
     1588            $alive = $row[0];
     1589            $running = $row[1];
     1590            $host = $row[2];
     1591        } else {
     1592            // Handle case where no data is found for the server
     1593            $alive = null;
     1594            $running = null;
     1595            $host = null;
     1596        }
     1597
     1598    } catch (PDOException $e) {
     1599        // Handle database errors
     1600        error_log("Database error in getServerStatus: " . $e->getMessage());
     1601        $alive = null;
     1602        $running = null;
     1603        $host = null;
     1604    }
    13481605}
    13491606
     
    13531610#
    13541611###########################################################################
    1355 function getLastUpdateTime($db) {
    1356 
    1357     $anyFaults = false;
     1612function getLastUpdateTime(PDO $db) {
    13581613    $sql = "SELECT timestamp FROM chip ORDER BY timestamp DESC LIMIT 1";
    1359     if ($debug) {echo "$sql<br>";}
    1360 
    1361     $qry = $db->query($sql);
    1362     if (dberror($qry)) {echo "<b>error with $sql </b><br>\n";}
    1363     $qry->fetchInto($row);
    1364 
    1365     return $row[0];
     1614   
     1615    // run the query
     1616    $stmt = $db->query($sql);
     1617    if (!$stmt) {
     1618        // if query fails, print error info
     1619        $errorInfo = $db->errorInfo();
     1620        echo "<b>Error with $sql:</b> " . $errorInfo[2] . "<br>\n";
     1621        return null;
     1622    }
     1623
     1624    // fetch the first row as numeric array
     1625    $row = $stmt->fetch(PDO::FETCH_NUM);
     1626
     1627    return $row ? $row[0] : null; // return timestamp or null
    13661628}
    13671629
     
    13911653###########################################################################
    13921654function showReplicationsStatus($replHost, $replUser, $replPassword, $replDatabaseName) {
    1393     //print "<br>$replHost, $replUser, $replPassword, $replDatabaseName<br/>";
    13941655    global $czardb;
    13951656
    1396     // Prepare storage info early, https://panstarrs.atlassian.net/browse/IPP-2338
    13971657    $storageInfo = '';
    13981658    $hostPrefixParts = explode(' ', $replDatabaseName);
    1399     $hostPrefix = addslashes($hostPrefixParts[0]);
    1400 
    1401     $sql = sprintf(
    1402         "SELECT host, FORMAT(total,1), FORMAT(available,2), FORMAT(used,2), writable, readable, FORMAT(used/total*100,1)
    1403          FROM hosts
    1404          WHERE host LIKE '%s%%' order by available desc
    1405          LIMIT 1",
    1406          $hostPrefix
    1407     );
    1408 
    1409     $qry = $czardb->query($sql);
    1410     if (PEAR::isError($qry)) {
    1411         $storageInfo = "<td style=\"background-color: red; color: white;\">Storage query error</td>";
    1412     } else {
    1413         if ($qry->fetchInto($row)) {
     1659    $hostPrefix = $hostPrefixParts[0];
     1660
     1661    // Using a prepared statement with a placeholder for security
     1662    $sql = "SELECT host, FORMAT(total,1), FORMAT(available,2), FORMAT(used,2), writable, readable, FORMAT(used/total*100,1)
     1663            FROM hosts
     1664            WHERE host LIKE ?
     1665            ORDER BY available DESC
     1666            LIMIT 1";
     1667
     1668    try {
     1669        $stmt = $czardb->prepare($sql);
     1670        $stmt->execute(["$hostPrefix%"]);
     1671        $row = $stmt->fetch(PDO::FETCH_NUM);
     1672
     1673        if ($row) {
    14141674            list($host, $total, $available, $used, $writable, $readable, $ratioStr) = $row;
    1415             $ratio = floatval($ratioStr);  // convert to number
    1416    
    1417             // Determine color
     1675            $ratio = floatval($ratioStr);
     1676
    14181677            if ($ratio < 80) {
    14191678                $color = 'lightgreen';
     
    14231682                $color = 'pink';
    14241683            }
    1425    
     1684
    14261685            $storageInfo = "<td style=\"background-color: $color;\">$ratioStr% of $total</td>";
    14271686        } else {
    14281687            $storageInfo = "<td>No host storage data</td>";
    14291688        }
    1430     }
    1431 
    1432     $dbRepl = DB::connect("mysql://$replUser:$replPassword@$replHost");
    1433     if (PEAR::isError($dbRepl)) {
     1689    } catch (PDOException $e) {
     1690        $storageInfo = "<td style=\"background-color: red; color: white;\">Storage query error</td>";
     1691    }
     1692
     1693    // Connect to the replication database using PDO
     1694    try {
     1695        $dbRepl = new PDO("mysql:host=$replHost;", $replUser, $replPassword);
     1696        $dbRepl->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
     1697    } catch (PDOException $e) {
    14341698        echo "<tr><td>$replDatabaseName</td>$storageInfo<td style=\"background-color: pink;\">MySQL DB connection error - check configuration</td></tr>";
    1435         return -1; // Don't stop the rest of the script
     1699        return -1;
    14361700    }
    14371701
    14381702    // Query replication status
    1439     $res = $dbRepl->query('SHOW SLAVE STATUS');
    1440     if (PEAR::isError($res)) {
     1703    try {
     1704        $res = $dbRepl->query('SHOW SLAVE STATUS');
     1705        $row = $res->fetch(PDO::FETCH_NUM);
     1706    } catch (PDOException $e) {
    14411707        echo "<tr><td>$replDatabaseName</td>$storageInfo<td style=\"background-color: pink;\">MySQL query error - invalid user or privileges</td></tr>";
    1442         $dbRepl->disconnect();
     1708        $dbRepl = null;
    14431709        return -1;
    14441710    }
    14451711
    1446     if (!$res->fetchInto($row)) {
     1712    if (!$row) {
    14471713        echo "<tr><td>$replDatabaseName</td>$storageInfo<td style=\"background-color: pink;\">No replication data available</td></tr>";
    1448         $dbRepl->disconnect();
     1714        $dbRepl = null;
    14491715        return 0;
    14501716    }
    14511717
     1718    // Note: The index for 'Seconds_Behind_Master' and 'Last_IO_Error' might vary based on MySQL version.
     1719    // It's safer to use PDO::FETCH_ASSOC and the column names.
    14521720    $errorStatusInMySql = $row[18];
    14531721    $sec_behind = $row[32];
     
    14631731
    14641732    echo "<tr><td>$replDatabaseName</td>$storageInfo";
    1465 
    14661733    echo "<td>$replStatus</td></tr>";
    14671734
     
    14711738    }
    14721739
    1473     $dbRepl->disconnect();
    1474 
    1475 }
    1476 
     1740    $dbRepl = null; // Close connection
     1741}
    14771742###########################################################################
    14781743#
     
    14811746###########################################################################
    14821747function showDatabaseStatus($Host, $User, $Password, $DatabaseName) {
    1483     //print "<br>$Host, $User, $Password, $DatabaseName<br/>";
    14841748    global $czardb;
    1485     // Prepare storage info early, https://panstarrs.atlassian.net/browse/IPP-2338
    14861749    $storageInfo = '';
    14871750    $hostPrefixParts = explode(' ', $DatabaseName);
    14881751    $hostPrefix = addslashes($hostPrefixParts[0]);
    14891752
    1490     $sql = sprintf(
    1491         "SELECT host, FORMAT(total,1), FORMAT(available,2), FORMAT(used,2), writable, readable, FORMAT(used/total*100,1)
    1492          FROM hosts
    1493          WHERE host LIKE '%s%%' order by available desc
    1494          LIMIT 1",
    1495          $hostPrefix
    1496     );
    1497 
    1498     $qry = $czardb->query($sql);
    1499     if (PEAR::isError($qry)) {
    1500         $storageInfo = "<td style=\"background-color: red; color: white;\">Storage query error</td>";
    1501     } else {
    1502         if ($qry->fetchInto($row)) {
     1753    $sql = "SELECT host, FORMAT(total,1), FORMAT(available,2), FORMAT(used,2), writable, readable, FORMAT(used/total*100,1)
     1754            FROM hosts
     1755            WHERE host LIKE ?
     1756            ORDER BY available DESC
     1757            LIMIT 1";
     1758
     1759    try {
     1760        $stmt = $czardb->prepare($sql);
     1761        $stmt->execute(["$hostPrefix%"]);
     1762        $row = $stmt->fetch(PDO::FETCH_NUM);
     1763
     1764        if ($row) {
    15031765            list($host, $total, $available, $used, $writable, $readable, $ratioStr) = $row;
    1504             $ratio = floatval($ratioStr);  // convert to number
    1505    
    1506             // Determine color
     1766            $ratio = floatval($ratioStr);
     1767
    15071768            if ($ratio < 80) {
    15081769                $color = 'lightgreen';
     
    15121773                $color = 'pink';
    15131774            }
    1514    
     1775
    15151776            $storageInfo = "<td style=\"background-color: $color;\">$ratioStr% of $total</td>";
    15161777        } else {
    15171778            $storageInfo = "<td>No host storage data</td>";
    15181779        }
    1519     }
    1520 
    1521     $db = DB::connect("mysql://$User:$Password@$Host");
    1522     if (PEAR::isError($db)) {
     1780    } catch (PDOException $e) {
     1781        $storageInfo = "<td style=\"background-color: red; color: white;\">Storage query error</td>";
     1782    }
     1783
     1784    try {
     1785        $db = new PDO("mysql:host=$Host;", $User, $Password);
     1786        $db->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
     1787        $Status = "OK";
     1788    } catch (PDOException $e) {
     1789        $Status = "<font style=\"BACKGROUND-COLOR: yellow\" color=\"red\">PROBLEM</font>";
    15231790        echo "<tr><td>$DatabaseName</td>$storageInfo<td>$Status</td>";
    15241791        return -1;
    15251792    }
    1526     $Status = ($errorStatusInMySql==0?"OK":"<font style=\"BACKGROUND-COLOR: yellow\"  color=\"red\">PROBLEM</font>");
     1793
    15271794    echo "<tr><td>$DatabaseName</td>$storageInfo<td>$Status</td>";
    1528 
    1529     $db->disconnect();
     1795    $db = null; // Close connection
    15301796}
    15311797
     
    15711837#
    15721838###########################################################################
    1573 function createSummitDataTable2($projectdb, $pass, $proj, $debug) {
    1574 
     1839function createSummitDataTable($projectdb, $pass, $proj, $debug) {
    15751840    // timer start
    15761841    $start = microtime(true);
    15771842
    15781843    // Define constants
    1579     $mjdDay = getMJD();
    1580     $expname = "%{$mjdDay}%";
    1581 
     1844    $mjdDay   = getMJD();
     1845    $expname  = "%{$mjdDay}%";
    15821846    $mjdDaym1 = $mjdDay - 1;
    15831847    $expnamem1 = "%{$mjdDaym1}%";
    1584 
    15851848    $mjdDaym5 = $mjdDay - 5;
    15861849    $expnamem5 = "o{$mjdDaym5}%";
    15871850
    1588     $currentDate = gmdate("Y-m-d H:i:s");
     1851    $currentDate  = gmdate("Y-m-d H:i:s");
    15891852    $previousDate = gmdate("Y-m-d", strtotime("-1 day"));
    15901853
     
    15971860    echo "<tr>";
    15981861      createTableColumnHeader("Exposure type");
    1599       createTableColumnHeader("<a href=\"czartool_labels.php?pass=".$pass."&proj=".$proj."&label=all&stage=summitExp\"><font color=\"blue\">summitExp status</font></a>");
    1600       createTableColumnHeader("<a href=\"czartool_labels.php?pass=".$pass."&proj=".$proj."&label=all&stage=downloadExp\"><font color=\"blue\">downloadExp status</font></a>");
    1601       createTableColumnHeader("<a href=\"czartool_labels.php?pass=".$pass."&proj=".$proj."&label=all&stage=rawExp\"><font color=\"blue\">rawExp </a>/
    1602                                <a href=\"czartool_labels.php?pass=".$pass."&proj=".$proj."&label=all&stage=newExp\"><font color=\"blue\"> newExp</font></a>");
    1603 
     1862      createTableColumnHeader("<a href=\"czartool_labels.php?pass=$pass&proj=$proj&label=all&stage=summitExp\"><font color=\"blue\">summitExp status</font></a>");
     1863      createTableColumnHeader("<a href=\"czartool_labels.php?pass=$pass&proj=$proj&label=all&stage=downloadExp\"><font color=\"blue\">downloadExp status</font></a>");
     1864      createTableColumnHeader("<a href=\"czartool_labels.php?pass=$pass&proj=$proj&label=all&stage=rawExp\"><font color=\"blue\">rawExp</font></a> /
     1865                               <a href=\"czartool_labels.php?pass=$pass&proj=$proj&label=all&stage=newExp\"><font color=\"blue\">newExp</font></a>");
     1866
     1867    // --- Query 1 ---
    16041868    $sql = "
    1605         SELECT sum_type, pzstate, rawstate, count(*), sum_exp, pz_exp, raw_exp
    1606         FROM
    1607           (SELECT summitExp.exp_type as sum_type, pzDownloadExp.state as pzstate, rawExp.state as rawstate, summitExp.exp_name as sum_exp, pzDownloadExp.exp_name as pz_exp, rawExp.exp_name as raw_exp
    1608            FROM summitExp
    1609            LEFT JOIN pzDownloadExp USING (summit_id)
    1610            LEFT JOIN rawExp ON summitExp.exp_name = rawExp.exp_name
    1611            WHERE summitExp.summit_id >= (
    1612                SELECT COALESCE(MIN(summit_id), 0)
    1613                FROM summitExp
    1614                WHERE exp_name LIKE '$expnamem5'
    1615             )
    1616             AND summitExp.exp_name LIKE '$expname' ORDER BY summitExp.dateobs desc) as sub
     1869        SELECT sum_type, pzstate, rawstate, count(*), sum_exp, pz_exp, raw_exp
     1870        FROM (SELECT summitExp.exp_type as sum_type,
     1871                     pzDownloadExp.state as pzstate,
     1872                     rawExp.state as rawstate,
     1873                     summitExp.exp_name as sum_exp,
     1874                     pzDownloadExp.exp_name as pz_exp,
     1875                     rawExp.exp_name as raw_exp
     1876              FROM summitExp
     1877              LEFT JOIN pzDownloadExp USING (summit_id)
     1878              LEFT JOIN rawExp ON summitExp.exp_name = rawExp.exp_name
     1879              WHERE summitExp.summit_id >= (
     1880                    SELECT COALESCE(MIN(summit_id), 0)
     1881                    FROM summitExp WHERE exp_name LIKE '$expnamem5')
     1882              AND summitExp.exp_name LIKE '$expname'
     1883              ORDER BY summitExp.dateobs desc) as sub
    16171884        GROUP BY sum_type, pzstate, rawstate";
    16181885    if ($debug) {echo "$sql<br>";}
    16191886
    1620 
    16211887    $qry = $projectdb->query($sql);
    1622     while ($qry->fetchInto($tmp)) {
     1888    while ($tmp = $qry->fetch(PDO::FETCH_NUM)) {
    16231889        list($expType, $downloadState, $rawState, $count, $summit_exp, $pz_exp, $raw_exp) = $tmp;
    1624         if ($proj == "gpc1") {
    1625             $summit_exp_link = "<a href=\"https://svn.ifa.hawaii.edu/summit/ds/gpc1/$summit_exp/\" target='_blank'><font color=\"blue\">$summit_exp</font></a>";
     1890        $summit_exp_link = ($proj == "gpc1")
     1891            ? "<a href=\"https://svn.ifa.hawaii.edu/summit/ds/gpc1/$summit_exp/\" target='_blank'><font color=\"blue\">$summit_exp</font></a>"
     1892            : "<a href=\"https://svn.ifa.hawaii.edu/conductorb/ds/gpc2/$summit_exp/\" target='_blank'><font color=\"blue\">$summit_exp</font></a>";
     1893
     1894        if ($downloadState === NULL) {
     1895            echo "<tr><td>$summit_exp_link $expType <td>loading imfiles <td>null <td bgcolor=\"yellow\"><font color=\"red\">$count";
     1896        } elseif ($downloadState === 'run') {
     1897            echo "<tr><td>$summit_exp_link $expType <td>$pz_exp copying <td>$pz_exp saving <td bgcolor=$columnHeaderColor><font color=\"red\">$count";
    16261898        } else {
    1627             $summit_exp_link = "<a href=\"https://svn.ifa.hawaii.edu/conductorb/ds/gpc2/$summit_exp/\" target='_blank'><font color=\"blue\">$summit_exp</font></a>";
     1899            if ($rawState === NULL) {
     1900                echo "<tr><td>$summit_exp_link $expType <td>$pz_exp $downloadState <td>$pz_exp registering <td bgcolor=$columnHeaderColor><font color=\"blue\">$count";
     1901            } else {
     1902                echo "<tr><td>$summit_exp_link $expType <td>$pz_exp $downloadState <td>$raw_exp $rawState <td>$count";
     1903            }
    16281904        }
    1629         if ($downloadState == NULL) {
    1630           echo  "<tr><td> $summit_exp_link $expType <td> loading imfiles <td> null <td bgcolor=\"yellow\"> <font color=\"red\">$count";
    1631         } else {
    1632           if ($downloadState == 'run') {
    1633             echo  "<tr><td> $summit_exp_link $expType <td> $pz_exp copying <td> $pz_exp saving <td bgcolor=$columnHeaderColor> <font color=\"red\">$count";
    1634           } else {
    1635             if ($rawState == NULL) {
    1636               echo  "<tr><td> $summit_exp_link $expType <td> $pz_exp $downloadState <td> $pz_exp registering <td bgcolor=$columnHeaderColor> <font color=\"blue\">$count";
    1637             } else {                                   
    1638               echo  "<tr><td> $summit_exp_link $expType <td> $pz_exp $downloadState <td> $raw_exp $rawState <td> $count";
    1639             }
    1640           }
    1641         }
    1642     }
    1643 
     1905    }
     1906
     1907    // --- Query 2 ---
    16441908    echo "<tr><th colspan=4>Last night $previousDate (UTC), $mjdDaym1 (MJD)";
    16451909    $sql = "
    1646         SELECT sum_type, pzstate, rawstate, count(*), sum_exp, pz_exp, raw_exp
    1647         FROM
    1648           (SELECT summitExp.exp_type as sum_type, pzDownloadExp.state as pzstate, rawExp.state as rawstate, summitExp.exp_name as sum_exp, pzDownloadExp.exp_name as pz_exp, rawExp.exp_name as raw_exp
    1649            FROM summitExp
    1650            LEFT JOIN pzDownloadExp USING (summit_id)
    1651            LEFT JOIN rawExp ON summitExp.exp_name = rawExp.exp_name
    1652            WHERE summitExp.summit_id >= (
    1653                SELECT COALESCE(MIN(summit_id), 0)
    1654                FROM summitExp
    1655                WHERE exp_name LIKE '$expnamem5'
    1656             )
    1657             AND summitExp.exp_name LIKE '$expnamem1' ORDER BY summitExp.dateobs desc) as sub
     1910        SELECT sum_type, pzstate, rawstate, count(*), sum_exp, pz_exp, raw_exp
     1911        FROM (SELECT summitExp.exp_type as sum_type,
     1912                     pzDownloadExp.state as pzstate,
     1913                     rawExp.state as rawstate,
     1914                     summitExp.exp_name as sum_exp,
     1915                     pzDownloadExp.exp_name as pz_exp,
     1916                     rawExp.exp_name as raw_exp
     1917              FROM summitExp
     1918              LEFT JOIN pzDownloadExp USING (summit_id)
     1919              LEFT JOIN rawExp ON summitExp.exp_name = rawExp.exp_name
     1920              WHERE summitExp.summit_id >= (
     1921                    SELECT COALESCE(MIN(summit_id), 0)
     1922                    FROM summitExp WHERE exp_name LIKE '$expnamem5')
     1923              AND summitExp.exp_name LIKE '$expnamem1'
     1924              ORDER BY summitExp.dateobs desc) as sub
    16581925        GROUP BY sum_type, pzstate, rawstate";
    16591926    if ($debug) {echo "$sql<br>";}
    16601927
    16611928    $qry = $projectdb->query($sql);
    1662     while ($qry->fetchInto($tmp)) {
     1929    while ($tmp = $qry->fetch(PDO::FETCH_NUM)) {
    16631930        list($expType, $downloadState, $rawState, $count, $summit_exp, $pz_exp, $raw_exp) = $tmp;
    1664         if ($proj == "gpc1") {
    1665             $summit_exp_link = "<a href=\"https://svn.ifa.hawaii.edu/summit/ds/gpc1/$summit_exp/\" target='_blank'><font color=\"blue\">$summit_exp</font></a>";
     1931        $summit_exp_link = ($proj == "gpc1")
     1932            ? "<a href=\"https://svn.ifa.hawaii.edu/summit/ds/gpc1/$summit_exp/\" target='_blank'><font color=\"blue\">$summit_exp</font></a>"
     1933            : "<a href=\"https://svn.ifa.hawaii.edu/conductorb/ds/gpc2/$summit_exp/\" target='_blank'><font color=\"blue\">$summit_exp</font></a>";
     1934
     1935        if ($downloadState === NULL) {
     1936            echo "<tr><td>$summit_exp_link $expType <td>loading imfiles <td>null <td bgcolor=\"yellow\"><font color=\"red\">$count";
     1937        } elseif ($downloadState === 'run') {
     1938            echo "<tr><td>$summit_exp_link $expType <td>$pz_exp copying <td>$pz_exp saving <td bgcolor=$columnHeaderColor><font color=\"red\">$count";
    16661939        } else {
    1667             $summit_exp_link = "<a href=\"https://svn.ifa.hawaii.edu/conductorb/ds/gpc2/$summit_exp/\" target='_blank'><font color=\"blue\">$summit_exp</font></a>";
     1940            if ($rawState === NULL) {
     1941                echo "<tr><td>$summit_exp_link $expType <td>$pz_exp $downloadState <td>$pz_exp registering <td bgcolor=$columnHeaderColor><font color=\"blue\">$count";
     1942            } else {
     1943                echo "<tr><td>$summit_exp_link $expType <td>$pz_exp $downloadState <td>$raw_exp $rawState <td>$count";
     1944            }
    16681945        }
    1669         if ($downloadState == NULL) {
    1670           echo  "<tr><td> $summit_exp_link $expType <td> loading imfiles <td> null <td bgcolor=\"yellow\"> <font color=\"red\">$count";
    1671         } else {
    1672           if ($downloadState == 'run') {
    1673             echo  "<tr><td> $summit_exp_link $expType <td> $pz_exp copying <td> $pz_exp saving <td bgcolor=$columnHeaderColor> <font color=\"red\">$count";
    1674           } else {
    1675             if ($rawState == NULL) {
    1676               echo  "<tr><td> $summit_exp_link $expType <td> $pz_exp $downloadState <td> $pz_exp registering <td bgcolor=$columnHeaderColor> <font color=\"blue\">$count";
    1677             } else {                                   
    1678               echo  "<tr><td> $summit_exp_link $expType <td> $pz_exp $downloadState <td> $raw_exp $rawState <td> $count";
    1679             }
    1680           }
    1681         }
    1682     }
    1683 
     1946    }
     1947
     1948    // End timer
    16841949    echo "<tr><th colspan=4>";
    1685     // End timer
    16861950    $total_time = round(microtime(true) - $start, 3);
    16871951    echo "<center>loading in $total_time seconds<br></center>";
    1688 
    16891952    echo "</th></tr>";
    16901953    echo "</table><br>";
    16911954}
    1692 
    1693 function formatState($state) {
    1694     // Formats display of the state for better readability
    1695     if (is_null($state)) {
    1696         return "<span style='color: red;'>null</span>";
    1697     } elseif ($state == 'run') {
    1698         return "<span style='color: red;'>loading</span>";
    1699     } else {
    1700         return $state;
    1701     }
    1702 }
    1703 ###########################################################################
    1704 #
    1705 # Gets disk status
    1706 #
    1707 ###########################################################################
    1708 function getHostsold($db) {
    1709     // timer start
    1710     $time = microtime();
    1711     $time = explode(' ', $time);
    1712     $time = $time[1] + $time[0];
    1713     $start = $time;
    1714 
    1715     #echo "<script type=\"text/javascript\" src=\"https://www.gstatic.com/charts/loader.js\"></script>";
    1716     echo "<script type=\"text/javascript\" src=\"loader.js\"></script>";
    1717     echo "<br><div class=\"chartWithOverlay\" style=\"position: relative; width: 640px\">";
    1718     echo "  <div id=\"disk_div\" style=\"width:640px; height:5000px\"></div>";
    1719     echo "  <div class=\"overlay\" style=\"position: absolute; width: 100px; top: 35px; right: 20px;\">";
    1720     echo "    <div style=\"font-size: 12px; height: 14px; border: 1px solid; background-color: #00ff00\"><center>Free</div>";
    1721     echo "    <div style=\"font-size: 12px; height: 14px; border: 1px solid; background-color: #ffff00\"><center>Used</div>";
    1722     echo "    <div style=\"font-size: 12px; height: 14px; border: 1px solid; background-color: #ff0000\"><center>> 97% limit</div>";
    1723     echo "    <div style=\"font-size: 12px; height: 14px; border: 1px solid; background-color: #bbbbbb\"><center>Down | Repair</div>";
    1724     echo "    <div style=\"font-size: 12px; height: 14px; border: 1px solid; background-color: #ffffff\"><center>Power Off</div>";
    1725     echo "  </div>";
    1726     echo "</div>";
    1727 
    1728     echo "<script type=\"text/javascript\">";
    1729     echo "google.charts.load('current', {packages: ['corechart', 'bar']});";
    1730     echo "google.charts.setOnLoadCallback(drawBarColors);";
    1731     echo "function drawBarColors() {";
    1732     echo "  var data = google.visualization.arrayToDataTable([";
    1733     echo "    ['Host', ";
    1734     echo "     'used', {type: 'string', role: 'style'}, {type: 'string', role: 'tooltip', 'p': {'html': true}}, ";
    1735     echo "     'free', {type: 'string', role: 'style'}, {type: 'string', role: 'tooltip', 'p': {'html': true}}],";
    1736 
    1737     //$sql = "SELECT host, format(total,1), format(available,2), format(used,2), writable, readable, format(used/total*100,2) FROM hosts";
    1738     $sql = "SELECT host, format(total, 2), format(available, 2), format(used, 2), writable, readable, format(used/total*100,3) as ratio FROM hosts where xattr <> 3 ";
    1739     if ($debug) {echo "$sql<br>";}
    1740     $qry = $db->query($sql);
    1741     if (dberror($qry)) {echo "<b>error with $sql </b><br>\n";}
    1742 
    1743     // Set environment variables
    1744     putenv("PATH=/usr/local/bin:/usr/bin:/bin:/data/ippc65.0/ipp/src/psconfig//ipp-20210708-gentoo.lin64/bin");
    1745     putenv("PERL5LIB=/data/ippc65.0/ipp/src/psconfig//ipp-20210708-gentoo.lin64/lib");
    1746 
    1747     # not sure why neb-host doesn't work on ipp113 CCL.20210216
    1748     exec("neb-host |cut -b 17-30,86-", $hostnoteall);
    1749     while ($qry->fetchInto($row)) {
    1750         list($host, $total, $available, $used, $writable, $readable, $ratio) = $row;
    1751        $hostnotes = preg_grep("/$host/", $hostnoteall);
    1752        $hostnote = array_values($hostnotes);
    1753        if ($writable == 1 && $readable == 1) {
    1754           if ($ratio >= 97) {
    1755             # space used over limits, up mode (read/write ok).
    1756             echo "[ \"$host:up\", ";
    1757             echo "      $used, \"stroke-width: 0.3;\"+\"stroke-color: black;\"+\"color: #ff0000;\", \"<i><b>$host: $ratio% of $total TB used</b></i><br>$hostnote[0]\", ";
    1758             echo " $available, \"stroke-width: 0.3;\"+\"stroke-color: black;\"+\"color: #00ff00;\", \"<i><b>$host: $available TB free</b></i>\"],";
    1759           }
    1760           else {
    1761             # enough space, up mode (read/write ok).
    1762             echo "[ \"$host:up\", ";
    1763             echo "      $used, \"stroke-width: 0.3;\"+\"stroke-color: black;\"+\"color: #ffff00;\", \"<i><b>$host: $ratio% of $total TB used</b></i><br>$hostnote[0]\", ";
    1764             echo " $available, \"stroke-width: 0.3;\"+\"stroke-color: black;\"+\"color: #00ff00;\", \"<i><b>$host: $available TB free</b></i>\"],";
    1765          }
    1766        }
    1767        else if ($writable == 0 && $readable == 1) {
    1768           if ($ratio >= 97) {
    1769             # space used over limits, repair mode (read only).
    1770             echo "[ \"$host:repair\", ";
    1771             echo "      $used, \"stroke-width: 0.3;\"+\"stroke-color: black;\"+\"color: #ff0000;\", \"<i><b>$host: $ratio% of $total TB used<br><font color=red>$hostnote[0]\", ";
    1772             echo " $available, \"stroke-width: 0.3;\"+\"stroke-color: black;\"+\"color: #bbbbbb;\", \"<i><b>$host: $available TB free</b></i>\"],";
    1773           }
    1774           else {
    1775             # enough space, but hosts are unstable, repair mode (read only).
    1776             echo "[ \"$host:repair\", ";
    1777             echo "      $used, \"stroke-width: 0.3;\"+\"stroke-color: black;\"+\"color: #ffff00;\", \"<i><b>$host: $ratio% of $total TB used<br>$hostnote[0]\", ";
    1778             echo " $available, \"stroke-width: 0.3;\"+\"stroke-color: black;\"+\"color: #bbbbbb;\", \"<i><b>$host: $available TB free</b></i>\"],";
    1779           }
    1780        }
    1781        else {
    1782             # down mode (can't read or write).
    1783             echo "[ \"$host:down\", ";
    1784             echo "      $used, \"stroke-width: 0.3;\"+\"stroke-color: black;\"+\"color: #bbbbbb;\", \"<i><b>$host: $ratio% used<br>$hostnote[0]\", ";
    1785             echo " $available, \"stroke-width: 0.3;\"+\"stroke-color: black;\"+\"color: #bbbbbb;\", \"<i><b>$host: $available TB free</b></i>\"],";
    1786        }
    1787     }
    1788    echo "  ]);";
    1789    echo "   var options = {";
    1790    echo "     title: 'Nebulous Disk Use across IPP Clusters',";
    1791    echo "     titleTextStyle: {color: 'black', fontSize: 15},";
    1792    echo "     width: '100%',";
    1793    echo "     height: '100%',";
    1794    echo "     tooltip: {isHtml: true},";
    1795    echo "     legend: { position: 'none',";
    1796    echo "               alignment: 'end',";
    1797    echo "               maxLines: 3},";
    1798    echo "     hAxis: {  title: 'Space (TB)',";
    1799    echo "               gridlines: {count: 10}";
    1800    echo "     },";
    1801    echo "     vAxis: { textPosition: 'in', direction: 1 },";
    1802    echo "     bar: { groupWidth: '95%' },";
    1803    echo "     chartArea: {left:20, top:30, right:20, bottom:40},";
    1804    echo "     fontSize: 15,";
    1805    echo "     isStacked: true,";
    1806    echo "     colors: ['#76A7FA', '#FF5733', '#C70039', '#900C3F', '#581845']";
    1807    echo "   };";
    1808    echo "   var chart = new google.visualization.BarChart(document.getElementById('disk_div'));";
    1809    echo "   chart.draw(data, options);";
    1810    echo " }";
    1811    echo "</script>";
    1812     $time = microtime();
    1813     $time = explode(' ', $time);
    1814     $time = $time[1] + $time[0];
    1815     $finish = $time;
    1816     $total_time = round(($finish - $start), 3);
    1817     $start= $finish;
    1818     echo "<center>loading in $total_time seconds<br></center>";
    1819 }
    1820 
    1821 ###########################################################################
    1822 #
    1823 # Gets disk status
    1824 #
    1825 ###########################################################################
    1826 function getHosts($db) {
    1827     $start = microtime(true);
    1828 
    1829 
    1830     // Set environment variables
    1831     putenv("PATH=/usr/local/bin:/usr/bin:/bin:/data/ippc65.0/ipp/src/psconfig//ipp-20210708-gentoo.lin64/bin");
    1832     putenv("PERL5LIB=/data/ippc65.0/ipp/src/psconfig//ipp-20210708-gentoo.lin64/lib");
    1833 
    1834     $hostnoteall = array();
    1835     exec("neb-host |cut -b 17-30,86-", $hostnoteall);
    1836 
    1837     $sql = "SELECT host, format(total, 2), format(available, 2), format(used, 2), writable, readable, format(used/total*100,3) as ratio FROM hosts where xattr <> 3 ORDER BY CASE WHEN ROUND(used/total*100,3) = 100 THEN 0 ELSE 1 END, CASE WHEN host LIKE '%\_bck.0' THEN 1 ELSE 0 END, host;";
    1838     $qry = $db->query($sql);
    1839 
    1840     if ($qry === false) {
    1841         echo "<b>Error with SQL query</b><br>";
    1842         return;
    1843     }
    1844 
    1845     $sqlMax = "SELECT MAX(total) AS max_total FROM hosts WHERE xattr <> 3";
    1846     $qryMax = $db->query($sqlMax);
    1847     $maxUsed = 0;
    1848 
    1849     if ($qryMax && $qryMax->fetchInto($rowMax)) {
    1850         $maxUsed = $rowMax[0];  // max_total value
    1851     }
    1852 
    1853     $dataRows = array();
    1854     while ($qry->fetchInto($row)) {
    1855         // Access by index
    1856         $host      = $row[0]; // ipp071.0
    1857         $total     = $row[1]; // 80.0352 TB
    1858         $available = $row[2]; // 10.8096 TB
    1859         $used      = $row[3]; // 69.2256 TB
    1860         $writable  = $row[4]; // 0
    1861         $readable  = $row[5]; // 1
    1862         $ratio     = $row[6]; // 86.4939 %
    1863 
    1864         // Match notes
    1865         $hostnotes = preg_grep("/$host/", $hostnoteall);
    1866         $hostnote = reset($hostnotes);
    1867 
    1868         // Defaults
    1869         $styles    = "stroke-width: .5; stroke-color: black;";
    1870         $status    = "down";
    1871         $usedColor = "lightgrey";
    1872         $freeColor = "lightgrey";
    1873 
    1874         // Status logic
    1875         if ($writable == 1 && $readable == 1) {
    1876             $status    = "up";
    1877             $usedColor = ($ratio >= 97) ? "pink" : "yellow"; // red if ≥97%
    1878             $freeColor = "#a6ec99"; // green free
    1879         } elseif ($writable == 0 && $readable == 1) {
    1880             $status    = "repair";
    1881             $usedColor = ($ratio >= 97) ? "pink" : "yellow";
    1882             $freeColor = "lightgrey"; // grey free
    1883         } elseif ($available < 1) {
    1884             $used      = $maxUsed;
    1885             $available = 0;
    1886             $status    = "offline";
    1887             $usedColor = "white";
    1888             $freeColor = "white";
    1889         }
    1890 
    1891         if ($status == "down") {
    1892             $tooltipUsed = $host . ": " . round($ratio, 1) . "% of " . $total . " TB used ".
    1893                            "<span style='color:red; font-weight:bold;'> <br>" . $hostnote .  "</span>";
    1894             $tooltipFree = "<span style='color:red; font-weight:bold;'>" .
    1895                            $host . ": " . $available . " TB free" .
    1896                            "</span>";
    1897         } elseif ($status == "offline") {
    1898             $tooltipUsed = $host . ": " . round($ratio, 1) . "% of " . $total . " TB used ".
    1899                            "<span style='color:red; font-weight:bold;'> <br>" . $hostnote .  "</span>";
    1900             $tooltipFree = "<span style='color:red; font-weight:bold;'>" .
    1901                            $host . ": " . $total . " TB free" .
    1902                            "</span>";
    1903         } else {
    1904             $tooltipUsed = $host . ": " . round($ratio, 1) . "% of " . $total . " TB used<br>" . $hostnote;
    1905             $tooltipFree = $host . ": " . $available . " TB free";
    1906         }
    1907 
    1908         // Build data row (manual string concatenation for old PHP)
    1909         $dataRows[] =
    1910             "[ \"" . $host . ":" . $status . "\", " .
    1911             $used . ", \"" . $styles . " color: " . $usedColor . ";\", \"" . $tooltipUsed . "\", " .
    1912             $available . ", \"" . $styles . " color: " . $freeColor . ";\", \"" . $tooltipFree . "\" ]";
    1913 
    1914     }
    1915     echo "<script type=\"text/javascript\">
    1916             google.charts.load('current', {packages: ['corechart', 'bar']});
    1917             google.charts.setOnLoadCallback(drawBarColors);
    1918 
    1919             function drawBarColors() {
    1920                 var data = google.visualization.arrayToDataTable([
    1921                     ['Host', 'Used', {type: 'string', role: 'style'}, {type: 'string', role: 'tooltip', 'p': {'html': true}},
    1922                      'Free', {type: 'string', role: 'style'}, {type: 'string', role: 'tooltip', 'p': {'html': true}}],
    1923                     " . implode(",\n", $dataRows) . "
    1924                 ]);
    1925 
    1926                 var options = {
    1927                     title: 'Nebulous Disk Use across IPP Clusters',
    1928                     titleTextStyle: {color: 'black', fontSize: 15},
    1929                     width: '100%',
    1930                     height: '100%',
    1931                     tooltip: {isHtml: true},
    1932                     legend: { position: 'none', alignment: 'end', maxLines: 3 },
    1933                     hAxis: { title: 'Space (TB)', gridlines: {count: 20} },
    1934                     vAxis: { textStyle: {fontSize: 15}, format: 'string',  textPosition: 'in', direction: 1},
    1935                     bar: { groupWidth: '95%' },
    1936                     chartArea: {left:40, top:50, right:20, bottom:40},
    1937                     fontSize: 15,
    1938                     allowHtml: true,
    1939                     isStacked: true
    1940                 };
    1941 
    1942                 var chart = new google.visualization.BarChart(document.getElementById('disk_div'));
    1943                 chart.draw(data, options);
    1944             }
    1945           </script>";
    1946 
    1947 echo "<script type='text/javascript' src='loader.js'></script>
    1948 <br>
    1949 <div class='chartWithOverlay' style='position: relative; width: 100%'>
    1950 
    1951     <div id='disk_div' style='width:100%; height:5000px'></div>
    1952 
    1953     <div class='overlay' style='position: absolute; width:160px; top:50px; right:30px; font-family:Arial, sans-serif;'>
    1954 
    1955       <div style='font-size:15px; border:1px solid #888; background-color:lightgreen; color:black; text-align:center; margin-bottom:1px;'>Free</div>
    1956       <div style='font-size:15px; border:1px solid #888; background-color:yellow    ; color:black; text-align:center; margin-bottom:1px;'>Used</div>
    1957       <div style='font-size:15px; border:1px solid #888; background-color:pink      ; color:black; text-align:center; margin-bottom:1px;'>97% Usage</div>
    1958       <div style='font-size:15px; border:1px solid #888; background-color:lightgrey ; color:black; text-align:center; margin-bottom:1px;'>Down/Repair</div>
    1959       <div style='font-size:15px; border:1px solid #888; background-color:white     ; color:black; text-align:center; margin-bottom:1px;'>Offline</div>
    1960       <div style='font-size:15px; border:1px solid #888; background-color:#42a5f5   ; color:white; text-align:center; margin-bottom:1px;'>
    1961         <a href='https://atrcganglia.ifa.hawaii.edu/' target='_blank' style='text-decoration:none; color:white; font-weight:bold;'>ATRC ganglia</a>
    1962       </div>
    1963 
    1964     </div>
    1965 
    1966 </div>";
    1967 
    1968     $total_time = round(microtime(true) - $start, 3);
    1969     echo "<center>loading in $total_time seconds<br></center>";
    1970 }
    1971 
    1972 ###########################################################################
    1973 #
    1974 # Gets disk spaces status
    1975 #
    1976 ###########################################################################
    1977 function getSpaces($db) {
    1978     // timer start
    1979     $time = microtime();
    1980     $time = explode(' ', $time);
    1981     $time = $time[1] + $time[0];
    1982     $start = $time;
    1983 
    1984 
    1985     $db->query("SELECT FORMAT(IFNULL(SUM(available), 0), 1) FROM hosts where host like 'ippb%' and xattr <> 3")->fetchInto($row);
    1986     $ippbusable = $row[0];
    1987     $db->query("SELECT FORMAT(IFNULL(SUM(available), 0), 1) FROM hosts where host like 'ippb%' and writable = 1 and xattr <> 3")->fetchInto($row);
    1988     $ippbfree = $row[0];
    1989 
    1990     $db->query("SELECT FORMAT(IFNULL(SUM(available), 0), 1) FROM hosts where host not like 'ippb%' and xattr <> 3")->fetchInto($row);
    1991     $ippusable = $row[0];
    1992     $db->query("SELECT FORMAT(IFNULL(SUM(available), 0), 1) FROM hosts where host not like 'ippb%' and writable = 1 and xattr <> 3")->fetchInto($row);
    1993     $ippfree = $row[0];
    1994 
    1995     $db->query("SELECT FORMAT(IFNULL(SUM(available), 0), 1) FROM hosts where host like 'ipp%_bck.0' and xattr <> 3")->fetchInto($row);
    1996     $bckusable = $row[0];
    1997     $db->query("SELECT FORMAT(IFNULL(SUM(available), 0), 1) FROM hosts where host like 'ipp%_bck.0' and writable = 1 and xattr <> 3")->fetchInto($row);
    1998     $bckfree = $row[0];
    1999 
    2000     $sql = "select format(used/total*100,1), usable from cluster_space order by timestamp desc limit 1;";
    2001 
    2002     if ($debug) {echo "$sql<br>";}
    2003     $qry = $db->query($sql);
    2004     if (dberror($qry)) {echo "<b>error with $sql </b><br>\n";}
    2005     $qry->fetchInto($row);
    2006     $allocated = $row[0];
    2007     $usable    = $row[1];
    2008 
    2009     #echo "<script type=\"text/javascript\" src=\"https://www.gstatic.com/charts/loader.js\"></script>";
    2010     echo "<script type=\"text/javascript\" src=\"loader.js\"></script>";
    2011     echo "<br><div class=\"chartWithOverlay\" style=\"position: relative; width: 640; height:400\">";
    2012     echo "  <div id=\"space_div\" style=\"width:640; \"></div>";
    2013     echo "  <div class=\"overlay\" style=\"position: absolute; width: 500px; bottom: 60px; left: 85px;\">";
    2014     echo "    <div style=\"font-size: 16px; color:#0000ff; \"><b>ipp*_bck: $bckusable TB (usable)</b></div>";
    2015     echo "    <div style=\"font-size: 16px; color:#33a532; \"><b>ipp*_bck: $bckfree   TB (free)</b></div>";
    2016     echo "  </div>";
    2017     echo "</div>";
    2018 
    2019     echo "<script type=\"text/javascript\">";
    2020     echo "google.charts.load('current', {packages: ['corechart']});";
    2021     echo "google.charts.setOnLoadCallback(drawChart);";
    2022     echo "function drawChart() {";
    2023     echo "  var data = new google.visualization.DataTable();";
    2024     echo "        data.addColumn('datetime', 'Day');";
    2025     echo "        data.addColumn('number', 'Usable (ipp:$ippusable, ippb:$ippbusable) TB');";
    2026     echo "        data.addColumn('number', 'Free (ipp:$ippfree, ippb:$ippbfree) TB');";
    2027     echo "   data.addRows([";
    2028     $interval = 10;
    2029     $sql = "select sub.dated, sub.usable, sub.available from (select date_format(timestamp, \"%Y.%m.%d.%H.%i\") as dated, usable, available from cluster_space where timestamp > curdate() - INTERVAL $interval DAY) as sub group by dated;";
    2030     if ($debug) {echo "$sql<br>";}
    2031     $qry = $db->query($sql);
    2032     if (dberror($qry)) {echo "<b>error with $sql </b><br>\n";}
    2033     while ($qry->fetchInto($row)) {
    2034         $tsp = explode('.', $row[0]);
    2035         $mon = $tsp[1]-1;
    2036         $timestamp = $row[0];
    2037         $usable    = $row[1];
    2038         $available = $row[2];
    2039 #       new Date(Year, Month, Day, Hours, Minutes, Seconds, Milliseconds)
    2040        echo "[new Date($tsp[0], $mon, $tsp[2], $tsp[3], $tsp[4]), $usable, $available],";
    2041     }
    2042    echo "  ]);";
    2043    echo "   var options = {";
    2044    echo "      chart: {"; 
    2045    echo "               position: 'center', " ;
    2046    echo "              },";
    2047    echo "     title: 'Total Available Cluster Space in the Past $interval Days ($allocated% of total allocated)',";
    2048    echo "     titleTextStyle: {color: 'black', fontSize: 15},";
    2049    echo "     width: '100%',";
    2050    echo "     height: 400,";
    2051    echo "     legend: { position: 'top',";
    2052    echo "               alignment: 'center',";
    2053    echo "              },";
    2054    echo "     lineWidth: 2, ";
    2055    echo "     series: {";
    2056    echo "         0: { color: '#0000ff', lineWidth: 3 },      ";
    2057    echo "         1: { color: '#33a532', lineWidth: 3 },      ";
    2058    echo "              },";
    2059    echo "     hAxis: {  ";
    2060    echo "             title: 'Date/Time (HST)',";
    2061    echo "             format: 'MMM dd',";
    2062    echo "            gridlines: {count: -1},";
    2063    echo "     },";
    2064    echo "     vAxis: {  ";
    2065    echo "                  title: 'Available (TB)',";
    2066    echo "     viewWindow: {   min: 500    },  ";
    2067    echo "     },";
    2068    echo "     chartArea: {left:80, top:50, right:20, bottom:50},";
    2069    echo "     fontSize: 12,";
    2070    echo "   };";
    2071    echo "   var chart = new google.visualization.LineChart(document.getElementById('space_div'));";
    2072    echo "   chart.draw(data, options);";
    2073    echo " }";
    2074    echo "</script>";
    2075     $time = microtime();
    2076     $time = explode(' ', $time);
    2077     $time = $time[1] + $time[0];
    2078     $finish = $time;
    2079     $total_time = round(($finish - $start), 3);
    2080     $start= $finish;
    2081     echo "<center>loading in $total_time seconds<br></center>";
    2082 }
    2083 
    2084 ###########################################################################
    2085 #
    2086 # Gets data quality status
    2087 #
    2088 ###########################################################################
    2089 function getDataQuality($db) {
    2090     // timer start
    2091     $time = microtime();
    2092     $time = explode(' ', $time);
    2093     $time = $time[1] + $time[0];
    2094     $start = $time;
    2095 
    2096     $date = gmdate("Y-m-d");
    2097     $sql = "SELECT MAX(exp_id) FROM rawExp WHERE dateobs BETWEEN DATE_SUB('$date', INTERVAL 20 DAY) AND DATE_SUB('$date', INTERVAL 10 DAY)";
    2098     if ($debug) {echo "$sql<br>";}
    2099     $qry = $db->query($sql);
    2100 
    2101     if (dberror($qry)) {
    2102         echo "*** WARNING: No data in the period 10-20 days before requested date (query will be slower) **\n";
    2103         $refExpID = 0;
    2104     } else {
    2105         $qry->fetchInto($refExpID);
    2106         $refExpID = isset($refExpID[0]) ? $refExpID[0] : 0;
    2107     }
    2108     // Print the result of the query
    2109     if ($debug) {echo "The result of MAX(exp_id) is: $refExpID<br>";}
    2110     {echo "The result of MAX(exp_id) is: $refExpID<br>";}
    2111 
    2112     #Query for the needed data
    2113     $DQarray = array();
    2114 
    2115     $sql = "SELECT subtime(dateobs,'10:00:00') as time,iq_fwhm_major,iq_fwhm_minor,zpt_obs,zpt_stdev,rawExp.filter,rawExp.exp_time,rawExp.comment FROM rawExp JOIN chipRun using (exp_id) JOIN camRun using (chip_id) JOIN camProcessedExp using (cam_id)";
    2116 #    $sql.= " WHERE (rawExp.dateobs > utc_date() - INTERVAL 14 hour) and (rawExp.dateobs < utc_date() + INTERVAL 10 hour)";
    2117     $sql.= " WHERE (rawExp.dateobs > utc_date())";
    2118 #    $sql.= " WHERE (rawExp.dateobs > '2024-10-24')";
    2119 #    $sql.= " AND (rawExp.dateobs < '2024-10-25')";
    2120     $sql.= " AND exp_id > $refExpID[0]";
    2121     $sql.= " AND exp_type = 'OBJECT' AND camRun.state like 'full' ORDER BY dateobs asc;";
    2122     if ($debug) {echo "$sql<br>";}
    2123     $qry = $db->query($sql);
    2124     if (dberror($qry)) {echo "<b>error with $sql </b><br>\n";}
    2125 #    echo "<b>error with $sql </b><br>\n";
    2126     while ($qry->fetchInto($row)) {
    2127         $DQarray[] = $row;
    2128     }
    2129 
    2130     #also find the median zeropoint
    2131 #    foreach($DQarray as $row) {
    2132 #        $zpt[]    = $row[3];
    2133 #    }
    2134 
    2135 #    sort($zpt);
    2136 #    $count = sizeof($zpt);   // cache the count
    2137 #    $index = floor($count/2);  // cache the index
    2138 #    $zpt_med = $zpt[$index];
    2139 
    2140     #echo "<script type=\"text/javascript\" src=\"https://www.gstatic.com/charts/loader.js\"></script>";
    2141     echo "<script type=\"text/javascript\" src=\"loader.js\"></script>";
    2142     echo "<br><div class=\"chartWithOverlay\" style=\"position: relative; width: 640; height:400\">";
    2143     echo "  <div id=\"dq_div\" style=\"width:640; \"></div>";
    2144     echo "  <div class=\"overlay\" style=\"position: absolute; width: 400px; bottom: 60px; left: 85px;\">";
    2145     echo "  </div>";
    2146     echo "</div>";
    2147 
    2148     echo "<script type=\"text/javascript\">";
    2149     echo "google.charts.load('current', {packages: ['corechart']});";
    2150     echo "google.charts.setOnLoadCallback(drawChart);";
    2151     echo "function drawChart() {";
    2152     echo "  var data = new google.visualization.DataTable();";
    2153     echo "        data.addColumn('datetime', 'Time');";
    2154     echo "        data.addColumn('number', 'FWHM_maj');";
    2155     echo "        data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});";
    2156     echo "        data.addColumn('number', 'FWHM_min');";
    2157     echo "        data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});";
    2158     echo "        data.addColumn('number', 'seeing=1');";
    2159     echo "        data.addColumn('number', 'seeing=1.5');";
    2160     echo "   data.addRows([";
    2161 
    2162     foreach($DQarray as $row) {
    2163         $tsp = explode(' ', $row[0]);
    2164         $dat = explode('-', $tsp[0]);
    2165         $tm = explode(':', $tsp[1]);
    2166         $timestamp = $row[0];
    2167         $fwhm_maj    = $row[1];
    2168         $fwhm_min = $row[2];
    2169 
    2170         $tt_string1 = sprintf('time: %s\n FWHM_major: %s\n filter: %s\n exp_time: %s\n comment: %s', $timestamp, $fwhm_maj,$row[5],$row[6],$row[7]);
    2171         $tt_string2 = sprintf('time: %s\n FWHM_minor: %s\n filter: %s\n exp_time: %s\n comment: %s', $timestamp, $fwhm_min,$row[5],$row[6],$row[7]);
    2172 #       new Date(Year, Month, Day, Hours, Minutes, Seconds, Milliseconds)
    2173        echo "[new Date($dat[0], $dat[1]-1, $dat[2], $tm[0], $tm[1]), $fwhm_maj,'$tt_string1',$fwhm_min,'$tt_string2',3.85,5.76],";
    2174     }
    2175    echo "  ]);";
    2176    echo "   var options = {";
    2177    echo "      chart: {"; 
    2178    echo "               position: 'center', " ;
    2179    echo "              },";
    2180    echo "     title: 'FWHM overview during the last night',";
    2181    echo "     titleTextStyle: {color: 'black', fontSize: 15},";
    2182    echo "     width: '100%',";
    2183    echo "     height: 400,";
    2184    echo "     legend: { position: 'top',";
    2185    echo "               alignment: 'center',";
    2186    echo "              },";
    2187    echo "     lineWidth: 2, ";
    2188    echo "     series: {";
    2189    echo "         0: { color: '#0000ff', lineWidth: 3 },      ";
    2190    echo "         1: { color: '#33a532', lineWidth: 3 },      ";
    2191    echo "         2: { color: 'red', lineWidth: 2 },      ";
    2192    echo "         3: { color: 'red', lineWidth: 2, lineDashStyle: [8, 4] },      ";
    2193    echo "              },";
    2194    echo "     hAxis: {  ";
    2195    echo "             title: 'Date/Time (HST)',";
    2196    echo "             format: 'HH:mm',";
    2197    echo "            gridlines: {count: -1},";
    2198    echo "     },";
    2199    echo "     vAxis: {  ";
    2200    echo "                  title: 'FWHM (pixels)',";
    2201    echo "     },";
    2202    echo "     chartArea: {left:80, top:50, right:20, bottom:50},";
    2203    echo "     fontSize: 12,";
    2204    echo "   };";
    2205    echo "   var chart = new google.visualization.LineChart(document.getElementById('dq_div'));";
    2206    echo "   chart.draw(data, options);";
    2207    echo " }";
    2208    echo "</script>";
    2209 
    2210 #   ---------------------------------------
    2211 #   Also the zeropoint
    2212 
    2213     #echo "<script type=\"text/javascript\" src=\"https://www.gstatic.com/charts/loader.js\"></script>";
    2214     echo "<script type=\"text/javascript\" src=\"loader.js\"></script>";
    2215     echo "<br><div class=\"chartWithOverlay\" style=\"position: relative; width: 640; height:400\">";
    2216     echo "  <div id=\"dqzp_div\" style=\"width:640; \"></div>";
    2217     echo "  <div class=\"overlay\" style=\"position: absolute; width: 400px; bottom: 60px; left: 85px;\">";
    2218     echo "  </div>";
    2219     echo "</div>";
    2220 
    2221     echo "<script type=\"text/javascript\">";
    2222     echo "google.charts.load('current', {packages: ['corechart']});";
    2223     echo "google.charts.setOnLoadCallback(drawChart);";
    2224     echo "function drawChart() {";
    2225     echo "  var data = new google.visualization.DataTable();";
    2226     echo "        data.addColumn('datetime', 'Time');";
    2227     echo "        data.addColumn('number', 'zeropoint');";
    2228     echo "        data.addColumn({'type': 'string', 'role': 'style'});";
    2229     echo "        data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});";
    2230     echo "        data.addColumn({id:'i0', type:'number', role:'interval'});";
    2231     echo "        data.addColumn({id:'i0','type': 'string', 'role': 'style'});";
    2232     echo "        data.addColumn({id:'i0', type:'number', role:'interval'});";
    2233     echo "        data.addColumn({id:'i0','type': 'string', 'role': 'style'});";
    2234     echo "   data.addRows([";
    2235     foreach($DQarray as $row) {
    2236         $tsp = explode(' ', $row[0]);
    2237         $dat = explode('-', $tsp[0]);
    2238         $tm = explode(':', $tsp[1]);
    2239         $timestamp = $row[0];
    2240         $zp    = $row[3];
    2241         $zp_stdev = $row[4];
    2242         $zp_low = $zp-$zp_stdev;
    2243         $zp_high = $zp+$zp_stdev;
    2244         $col_string = '#0000ff';
    2245         $zpt_off = 0.;
    2246 
    2247         $tt_string1 = sprintf('time: %s\n zeropoint: %s\n filter: %s\n exp_time: %s\n comment: %s', $timestamp, $zp,$row[5],$row[6],$row[7]);
    2248 #       new Date(Year, Month, Day, Hours, Minutes, Seconds, Milliseconds)
    2249        echo "[new Date($dat[0], $dat[1]-1, $dat[2], $tm[0], $tm[1]), $zp,'$col_string','$tt_string1', $zp_low,'$col_string', $zp_high,'$col_string'],";
    2250     }
    2251 
    2252    echo "  ]);";
    2253    echo "   var options = {";
    2254    echo "      chart: {"; 
    2255    echo "               position: 'center', " ;
    2256    echo "              },";
    2257    echo "     title: 'Zeropoint overview during the last night',";
    2258    echo "     titleTextStyle: {color: 'black', fontSize: 15},";
    2259    echo "     width: '100%',";
    2260    echo "     height: 400,";
    2261    echo "     legend: { position: 'top',";
    2262    echo "               alignment: 'center',";
    2263    echo "              },";
    2264    echo "     series: {";
    2265    echo "         0: { color: '#0000ff'},      ";
    2266    echo "              },";
    2267    echo "     intervals: { color: 'grey','style':'points', pointSize: 1 },  ";
    2268    echo "     hAxis: {  ";
    2269    echo "             title: 'Date/Time (HST)',";
    2270    echo "             format: 'HH:mm',";
    2271    echo "            gridlines: {count: -1},";
    2272    echo "     },";
    2273    echo "     vAxis: {  ";
    2274    echo "                  title: 'Zeropoint (mag)',";
    2275    echo "     },";
    2276    echo "     chartArea: {left:80, top:50, right:20, bottom:50},";
    2277    echo "     fontSize: 12,";
    2278    echo "   };";
    2279    echo "   var chart = new google.visualization.ScatterChart(document.getElementById('dqzp_div'));";
    2280    echo "   chart.draw(data, options);";
    2281    echo " }";
    2282    echo "</script>";
    2283 
    2284 #   ---------------------------------------
    2285 #   And a fixed zoom in on the zeropoint to see details
    2286 
    2287     #echo "<script type=\"text/javascript\" src=\"https://www.gstatic.com/charts/loader.js\"></script>";
    2288     echo "<script type=\"text/javascript\" src=\"loader.js\"></script>";
    2289     echo "<br><div class=\"chartWithOverlay\" style=\"position: relative; width: 640; height:400\">";
    2290     echo "  <div id=\"dqzp_zoom_div\" style=\"width:640; \"></div>";
    2291     echo "  <div class=\"overlay\" style=\"position: absolute; width: 400px; bottom: 60px; left: 85px;\">";
    2292     echo "  </div>";
    2293     echo "</div>";
    2294 
    2295     echo "<script type=\"text/javascript\">";
    2296     echo "google.charts.load('current', {packages: ['corechart']});";
    2297     echo "google.charts.setOnLoadCallback(drawChart);";
    2298     echo "function drawChart() {";
    2299     echo "  var data = new google.visualization.DataTable();";
    2300     echo "        data.addColumn('datetime', 'Time');";
    2301     echo "        data.addColumn('number', 'zeropoint');";
    2302     echo "        data.addColumn({'type': 'string', 'role': 'style'});";
    2303     echo "        data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});";
    2304     echo "        data.addColumn({id:'i0', type:'number', role:'interval'});";
    2305     echo "        data.addColumn({id:'i1', type:'number', role:'interval'});";
    2306     echo "   data.addRows([";
    2307     foreach($DQarray as $row) {
    2308         $tsp = explode(' ', $row[0]);
    2309         $dat = explode('-', $tsp[0]);
    2310         $tm = explode(':', $tsp[1]);
    2311         $timestamp = $row[0];
    2312         $zp    = $row[3];
    2313         $zp_stdev = $row[4];
    2314         $zp_low = $zp_stdev;
    2315         $zp_high = $zp_stdev;
    2316         $col_string = '#0000ff';
    2317         $zpt_off = 0.;
    2318 
    2319         #if(substr($row[5],0,1) == 'g') {$col_string = 'blue'; $zpt_off = $zp - 24.563;}
    2320         #if(substr($row[5],0,1) == 'r') {$col_string = 'red'; $zpt_off = $zp - 24.740;}
    2321         #if(substr($row[5],0,1) == 'i') {$col_string = '#6a3d9a'; $zpt_off = $zp - 24.611;}
    2322         #if(substr($row[5],0,1) == 'z') {$col_string = 'green'; $zpt_off = $zp - 24.250;}
    2323         #if(substr($row[5],0,1) == 'y') {$col_string = 'cyan'; $zpt_off = $zp - 23.320;}
    2324         #if(substr($row[5],0,1) == 'w') {$col_string = 'grey'; $zpt_off = $zp - 26.300;}
    2325 
    2326         #define new zeropoints
    2327         if(substr($row[5],0,1) == 'g') {$col_string = '#00f000'; $zpt_off = $zp - 25.013;}
    2328         if(substr($row[5],0,1) == 'r') {$col_string = '#f00000'; $zpt_off = $zp - 24.840;}
    2329         if(substr($row[5],0,1) == 'i') {$col_string = '#f05000'; $zpt_off = $zp - 24.711;}
    2330         if(substr($row[5],0,1) == 'z') {$col_string = '#0050a0'; $zpt_off = $zp - 24.300;}
    2331         if(substr($row[5],0,1) == 'y') {$col_string = '#ffe000'; $zpt_off = $zp - 23.570;}
    2332         if(substr($row[5],0,1) == 'w') {$col_string = '#808080'; $zpt_off = $zp - 26.060;}
    2333 
    2334         $tt_string1 = sprintf('time: %s\n zeropoint: %s\n filter: %s\n exp_time: %s\n comment: %s', $timestamp, $zp,$row[5],$row[6],$row[7]);
    2335 #       new Date(Year, Month, Day, Hours, Minutes, Seconds, Milliseconds)
    2336        echo "[new Date($dat[0], $dat[1]-1, $dat[2], $tm[0], $tm[1]), $zpt_off,'$col_string','$tt_string1', $zp_low, $zp_high],";
    2337     }
    2338 
    2339    echo "  ]);";
    2340    echo "   var options = {";
    2341    echo "      chart: {"; 
    2342    echo "               position: 'center', " ;
    2343    echo "              },";
    2344    echo "     title: 'Zeropoint zoom-in during the last night',";
    2345    echo "     titleTextStyle: {color: 'black', fontSize: 15},";
    2346    echo "     width: '100%',";
    2347    echo "     height: 400,";
    2348    echo "     legend: { position: 'top',";
    2349    echo "               alignment: 'center',";
    2350    echo "              },";
    2351    echo "     pointSize: 3, ";
    2352    echo "     series: {";
    2353    echo "         0: { color: '#0000ff'},      ";
    2354    echo "              },";
    2355    echo "     intervals: { color: 'grey','style':'points', pointSize: 1 },  ";
    2356    echo "     hAxis: {  ";
    2357    echo "             title: 'Date/Time (HST)',";
    2358    echo "             format: 'HH:mm',";
    2359    echo "            gridlines: {count: -1},";
    2360    echo "     },";
    2361    echo "     vAxis: {  ";
    2362    echo "                  title: 'Zeropoint offset (mag)',";
    2363    echo "     viewWindow: {   min: -0.25, max: 0.25    },  ";
    2364    echo "     },";
    2365    echo "     chartArea: {left:80, top:50, right:20, bottom:50},";
    2366    echo "     fontSize: 12,";
    2367    echo "   };";
    2368    echo "   var chart = new google.visualization.ScatterChart(document.getElementById('dqzp_zoom_div'));";
    2369    echo "   chart.draw(data, options);";
    2370    echo " }";
    2371    echo "</script>";
    2372 
    2373 
    2374 
    2375     $time = microtime();
    2376     $time = explode(' ', $time);
    2377     $time = $time[1] + $time[0];
    2378     $finish = $time;
    2379     $total_time = round(($finish - $start), 3);
    2380     $start= $finish;
    2381     echo "<center>loading plot in $total_time seconds<br></center>";
    2382 }
    2383 
    23841955
    23851956
     
    32692840}
    32702841
    3271 
    3272 ###########################################################################
    3273 #
    3274 # Get data quality
    3275 #
    3276 ###########################################################################
    3277 function getDataQuality2($db) {
    3278     // Start timer
    3279     $start = microtime(true);
    3280 
    3281     $mjdDay  = getMJD();
    3282     $expname = "o".$mjdDay."%";
    3283 
    3284     $sql = "SELECT SUBTIME(dateobs, '10:00:00') AS time,
    3285                    round(( 367*YEAR(dateobs)- FLOOR((7 * (YEAR(dateobs) + FLOOR((MONTH(dateobs) + 9) / 12))) / 4) + FLOOR(275 * MONTH(dateobs) / 9)
    3286                    + DAY(dateobs) + 1721013.5 + (HOUR(dateobs) / 24.0) + (MINUTE(dateobs) / 1440.0) + (SECOND(dateobs) / 86400.0) - 2400000.5), 5) AS MJD,
    3287                    filter, comment, ra*180/pi() as RA, decl*180/pi() as DECL, exp_name, alt, az, fwhm_major, fwhm_minor, zpt_obs, zpt_stdev, rawExp.exp_id
    3288             FROM rawExp JOIN chipRun using (exp_id) JOIN camRun using (chip_id) JOIN camProcessedExp using (cam_id)
    3289             WHERE rawExp.exp_name like '$expname'
    3290                AND exp_type = 'OBJECT'
    3291                AND camRun.state like 'full'
    3292                AND camProcessedExp.fault = 0
    3293                AND camProcessedExp.zpt_obs != 0; ";
    3294 
    3295     $DQarray = array();
    3296     $qry = $db->query($sql);
    3297     if (dberror($qry)) {
    3298         echo "<b>Error with $sql</b><br>\n";
    3299         return;
    3300     }
    3301    
    3302     while ($qry->fetchInto($row)) {
    3303         $DQarray[] = $row;
    3304     }
    3305    
    3306     // Ensure $DQarray is not empty
    3307     if (empty($DQarray)) {
    3308         echo "<center><b> No data for MJD: $mjdDay </b></center>";
    3309         return; // Stop execution if no data to encode
    3310     }
    3311    
    3312     // Convert data array to JSON for easier handling in JavaScript
    3313     $dataJson = json_encode($DQarray);
    3314    
    3315     if ($dataJson === false) {
    3316         echo "<b>Error encoding JSON:</b> " . json_last_error_msg(); // Display any JSON encoding errors
    3317         return; // Stop execution if JSON encoding fails
    3318     }
    3319    
    3320     echo "<script>console.log(" . json_encode($dataJson) . ");</script>"; // Log JSON output to console for debugging
    3321 
    3322 echo <<<HTML
    3323 <script type="text/javascript" src="loader.js"></script>
    3324 
    3325 <script type="text/javascript">
    3326     google.charts.load('current', {packages: ['corechart']});
    3327 
    3328     // Use the encoded JSON directly in JavaScript
    3329     const dataArray = JSON.parse('{$dataJson}'); // Directly parse the PHP JSON string
    3330 
    3331     // draw FWHM vs MJD
    3332     function drawFWHMChart() {
    3333         var data = new google.visualization.DataTable();
    3334             data.addColumn('number', 'MJD');
    3335             data.addColumn('number', 'FWHM_major');
    3336             data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
    3337             data.addColumn('number', 'FWHM_minor');
    3338             data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
    3339             data.addColumn('number', 'seeing ~ 1"');
    3340             data.addColumn('number', 'seeing ~ 3"');
    3341 
    3342         const rows = dataArray.map(row => {
    3343             const [timestamp, mjd, filter, comment, ra, dec, exp_name, alt, az, iq_fwhm_maj, iq_fwhm_min, zpt_obs, zpt_stdev, exp_id] = row;
    3344 
    3345             return [
    3346                 parseFloat(mjd),      // Position Angle
    3347                 parseFloat(iq_fwhm_maj),         // RA_offset (AST_R0)
    3348                 "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,
    3349                 parseFloat(iq_fwhm_min),        // Dec_offset (AST_D0)
    3350                 "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,
    3351                 4,
    3352                 12
    3353             ];
    3354         });
    3355 
    3356         data.addRows(rows);
    3357 
    3358         const options = {
    3359             title: 'FWHM (MJD)',
    3360             titleTextStyle: {color: 'black', fontSize: 15},
    3361             width: '100%',
    3362             height: 480,
    3363             legend: { position: 'top', alignment: 'center' },
    3364             series: {
    3365                 0: { color: '#0000ff', pointSize: 3, lineWidth: 1 },
    3366                 1: { color: '#33a532', pointSize: 3, lineWidth: 1 },
    3367                 2: { color: 'red', lineWidth: 2, pointSize: 0 },
    3368                 3: { color: 'red', lineWidth: 2, lineDashStyle: [8, 4], pointSize: 0 }
    3369             },
    3370             intervals: { color: 'red', lineWidth: 2, style: 'sticks' },
    3371             hAxis: {
    3372                 title: 'MJD',
    3373                 gridlines: {count: -1}
    3374             },
    3375             vAxis: {
    3376                 title: 'FWHM (pixels)',
    3377             },
    3378             tooltip: { isHtml: true }, // Render tooltips as HTML
    3379             chartArea: {left: '8%', top: 60, right: '5%', bottom: 50}, // Adjust left and right
    3380             fontSize: 12,
    3381         };
    3382 
    3383         const chart = new google.visualization.LineChart(document.getElementById('dq_div_fwhm'));
    3384         chart.draw(data, options);
    3385     }
    3386 
    3387     // draw zoomed zerpoint vs mjd
    3388     function drawZOOMZPTChart() {
    3389         var data = new google.visualization.DataTable();
    3390         data.addColumn('number', 'MJD');
    3391 
    3392         // Add columns for each filter type to handle separate colors
    3393         data.addColumn('number', 'g (24.563, 25.013)');
    3394         data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
    3395         data.addColumn({type: 'number', role: 'interval'});
    3396         data.addColumn({type: 'number', role: 'interval'});
    3397         data.addColumn('number', 'r (24.750, 24.840)');
    3398         data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
    3399         data.addColumn({type: 'number', role: 'interval'});
    3400         data.addColumn({type: 'number', role: 'interval'});
    3401         data.addColumn('number', 'i (24.611, 24.711)');
    3402         data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
    3403         data.addColumn({type: 'number', role: 'interval'});
    3404         data.addColumn({type: 'number', role: 'interval'});
    3405         data.addColumn('number', 'z (24.240, 24.300)');
    3406         data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
    3407         data.addColumn({type: 'number', role: 'interval'});
    3408         data.addColumn({type: 'number', role: 'interval'});
    3409         data.addColumn('number', 'y (23.320, 23.570)');
    3410         data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
    3411         data.addColumn({type: 'number', role: 'interval'});
    3412         data.addColumn({type: 'number', role: 'interval'});
    3413         data.addColumn('number', 'w (26.000, 26.300)');
    3414         data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
    3415         data.addColumn({type: 'number', role: 'interval'});
    3416         data.addColumn({type: 'number', role: 'interval'});
    3417 
    3418         const rows = dataArray.map(row => {
    3419             const [timestamp, mjd, filter, comment, ra, dec, exp_name, alt, az, iq_fwhm_maj, iq_fwhm_min, zpt_obs, zpt_stdev, exp_id] = row;
    3420 
    3421             // Initialize all filter values as null
    3422             let zp_g = null, zp_r = null, zp_i = null, zp_z = null, zp_y = null, zp_w = null;
    3423 
    3424             // Set the zeropoint offset based on filter and assign it to the correct series
    3425             switch (filter[0]) {
    3426                 case 'g':
    3427                     zp_g = zpt_obs - 24.563;
    3428                     break;
    3429                 case 'r':
    3430                     zp_r = zpt_obs - 24.750;
    3431                     break;
    3432                 case 'i':
    3433                     zp_i = zpt_obs - 24.611;
    3434                     break;
    3435                 case 'z':
    3436                     zp_z = zpt_obs - 24.240;
    3437                     break;
    3438                 case 'y':
    3439                    zp_y = zpt_obs - 23.320;
    3440                     break;
    3441                 case 'w':
    3442                     zp_w = zpt_obs - 26.000;
    3443                     break;
    3444             }
    3445 
    3446             return [
    3447                 parseFloat(mjd), // MJD value
    3448                 zp_g, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,  parseFloat(zp_g - zpt_stdev), parseFloat(zp_g - zpt_stdev*-1),
    3449                 zp_r, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,  parseFloat(zp_r - zpt_stdev), parseFloat(zp_r - zpt_stdev*-1),
    3450                 zp_i, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,  parseFloat(zp_i - zpt_stdev), parseFloat(zp_i - zpt_stdev*-1),
    3451                 zp_z, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,  parseFloat(zp_z - zpt_stdev), parseFloat(zp_z - zpt_stdev*-1),
    3452                 zp_y, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,  parseFloat(zp_y - zpt_stdev), parseFloat(zp_y - zpt_stdev*-1),
    3453                 zp_w, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,  parseFloat(zp_w - zpt_stdev), parseFloat(zp_w - zpt_stdev*-1)
    3454             ];
    3455         });
    3456 
    3457         data.addRows(rows);
    3458 
    3459         const options = {
    3460             title: 'Zeropoint zoom-in (MJD)',
    3461             titleTextStyle: { color: 'black', fontSize: 15 },
    3462             width: '100%',
    3463             height: 480,
    3464             legend: { position: 'top', alignment: 'start', maxLines: 3 },
    3465             series: {
    3466                 0: { color: '#00f000', pointSize: 10, pointShape: 'circle',   linewidth: 1},  // 'g' filter
    3467                 1: { color: '#f00000', pointSize: 10, pointShape: 'triangle', linewidth: 1},  // 'r' filter
    3468                 2: { color: '#f05000', pointSize: 10, pointShape: 'square',   linewidth: 1},  // 'i' filter
    3469                 3: { color: '#0050a0', pointSize: 10, pointShape: 'diamond',  linewidth: 1},  // 'z' filter
    3470                 4: { color: '#ffe000', pointSize: 10, pointShape: 'star',     linewidth: 1},  // 'y' filter
    3471                 5: { color: '#808080', pointSize: 10, pointShape: 'polygon',  linewidth: 1},  // 'w' filter
    3472             },
    3473             intervals: { color: 'red', lineWidth: 1, style: 'sticks' },
    3474             hAxis: {
    3475                 title: 'MJD',
    3476                 gridlines: { count: -1 }
    3477             },
    3478             vAxis: {
    3479                 title: 'Zeropoint (mag)',
    3480                 viewWindow: {   min: -1.00, max: .5    },
    3481             },
    3482             tooltip: { isHtml: true },
    3483             chartArea: { left: '9%', top: 60, right: '5%', bottom: 50 },
    3484             fontSize: 12,
    3485         };
    3486 
    3487         const chart = new google.visualization.LineChart(document.getElementById('dq_div_zoomzpt'));
    3488         chart.draw(data, options);
    3489     }
    3490 
    3491     // draw zeropoint vs mjd
    3492     function drawZPTChart() {
    3493         var data = new google.visualization.DataTable();
    3494         data.addColumn('number', 'MJD');
    3495 
    3496         // Add columns for each filter type to handle separate colors
    3497         data.addColumn('number', 'g' );
    3498         data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
    3499         data.addColumn({type: 'number', role: 'interval'});
    3500         data.addColumn({type: 'number', role: 'interval'});
    3501         data.addColumn('number', 'r' );
    3502         data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
    3503         data.addColumn({type: 'number', role: 'interval'});
    3504         data.addColumn({type: 'number', role: 'interval'});
    3505         data.addColumn('number', 'i' );
    3506         data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
    3507         data.addColumn({type: 'number', role: 'interval'});
    3508         data.addColumn({type: 'number', role: 'interval'});
    3509         data.addColumn('number', 'z' );
    3510         data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
    3511         data.addColumn({type: 'number', role: 'interval'});
    3512         data.addColumn({type: 'number', role: 'interval'});
    3513         data.addColumn('number', 'y' );
    3514         data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
    3515         data.addColumn({type: 'number', role: 'interval'});
    3516         data.addColumn({type: 'number', role: 'interval'});
    3517         data.addColumn('number', 'w' );
    3518         data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
    3519         data.addColumn({type: 'number', role: 'interval'});
    3520         data.addColumn({type: 'number', role: 'interval'});
    3521 
    3522         // Count occurrences of each filter
    3523         const filterCounts = { g: 0, r: 0, i: 0, z: 0, y: 0, w: 0 };
    3524        const rows = dataArray.map(row => {
    3525             const [timestamp, mjd, filter, comment, ra, dec, exp_name, alt, az, iq_fwhm_maj, iq_fwhm_min, zpt_obs, zpt_stdev, exp_id] = row;
    3526 
    3527             // Initialize all filter values as null
    3528             let zp_g = null, zp_r = null, zp_i = null, zp_z = null, zp_y = null, zp_w = null;
    3529 
    3530             // Increment the count for the filter
    3531             if (filterCounts.hasOwnProperty(filter[0])) {
    3532                 filterCounts[filter[0]] += 1;
    3533             }
    3534 
    3535             // Set the zeropoint offset based on filter and assign it to the correct series
    3536             switch (filter[0]) {
    3537                 case 'g':
    3538                     zp_g = zpt_obs - 0.0;
    3539                     break;
    3540                 case 'r':
    3541                     zp_r = zpt_obs - 0.0;
    3542                     break;
    3543                 case 'i':
    3544                     zp_i = zpt_obs - 0.0;
    3545                     break;
    3546                 case 'z':
    3547                     zp_z = zpt_obs - 0.0;
    3548                     break;
    3549                 case 'y':
    3550                     zp_y = zpt_obs - 0.0;
    3551                     break;
    3552                 case 'w':
    3553                     zp_w = zpt_obs - 0.0;
    3554                     break;
    3555             }
    3556 
    3557             return [
    3558                 parseFloat(mjd), // MJD value
    3559                 zp_g, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,  parseFloat(zp_g - zpt_stdev), parseFloat(zp_g - zpt_stdev*-1),
    3560                 zp_r, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,  parseFloat(zp_r - zpt_stdev), parseFloat(zp_r - zpt_stdev*-1),
    3561                 zp_i, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,  parseFloat(zp_i - zpt_stdev), parseFloat(zp_i - zpt_stdev*-1),
    3562                 zp_z, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,  parseFloat(zp_z - zpt_stdev), parseFloat(zp_z - zpt_stdev*-1),
    3563                 zp_y, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,  parseFloat(zp_y - zpt_stdev), parseFloat(zp_y - zpt_stdev*-1),
    3564                 zp_w, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment,  parseFloat(zp_w - zpt_stdev), parseFloat(zp_w - zpt_stdev*-1),
    3565 
    3566             ];
    3567         });
    3568       data.addRows(rows);
    3569 
    3570         // Include counts in the title using string concatenation
    3571         const countsTitle = 'Zeropoint (MJD) g: ' + filterCounts.g + ', r: ' + filterCounts.r + ', i: ' + filterCounts.i + ', z: ' + filterCounts.z + ', y: ' + filterCounts.y + ', w: ' + filterCounts.w;
    3572 
    3573 
    3574         console.log(countsTitle);
    3575 
    3576         const options = {
    3577             title: countsTitle,
    3578             titleTextStyle: { color: 'black', fontSize: 15 },
    3579             width: '100%',
    3580             height: 480,
    3581             legend: { position: 'top', alignment: 'center' },
    3582             series: {
    3583                 0: { color: '#00f000', pointSize: 10, pointShape: 'circle',   linewidth: 1},  // 'g' filter
    3584                 1: { color: '#f00000', pointSize: 10, pointShape: 'triangle', linewidth: 1},  // 'r' filter
    3585                 2: { color: '#f05000', pointSize: 10, pointShape: 'square',   linewidth: 1},  // 'i' filter
    3586                 3: { color: '#0050a0', pointSize: 10, pointShape: 'diamond',  linewidth: 1},  // 'z' filter
    3587                 4: { color: '#ffe000', pointSize: 10, pointShape: 'star',     linewidth: 1},  // 'y' filter
    3588                 5: { color: '#808080', pointSize: 10, pointShape: 'polygon',  linewidth: 1},  // 'w' filter
    3589             },
    3590             intervals: { color: 'red', lineWidth: 1, style: 'sticks' },
    3591             hAxis: {
    3592                 title: 'MJD',
    3593                 gridlines: { count: -1 }
    3594             },
    3595             vAxis: {
    3596                 title: 'Zeropoint (mag)'
    3597             },
    3598             tooltip: { isHtml: true },
    3599             chartArea: { left: '9%', top: 60, right: '5%', bottom: 50 },
    3600             fontSize: 12,
    3601         };
    3602 
    3603         const chart = new google.visualization.LineChart(document.getElementById('dq_div_zpt'));
    3604         chart.draw(data, options);
    3605     }
    3606 
    3607     google.charts.setOnLoadCallback(drawFWHMChart);
    3608     google.charts.setOnLoadCallback(drawZPTChart);
    3609     google.charts.setOnLoadCallback(drawZOOMZPTChart);
    3610 
    3611 </script>
    3612 
    3613 <div class="chartWithOverlay" style="position: relative; width: 640px; height:500px;">
    3614     <div id="dq_div_fwhm" style="width:640px;"></div>
    3615     <div class="overlay" style="position: absolute; width: 400px; bottom: 60px; left: 85px;"></div>
    3616 </div>
    3617 
    3618 <div class="chartWithOverlay" style="position: relative; width: 640px; height:500px;">
    3619     <div id="dq_div_zpt" style="width:640px;"></div>
    3620     <div class="overlay" style="position: absolute; width: 400px; bottom: 60px; left: 85px;"></div>
    3621 </div>
    3622 
    3623 <div class="chartWithOverlay" style="position: relative; width: 640px; height:500px;">
    3624     <div id="dq_div_zoomzpt" style="width:640px;"></div>
    3625     <div class="overlay" style="position: absolute; width: 400px; bottom: 60px; left: 85px;"></div>
    3626 </div>
    3627 
    3628 HTML;
    3629     // End timer
    3630     $total_time = round(microtime(true) - $start, 3);
    3631     echo "<center>loading plot in $total_time seconds<br></center>";
    3632 
    3633 }
    3634 
    3635 
    3636 
    36372842function getprocrate ($proc1, $proc0, $trange) {
    36382843  if($proc1 > $proc0) {
     
    36462851
    36472852
    3648 
    36492853?>
  • trunk/ippMonitor/raw/dataquality.php

    r42795 r42972  
    1515else {$myMenu = "ipp.czar.dat";}
    1616
    17 menu($myMenu, 'Czartool on '.$lastUpdateTime, 'ipp.css', $ID['link'], $ID['proj']);
     17menu($myMenu, 'Czartool on', ' ipp.css', $ID['link'], $ID['proj']);
    1818
    1919$pass = $ID['pass'];
     
    2222
    2323// Fetch data based on range selection
    24 $range = isset($_GET['range']) ? $_GET['range'] : 'yday';
     24$range = isset($_GET['range']) ? $_GET['range'] : 'week';
     25
     26require_once 'functions.php';
    2527
    2628echo "<table border=\"0\">";
     
    2830echo "  <td style=width:1240>";
    2931echo "    <h1 align=\"middle\">Data Quality Report</h1>";
    30 echo "  <center>";
    31 echo '    <a href="?pass=' . urlencode($pass) . '&proj=' . urlencode($proj) . '&range=yday">Last night (default)</a> | ';
    32 echo '    <a href="?pass=' . urlencode($pass) . '&proj=' . urlencode($proj) . '&range=day">Today (MJD '.getMJD().')</a> | ';
    33 echo '    <a href="?pass=' . urlencode($pass) . '&proj=' . urlencode($proj) . '&range=week">Past 7 days</a> | ';
    34 echo '    <a href="?pass=' . urlencode($pass) . '&proj=' . urlencode($proj) . '&range=month">Past 30 days</a> | ';
    35 echo '    <a href="?pass=' . urlencode($pass) . '&proj=' . urlencode($proj) . '&range=year">Past 365 days</a> | ';
    36 echo '    <a href="?pass=' . urlencode($pass) . '&proj=' . urlencode($proj) . '&range=all">All </a>   ';
     32
     33#
     34# project selector
     35echo '<center>';
     36# Project selector with highlight
     37$projects = ["gpc1" => "GPC1", "gpc2" => "GPC2"];
     38foreach ($projects as $key => $label) {
     39    if ($proj == $key) {
     40        echo "<strong style='color:blue;'>$label</strong> | ";
     41    } else {
     42        echo '<a href="?pass=' . urlencode($pass) . '&proj=' . $key . '&range=' . urlencode($range) . '">' . $label . '</a> | ';
     43    }
     44}
     45echo "<br>";
     46
     47# range links with highlighting
     48$ranges = [
     49    "today"     => "Today",
     50    "yesterday" => "Yesterday",
     51    "week"      => "Week",
     52    "month"     => "Month",
     53    "year"      => "Year",
     54    "all"       => "All"
     55];
     56
     57foreach ($ranges as $key => $label) {
     58    if ($range == $key) {
     59        echo "<strong style='color:blue;'>$label</strong> | ";
     60    } else {
     61        echo '<a href="?pass=' . urlencode($pass) . '&proj=' . urlencode($proj) . '&range=' . $key . '">' . $label . '</a> | ';
     62    }
     63}
     64
    3765echo "  </td>";
    3866echo "</tr>";
    3967echo "</table>";
    40 echo getDataQuality($projectdb, $range);
     68
     69echo "    <div style=\"margin-bottom:10px; font-size:14px; color:#555;\">";
     70echo "      Current MJD: " . getMJD();
     71echo "    </div>";
     72
     73echo getDataQuality($projectdb, $range, $proj);
    4174
    4275menu_end();
    4376
    44 /////////////////////////////////////////////////////////////////////////////
    45 //                                                                         //
    46 //                               Functions                                 //
    47 //                                                                         //
    48 /////////////////////////////////////////////////////////////////////////////
    49 
    50 ###########################################################################
    51 #
    52 # Get data quality
    53 #
    54 ###########################################################################
    55 function getDataQuality($db, $range = 'yday') {
    56     // Start timer
    57     $start = microtime(true);
    58 
    59     $mjdDay  = getMJD();
    60     $mjdDay_yearback = $mjdDay - 366;
    61     $mjdDay_monthback = $mjdDay - 32;
    62     $min_exp_id = 0;
    63 
    64     // Determine the reference exp_name based on the selected range
    65     switch ($range) {
    66         case 'year':
    67             $expname = "o" . $mjdDay_yearback . "%";
    68             break;
    69         case 'month':
    70             $expname = "o" . $mjdDay_monthback . "%";
    71             break;
    72         case 'week':
    73             $expname = "o" . ($mjdDay - 7) . "%";
    74             break;
    75         case 'day':
    76             $expname = "o" . $mjdDay . "%";
    77             break;
    78         default:
    79             $expname = "o" . ($mjdDay - 1) . "%"; // Default: last night
    80             break;
    81     }
    82 
    83     // Query to get the minimum exp_id
    84     $expIdQuery = "SELECT MIN(exp_id) AS min_exp_id FROM rawExp WHERE rawExp.exp_name LIKE '$expname'";
    85     $result = $db->query($expIdQuery);
    86 
    87     if (DB::isError($result)) {
    88         die("Database error: " . $result->getMessage());
    89     }
    90 
    91     $row = $result->fetchRow(DB_FETCHMODE_ASSOC);
    92 
    93     if (!empty($row['min_exp_id'])) {
    94         $min_exp_id = $row['min_exp_id'];
    95     //    echo "Minimum exp_id: " . $min_exp_id;
    96     } else {
    97         echo "No results found.";
    98     }
    99 
    100     //echo "<br><br>";
    101 
    102     // Determine the date constraint based on the range
    103     switch ($range) {
    104         case 'year':
    105             $dateConstraint = "dateobs >= NOW() - INTERVAL 365 DAY AND rawExp.exp_id >= $min_exp_id";
    106             break;
    107         case 'month':
    108             $dateConstraint = "dateobs >= NOW() - INTERVAL 30 DAY AND rawExp.exp_id >= $min_exp_id";
    109             break;
    110         case 'week':
    111             $expnames = array();
    112             for ($i = 0; $i < 7; $i++) {
    113                 $expnames[] = "rawExp.exp_name LIKE 'o" . ($mjdDay - $i) . "%'";
    114             }
    115             $dateConstraint = "(" . implode(" OR ", $expnames) . ") AND rawExp.exp_id >= $min_exp_id";
    116             break;
    117         case 'yday':
    118             $dateConstraint = "rawExp.exp_name LIKE 'o" . ($mjdDay - 1) . "%' AND rawExp.exp_id >= $min_exp_id";
    119             break;
    120         case 'day':
    121             $dateConstraint = "rawExp.exp_name LIKE 'o$mjdDay%' AND rawExp.exp_id >= $min_exp_id";
    122             break;
    123         case 'all':
    124             $dateConstraint = "rawExp.exp_id >= 0";
    125             break;
    126         default: // Default to last night
    127             $dateConstraint = "rawExp.exp_name LIKE 'o" . ($mjdDay - 1) . "%' AND rawExp.exp_id >= $min_exp_id";
    128             break;
    129     }
    130 
    131     // Updated SQL query
    132     $sql = "select * from (SELECT SUBTIME(dateobs, '10:00:00') AS time,
    133                    round(( 367*YEAR(dateobs)- FLOOR((7 * (YEAR(dateobs) + FLOOR((MONTH(dateobs) + 9) / 12))) / 4) + FLOOR(275 * MONTH(dateobs) / 9)
    134                    + DAY(dateobs) + 1721013.5 + (HOUR(dateobs) / 24.0) + (MINUTE(dateobs) / 1440.0) + (SECOND(dateobs) / 86400.0) - 2400000.5), 5) AS MJD,
    135                    filter, comment, ra*180/pi() as RA, decl*180/pi() as DECL, exp_name, alt, az, fwhm_major, fwhm_minor, zpt_obs, zpt_stdev, rawExp.exp_id, camRun.label
    136             FROM rawExp
    137             JOIN chipRun USING (exp_id)
    138             JOIN camRun USING (chip_id)
    139             JOIN camProcessedExp USING (cam_id)
    140             WHERE $dateConstraint
    141               AND exp_type = 'OBJECT'
    142               AND camRun.state LIKE 'full'
    143               AND camProcessedExp.fault = 0
    144               AND camProcessedExp.zpt_obs != 0 ORDER BY RAND() limit 5000) as sub order by time;";
    145 
    146     $DQarray = array();
    147     $qry = $db->query($sql);
    148     if (dberror($qry)) {
    149         echo "<b>Error with $sql</b><br>\n";
    150         return;
    151     }
    152     //    echo "$sql<br><br>\n";
    153 
    154     while ($qry->fetchInto($row)) {
    155         $DQarray[] = $row;
    156     }
    157 
    158     // Ensure $DQarray is not empty
    159     if (empty($DQarray)) {
    160         echo "<center><b> No data for MJD: $mjdDay </b></center>";
    161         return; // Stop execution if no data to encode
    162     }
    163 
    164     // Convert data array to JSON for easier handling in JavaScript
    165     $dataJson = json_encode($DQarray);
    166 
    167     if ($dataJson === false) {
    168         echo "<b>Error encoding JSON:</b> " . json_last_error_msg(); // Display any JSON encoding errors
    169         return; // Stop execution if JSON encoding fails
    170     }
    171 
    172     echo "<script>console.log(" . json_encode($dataJson) . ");</script>"; // Log JSON output to console for debugging
    173 echo <<<HTML
    174 <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js" onerror="loadLocalGoogleCharts()"></script>
    175 
    176 <script type="text/javascript">
    177     // Check if Google Charts library loaded successfully
    178     window.addEventListener("load", function() {
    179         if (typeof google === "undefined" || typeof google.charts === "undefined") {
    180             loadLocalGoogleCharts();
    181         }
    182     });
    183 
    184     // Function to load local version of Google Charts if CDN fails
    185     function loadLocalGoogleCharts() {
    186         console.warn("Google Charts CDN failed, loading local loader.js instead.");
    187         var script = document.createElement("script");
    188         script.src = "loader.js"; // Path to your local loader.js file
    189         document.head.appendChild(script);
    190     }
    191 </script>
    192 
    193 <script type="text/javascript">
    194     google.charts.load('current', {packages: ['corechart']});
    195 
    196     // Use the encoded JSON directly in JavaScript
    197     const dataArray = JSON.parse('{$dataJson}'); // Directly parse the PHP JSON string
    198 
    199     // draw FWHM vs MJD
    200     function drawFWHMChart() {
    201         var data = new google.visualization.DataTable();
    202             data.addColumn('number', 'MJD');
    203             data.addColumn('number', 'FWHM_major');
    204             data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
    205             data.addColumn('number', 'FWHM_minor');
    206             data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
    207             data.addColumn('number', 'seeing ~ 1"');
    208             data.addColumn('number', 'seeing ~ 3"');
    209 
    210         const rows = dataArray.map(row => {
    211             const [timestamp, mjd, filter, comment, ra, dec, exp_name, alt, az, iq_fwhm_maj, iq_fwhm_min, zpt_obs, zpt_stdev, exp_id, cam_label] = row;
    212 
    213             return [
    214                 parseFloat(mjd),      // Position Angle
    215                 parseFloat(iq_fwhm_maj),         // RA_offset (AST_R0)
    216                 "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label,
    217                 parseFloat(iq_fwhm_min),        // Dec_offset (AST_D0)
    218                 "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label,
    219                 4,
    220                 12
    221             ];
    222         });
    223 
    224         data.addRows(rows);
    225 
    226         // Extract the first MJD value from dataArray
    227         //const firstMJD = dataArray.length > 0 ? parseFloat(dataArray[0][1]) : "N/A";
    228         const firstMJD = dataArray.length > 0 ? Math.floor(parseFloat(dataArray[0][1])) : "N/A";
    229         console.log("First MJD Value:", firstMJD);
    230 
    231         const options = {
    232             title: 'FWHM (MJD: '+firstMJD+')',
    233             titleTextStyle: {color: 'black', fontSize: 15},
    234             width: '100%',
    235             height: 480,
    236             legend: { position: 'top', alignment: 'center' },
    237             series: {
    238                 0: { color: '#0000ff', pointSize: 2, lineWidth: 0. },
    239                 1: { color: '#33a532', pointSize: 2, lineWidth: 0. },
    240                 2: { color: 'red', lineWidth: 2, pointSize: 0 },
    241                 3: { color: 'red', lineWidth: 2, lineDashStyle: [8, 4], pointSize: 0 }
    242             },
    243             intervals: { color: 'red', lineWidth: 2, style: 'sticks' },
    244             hAxis: {
    245                 title: 'MJD',
    246                 gridlines: {count: -1}
    247             },
    248             vAxis: {
    249                 title: 'FWHM (pixels)',
    250             },
    251             tooltip: { isHtml: true }, // Render tooltips as HTML
    252             chartArea: {left: '8%', top: 60, right: '5%', bottom: 50}, // Adjust left and right
    253             fontSize: 12,
    254         };
    255 
    256         const chart = new google.visualization.ScatterChart(document.getElementById('dq_div_fwhm'));
    257         chart.draw(data, options);
    258     }
    259 
    260     // draw zoomed zerpoint vs mjd
    261     function drawZOOMZPTChart() {
    262         var data = new google.visualization.DataTable();
    263         data.addColumn('number', 'MJD');
    264 
    265         // Add columns for each filter type to handle separate colors
    266         data.addColumn('number', 'g (24.563, 25.013)');
    267         data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
    268         data.addColumn({type: 'number', role: 'interval'});
    269         data.addColumn({type: 'number', role: 'interval'});
    270         data.addColumn('number', 'r (24.750, 24.840)');
    271         data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
    272         data.addColumn({type: 'number', role: 'interval'});
    273         data.addColumn({type: 'number', role: 'interval'});
    274         data.addColumn('number', 'i (24.611, 24.711)');
    275         data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
    276         data.addColumn({type: 'number', role: 'interval'});
    277         data.addColumn({type: 'number', role: 'interval'});
    278         data.addColumn('number', 'z (24.240, 24.300)');
    279         data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
    280         data.addColumn({type: 'number', role: 'interval'});
    281         data.addColumn({type: 'number', role: 'interval'});
    282         data.addColumn('number', 'y (23.320, 23.570)');
    283         data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
    284         data.addColumn({type: 'number', role: 'interval'});
    285         data.addColumn({type: 'number', role: 'interval'});
    286         data.addColumn('number', 'w (26.000, 26.300)');
    287         data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
    288         data.addColumn({type: 'number', role: 'interval'});
    289         data.addColumn({type: 'number', role: 'interval'});
    290 
    291         const rows = dataArray.map(row => {
    292             const [timestamp, mjd, filter, comment, ra, dec, exp_name, alt, az, iq_fwhm_maj, iq_fwhm_min, zpt_obs, zpt_stdev, exp_id, cam_label] = row;
    293 
    294             // Initialize all filter values as null
    295             let zp_g = null, zp_r = null, zp_i = null, zp_z = null, zp_y = null, zp_w = null;
    296 
    297             // Set the zeropoint offset based on filter and assign it to the correct series
    298             switch (filter[0]) {
    299                 case 'g':
    300                     zp_g = zpt_obs - 24.563;
    301                     break;
    302                 case 'r':
    303                     zp_r = zpt_obs - 24.750;
    304                     break;
    305                 case 'i':
    306                     zp_i = zpt_obs - 24.611;
    307                     break;
    308                 case 'z':
    309                     zp_z = zpt_obs - 24.240;
    310                     break;
    311                 case 'y':
    312                    zp_y = zpt_obs - 23.320;
    313                     break;
    314                 case 'w':
    315                     zp_w = zpt_obs - 26.000;
    316                     break;
    317             }
    318 
    319             return [
    320                 parseFloat(mjd), // MJD value
    321                 zp_g, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label, 
    322 parseFloat(zp_g - zpt_stdev), parseFloat(zp_g - zpt_stdev*-1),
    323                 zp_r, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label, 
    324 parseFloat(zp_r - zpt_stdev), parseFloat(zp_r - zpt_stdev*-1),
    325                 zp_i, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label, 
    326 parseFloat(zp_i - zpt_stdev), parseFloat(zp_i - zpt_stdev*-1),
    327                 zp_z, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label, 
    328 parseFloat(zp_z - zpt_stdev), parseFloat(zp_z - zpt_stdev*-1),
    329                 zp_y, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label, 
    330 parseFloat(zp_y - zpt_stdev), parseFloat(zp_y - zpt_stdev*-1),
    331                 zp_w, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label, 
    332 parseFloat(zp_w - zpt_stdev), parseFloat(zp_w - zpt_stdev*-1)
    333             ];
    334         });
    335 
    336         data.addRows(rows);
    337 
    338         const options = {
    339             title: 'Zeropoint zoom-in (MJD)',
    340             titleTextStyle: { color: 'black', fontSize: 15 },
    341             width: '100%',
    342             height: 480,
    343             legend: { position: 'top', alignment: 'start', maxLines: 3 },
    344             series: {
    345                 0: { color: '#00f000', pointSize: 5, pointShape: 'circle',   linewidth: .0},  // 'g' filter
    346                 1: { color: '#f00000', pointSize: 5, pointShape: 'triangle', linewidth: .0},  // 'r' filter
    347                 2: { color: '#f05000', pointSize: 5, pointShape: 'square',   linewidth: .0},  // 'i' filter
    348                 3: { color: '#0050a0', pointSize: 5, pointShape: 'diamond',  linewidth: .0},  // 'z' filter
    349                 4: { color: '#ffe000', pointSize: 5, pointShape: 'star',     linewidth: .0},  // 'y' filter
    350                 5: { color: '#808080', pointSize: 5, pointShape: 'polygon',  linewidth: .0},  // 'w' filter
    351             },
    352             intervals: { color: 'red', lineWidth: .0, style: 'sticks' },
    353             hAxis: {
    354                 title: 'MJD',
    355                 gridlines: { count: -1 }
    356             },
    357             vAxis: {
    358                 title: 'Zeropoint (mag)',
    359                 viewWindow: {   min: -1.00, max: .5    },
    360             },
    361             tooltip: { isHtml: true },
    362             chartArea: { left: '9%', top: 60, right: '5%', bottom: 50 },
    363             fontSize: 12,
    364         };
    365 
    366         const chart = new google.visualization.ScatterChart(document.getElementById('dq_div_zoomzpt'));
    367         chart.draw(data, options);
    368     }
    369 
    370     // draw zeropoint vs mjd
    371     function drawZPTChart() {
    372         var data = new google.visualization.DataTable();
    373         data.addColumn('number', 'MJD');
    374 
    375         // Add columns for each filter type to handle separate colors
    376         data.addColumn('number', 'g' );
    377         data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
    378         data.addColumn({type: 'number', role: 'interval'});
    379         data.addColumn({type: 'number', role: 'interval'});
    380         data.addColumn('number', 'r' );
    381         data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
    382         data.addColumn({type: 'number', role: 'interval'});
    383         data.addColumn({type: 'number', role: 'interval'});
    384         data.addColumn('number', 'i' );
    385         data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
    386         data.addColumn({type: 'number', role: 'interval'});
    387         data.addColumn({type: 'number', role: 'interval'});
    388         data.addColumn('number', 'z' );
    389         data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
    390         data.addColumn({type: 'number', role: 'interval'});
    391         data.addColumn({type: 'number', role: 'interval'});
    392         data.addColumn('number', 'y' );
    393         data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
    394         data.addColumn({type: 'number', role: 'interval'});
    395         data.addColumn({type: 'number', role: 'interval'});
    396         data.addColumn('number', 'w' );
    397         data.addColumn({type: 'string', role: 'tooltip', 'p': {'html': true}});
    398         data.addColumn({type: 'number', role: 'interval'});
    399         data.addColumn({type: 'number', role: 'interval'});
    400 
    401         // Count occurrences of each filter
    402         const filterCounts = { g: 0, r: 0, i: 0, z: 0, y: 0, w: 0 };
    403        const rows = dataArray.map(row => {
    404             const [timestamp, mjd, filter, comment, ra, dec, exp_name, alt, az, iq_fwhm_maj, iq_fwhm_min, zpt_obs, zpt_stdev, exp_id, cam_label] = row;
    405 
    406             // Initialize all filter values as null
    407             let zp_g = null, zp_r = null, zp_i = null, zp_z = null, zp_y = null, zp_w = null;
    408 
    409             // Increment the count for the filter
    410             if (filterCounts.hasOwnProperty(filter[0])) {
    411                 filterCounts[filter[0]] += 1;
    412             }
    413 
    414             // Set the zeropoint offset based on filter and assign it to the correct series
    415             switch (filter[0]) {
    416                 case 'g':
    417                     zp_g = zpt_obs - 0.0;
    418                     break;
    419                 case 'r':
    420                     zp_r = zpt_obs - 0.0;
    421                     break;
    422                 case 'i':
    423                     zp_i = zpt_obs - 0.0;
    424                     break;
    425                 case 'z':
    426                     zp_z = zpt_obs - 0.0;
    427                     break;
    428                 case 'y':
    429                     zp_y = zpt_obs - 0.0;
    430                     break;
    431                 case 'w':
    432                     zp_w = zpt_obs - 0.0;
    433                     break;
    434             }
    435 
    436             return [
    437                 parseFloat(mjd), // MJD value
    438                 zp_g, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label, 
    439 parseFloat(zp_g - zpt_stdev), parseFloat(zp_g - zpt_stdev*-1),
    440                 zp_r, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label, 
    441 parseFloat(zp_r - zpt_stdev), parseFloat(zp_r - zpt_stdev*-1),
    442                 zp_i, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label, 
    443 parseFloat(zp_i - zpt_stdev), parseFloat(zp_i - zpt_stdev*-1),
    444                 zp_z, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label, 
    445 parseFloat(zp_z - zpt_stdev), parseFloat(zp_z - zpt_stdev*-1),
    446                 zp_y, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label, 
    447 parseFloat(zp_y - zpt_stdev), parseFloat(zp_y - zpt_stdev*-1),
    448                 zp_w, "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>HST: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label, 
    449 parseFloat(zp_w - zpt_stdev), parseFloat(zp_w - zpt_stdev*-1),
    450             ];
    451         });
    452       data.addRows(rows);
    453 
    454         // Include counts in the title using string concatenation
    455         const countsTitle = 'Zeropoint (MJD) g: ' + filterCounts.g + ', r: ' + filterCounts.r + ', i: ' + filterCounts.i + ', z: ' + filterCounts.z + ', y: ' + filterCounts.y + ', w: ' + filterCounts.w;
    456 
    457 
    458         console.log(countsTitle);
    459 
    460         const options = {
    461             title: countsTitle,
    462             titleTextStyle: { color: 'black', fontSize: 15 },
    463             width: '100%',
    464             height: 480,
    465             legend: { position: 'top', alignment: 'center' },
    466             series: {
    467                 0: { color: '#00f000', pointSize: 5, pointShape: 'circle',   linewidth: 1},  // 'g' filter
    468                 1: { color: '#f00000', pointSize: 5, pointShape: 'triangle', linewidth: 1},  // 'r' filter
    469                 2: { color: '#f05000', pointSize: 5, pointShape: 'square',   linewidth: 1},  // 'i' filter
    470                 3: { color: '#0050a0', pointSize: 5, pointShape: 'diamond',  linewidth: 1},  // 'z' filter
    471                 4: { color: '#ffe000', pointSize: 5, pointShape: 'star',     linewidth: 1},  // 'y' filter
    472                 5: { color: '#808080', pointSize: 5, pointShape: 'polygon',  linewidth: 1},  // 'w' filter
    473             },
    474             intervals: { color: 'red', lineWidth: 0, style: 'sticks' },
    475             hAxis: {
    476                 title: 'MJD',
    477                 gridlines: { count: -1 }
    478             },
    479             vAxis: {
    480                 title: 'Zeropoint (mag)'
    481             },
    482             tooltip: { isHtml: true },
    483             chartArea: { left: '9%', top: 60, right: '5%', bottom: 50 },
    484             fontSize: 12,
    485         };
    486 
    487         const chart = new google.visualization.ScatterChart(document.getElementById('dq_div_zpt'));
    488         chart.draw(data, options);
    489     }
    490 
    491     google.charts.setOnLoadCallback(drawFWHMChart);
    492     google.charts.setOnLoadCallback(drawZPTChart);
    493     google.charts.setOnLoadCallback(drawZOOMZPTChart);
    494 
    495 </script>
    496 
    497 <div class="chartWithOverlay" style="position: relative; width: 1240px; height:500px;">
    498     <div id="dq_div_fwhm" style="width:100%;"></div>
    499     <div class="overlay" style="position: absolute; width: 400px; bottom: 60px; left: 85px;"></div>
    500 </div>
    501 
    502 <div class="chartWithOverlay" style="position: relative; width: 1240px; height:500px;">
    503     <div id="dq_div_zpt" style="width:100%;"></div>
    504     <div class="overlay" style="position: absolute; width: 400px; bottom: 60px; left: 85px;"></div>
    505 </div>
    506 
    507 <div class="chartWithOverlay" style="position: relative; width: 1240px; height:500px;">
    508     <div id="dq_div_zoomzpt" style="width:100%;"></div>
    509     <div class="overlay" style="position: absolute; width: 400px; bottom: 60px; left: 85px;"></div>
    510 </div>
    511 
    512 HTML;
    513     // End timer
    514     $total_time = round(microtime(true) - $start, 3);
    515     echo "<center>loading plot in $total_time seconds<br></center>";
    516 
    517 }
    518 
    519 
    520 
    52177?>
    52278
  • trunk/ippMonitor/raw/pointing.php

    r42795 r42972  
    1515else {$myMenu = "ipp.czar.dat";}
    1616
    17 menu($myMenu, 'Czartool on '.$lastUpdateTime, 'ipp.css', $ID['link'], $ID['proj']);
     17menu($myMenu, 'Czartool on ', 'ipp.css', $ID['link'], $ID['proj']);
    1818
    1919$pass = $ID['pass'];
     
    2222
    2323// Fetch data based on range selection
    24 $range = isset($_GET['range']) ? $_GET['range'] : 'yday';
     24$range = isset($_GET['range']) ? $_GET['range'] : 'week';
     25
     26require_once 'functions.php';
    2527
    2628echo "<table border=\"0\">";
    2729echo "<tr>";
    2830echo "  <td style=width:1240>";
    29 echo "    <h1 align=\"middle\">Pointing Report</h1>";
    30 echo "  <center>";
    31 echo '    <a href="?pass=' . urlencode($pass) . '&proj=' . urlencode($proj) . '&range=yday">Last night (default)</a> | ';
    32 echo '    <a href="?pass=' . urlencode($pass) . '&proj=' . urlencode($proj) . '&range=day">Today (MJD '.getMJD().')</a> | ';
    33 echo '    <a href="?pass=' . urlencode($pass) . '&proj=' . urlencode($proj) . '&range=week">Past 7 days</a> | ';
    34 echo '    <a href="?pass=' . urlencode($pass) . '&proj=' . urlencode($proj) . '&range=month">Past 30 days</a> | ';
    35 echo '    <a href="?pass=' . urlencode($pass) . '&proj=' . urlencode($proj) . '&range=year">Past 365 days</a> | ';
    36 echo '    <a href="?pass=' . urlencode($pass) . '&proj=' . urlencode($proj) . '&range=all">All </a>   ';
     31echo "    <h1 align=\"middle\">Data Quality Report</h1>";
     32
     33#
     34# project selector
     35echo '<center>';
     36# Project selector with highlight
     37$projects = ["gpc1" => "GPC1", "gpc2" => "GPC2"];
     38foreach ($projects as $key => $label) {
     39    if ($proj == $key) {
     40        echo "<strong style='color:blue;'>$label</strong> | ";
     41    } else {
     42        echo '<a href="?pass=' . urlencode($pass) . '&proj=' . $key . '&range=' . urlencode($range) . '">' . $label . '</a> | ';
     43    }
     44}
     45echo "<br>";
     46
     47# range links with highlighting
     48$ranges = [
     49    "today"     => "Today",
     50    "yesterday" => "Yesterday",
     51    "week"      => "Week",
     52    "month"     => "Month",
     53    "year"      => "Year",
     54    "all"       => "All"
     55];
     56
     57foreach ($ranges as $key => $label) {
     58    if ($range == $key) {
     59        echo "<strong style='color:blue;'>$label</strong> | ";
     60    } else {
     61        echo '<a href="?pass=' . urlencode($pass) . '&proj=' . urlencode($proj) . '&range=' . $key . '">' . $label . '</a> | ';
     62    }
     63}
     64
    3765echo "  </td>";
    3866echo "</tr>";
    3967echo "</table>";
    40 echo getPointing($projectdb, $range);
    4168
     69echo "    <div style=\"margin-bottom:10px; font-size:14px; color:#555;\">";
     70echo "      Current MJD: " . getMJD();
     71echo "    </div>";
     72
     73echo getPointing($projectdb, $range, $proj);
    4274menu_end();
    43 
    44 
    45 /////////////////////////////////////////////////////////////////////////////
    46 //                                                                         //
    47 //                               Functions                                 //
    48 //                                                                         //
    49 /////////////////////////////////////////////////////////////////////////////
    50 ###########################################################################
    51 #
    52 # Get data quality
    53 #
    54 ###########################################################################
    55 function getPointing($db, $range = 'yday') {
    56     // Start timer
    57     $start = microtime(true);
    58 
    59     $mjdDay  = getMJD();
    60     $mjdDay_yearback = $mjdDay - 366;
    61     $mjdDay_monthback = $mjdDay - 32;
    62     $min_exp_id = 0;
    63 
    64     // Determine the reference exp_name based on the selected range
    65     switch ($range) {
    66         case 'year':
    67             $expname = "o" . $mjdDay_yearback . "%";
    68             break;
    69         case 'month':
    70             $expname = "o" . $mjdDay_monthback . "%";
    71             break;
    72         case 'week':
    73             $expname = "o" . ($mjdDay - 7) . "%";
    74             break;
    75         case 'day':
    76             $expname = "o" . $mjdDay . "%";
    77             break;
    78         default:
    79             $expname = "o" . ($mjdDay - 1) . "%"; // Default: last night
    80             break;
    81     }
    82 
    83     // Query to get the minimum exp_id
    84     $expIdQuery = "SELECT MIN(exp_id) AS min_exp_id FROM rawExp WHERE rawExp.exp_name LIKE '$expname'";
    85     $result = $db->query($expIdQuery);
    86 
    87     if (DB::isError($result)) {
    88         die("Database error: " . $result->getMessage());
    89     }
    90 
    91     $row = $result->fetchRow(DB_FETCHMODE_ASSOC);
    92 
    93     if (!empty($row['min_exp_id'])) {
    94         $min_exp_id = $row['min_exp_id'];
    95     //    echo "Minimum exp_id: " . $min_exp_id;
    96     } else {
    97         echo "No results found.";
    98     }
    99 
    100     //echo "<br><br>";
    101 
    102     // Determine the date constraint based on the range
    103     switch ($range) {
    104         case 'year':
    105             $dateConstraint = "dateobs >= NOW() - INTERVAL 365 DAY AND rawExp.exp_id >= $min_exp_id";
    106             break;
    107         case 'month':
    108             $dateConstraint = "dateobs >= NOW() - INTERVAL 30 DAY AND rawExp.exp_id >= $min_exp_id";
    109             break;
    110         case 'week':
    111             $expnames = array();
    112             for ($i = 0; $i < 7; $i++) {
    113                 $expnames[] = "rawExp.exp_name LIKE 'o" . ($mjdDay - $i) . "%'";
    114             }
    115             $dateConstraint = "(" . implode(" OR ", $expnames) . ") AND rawExp.exp_id >= $min_exp_id";
    116             break;
    117         case 'yday':
    118             $dateConstraint = "rawExp.exp_name LIKE 'o" . ($mjdDay - 1) . "%' AND rawExp.exp_id >= $min_exp_id";
    119             break;
    120         case 'day':
    121             $dateConstraint = "rawExp.exp_name LIKE 'o$mjdDay%' AND rawExp.exp_id >= $min_exp_id";
    122             break;
    123         case 'all':
    124             $dateConstraint = "rawExp.exp_id >= 0";
    125             break;
    126         default: // Default to last night
    127             $dateConstraint = "rawExp.exp_name LIKE 'o" . ($mjdDay - 1) . "%' AND rawExp.exp_id >= $min_exp_id";
    128             break;
    129     }
    130  
    131     // Updated SQL query
    132     $sql = "select * from (SELECT SUBTIME(dateobs, '10:00:00') AS time, AST_R0, AST_D0,
    133                    round(( 367*YEAR(dateobs)- FLOOR((7 * (YEAR(dateobs) + FLOOR((MONTH(dateobs) + 9) / 12))) / 4) + FLOOR(275 * MONTH(dateobs) / 9)
    134                    + DAY(dateobs) + 1721013.5 + (HOUR(dateobs) / 24.0) + (MINUTE(dateobs) / 1440.0) + (SECOND(dateobs) / 86400.0) - 2400000.5), 5) AS MJD,
    135                    filter, exp_time, comment, ra*180/pi() as RA, decl*180/pi() as DECL, AST_RS, AST_DS, AST_T0, posang, exp_name, alt, az, rawExp.exp_id, camRun.label
    136             FROM rawExp
    137             JOIN chipRun USING (exp_id)
    138             JOIN camRun USING (chip_id)
    139             JOIN camProcessedExp USING (cam_id)
    140             WHERE $dateConstraint
    141               AND exp_type = 'OBJECT'
    142               AND camRun.state LIKE 'full'
    143               AND camProcessedExp.fault = 0
    144               AND camProcessedExp.zpt_obs != 0 ORDER BY RAND() limit 5000) as sub order by time;";
    145 
    146     $DQarray = array();
    147     $qry = $db->query($sql);
    148     if (dberror($qry)) {
    149         echo "<b>Error with $sql</b><br>\n";
    150         return;
    151     }
    152     //    echo "$sql<br><br>\n";
    153    
    154     while ($qry->fetchInto($row)) {
    155         $DQarray[] = $row;
    156     }
    157    
    158     // Ensure $DQarray is not empty
    159     if (empty($DQarray)) {
    160         echo "<center><b> No data for MJD: $mjdDay </b></center>";
    161         return; // Stop execution if no data to encode
    162     }
    163    
    164     // Convert data array to JSON for easier handling in JavaScript
    165     $dataJson = json_encode($DQarray);
    166    
    167     if ($dataJson === false) {
    168         echo "<b>Error encoding JSON:</b> " . json_last_error_msg(); // Display any JSON encoding errors
    169         return; // Stop execution if JSON encoding fails
    170     }
    171    
    172     echo "<script>console.log(" . json_encode($dataJson) . ");</script>"; // Log JSON output to console for debugging
    173 echo <<<HTML
    174 <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js" onerror="loadLocalGoogleCharts()"></script>
    175 
    176 <script type="text/javascript">
    177     // Check if Google Charts library loaded successfully
    178     window.addEventListener("load", function() {
    179         if (typeof google === "undefined" || typeof google.charts === "undefined") {
    180             loadLocalGoogleCharts();
    181         }
    182     });
    183 
    184     // Function to load local version of Google Charts if CDN fails
    185     function loadLocalGoogleCharts() {
    186         console.warn("Google Charts CDN failed, loading local loader.js instead.");
    187         var script = document.createElement("script");
    188         script.src = "loader.js"; // Path to your local loader.js file
    189         document.head.appendChild(script);
    190     }
    191 </script>
    192 
    193 <script type="text/javascript">
    194     google.charts.load('current', {packages: ['corechart']});
    195 
    196     // Use the encoded JSON directly in JavaScript
    197     const dataArray = JSON.parse('{$dataJson}'); // Directly parse the PHP JSON string
    198 
    199     // draw pointing vs mjd
    200     function drawMJDChart() {
    201         var data = new google.visualization.DataTable();
    202             data.addColumn('number', 'MJD');
    203             data.addColumn('number', 'RA_offset (AST_R0)');
    204             data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
    205             data.addColumn('number', 'Dec_offset (AST_D0)');
    206             data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
    207             data.addColumn('number', '20 TP unit ~5.1"');
    208             data.addColumn('number', '-20 TP unit ~5.1"');
    209 
    210         const rows = dataArray.map(row => {
    211             const [timestamp, ra_offset, dec_offset, mjd, filter, exp_time, comment, ra, dec, ra_offset_sig, dec_offset_sig, boresite_ang, position_ang, exp_name, alt, az, exp_id, cam_label] = row;
    212 
    213             return [
    214                 parseFloat(mjd),               // MJD
    215                 parseFloat(ra_offset),         // RA_offset (AST_R0)
    216                 "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>timestamp: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label,
    217                 parseFloat(dec_offset),        // Dec_offset (AST_D0)
    218                 "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>timestamp: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label,
    219                 20,                            // Dummy value for 20 TP unit
    220                 -20                            // Dummy value for -20 TP unit
    221             ];
    222         });
    223 
    224         data.addRows(rows);
    225 
    226         // Extract the first MJD value from dataArray
    227         //const firstMJD = dataArray.length > 0 ? parseFloat(dataArray[0][1]) : "N/A";
    228         const firstMJD = dataArray.length > 0 ? Math.floor(parseFloat(dataArray[0][3])) : "N/A";
    229         console.log("First MJD Value:", firstMJD);
    230 
    231         const options = {
    232             title: 'Pointing Offset (MJD: '+firstMJD+')',
    233             titleTextStyle: {color: 'black', fontSize: 15},
    234             width: '100%',
    235             height: 480,
    236             legend: { position: 'top', alignment: 'center' },
    237             series: {
    238                 0: { color: '#0000ff', pointSize: 3, lineWidth: 0 }, // RA_offset series
    239                 1: { color: '#33a532', pointSize: 3, lineWidth: 0 }, // Dec_offset series
    240                 2: { color: 'red', lineWidth: 2, lineDashStyle: [8, 4], pointSize: 0 }, // Line at y = 20
    241                 3: { color: 'red', lineWidth: 2, lineDashStyle: [8, 4], pointSize: 0 }  // Line at y = -20
    242             },
    243             intervals: { color: 'red', lineWidth: 2, style: 'sticks' },
    244             hAxis: {
    245                 title: 'MJD',
    246                 gridlines: {count: -1}
    247             },
    248             vAxis: {
    249                 title: 'Offset (TP units)',
    250             },
    251             tooltip: { isHtml: true }, // Render tooltips as HTML
    252             chartArea: {left: '7%', top: 60, right: '4%', bottom: 50}, // Adjust left and right
    253             fontSize: 15,
    254         };
    255 
    256         const chart = new google.visualization.ScatterChart(document.getElementById('dq_div_mjd'));
    257         chart.draw(data, options);
    258     }
    259 
    260     // draw pointing scatter
    261     function drawSIGChart() {
    262         var data = new google.visualization.DataTable();
    263             data.addColumn('number', 'MJD');
    264             data.addColumn('number', 'RA_offset_scatter (AST_RS)');
    265             data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
    266             data.addColumn('number', 'Dec_offset_scatter (AST_DS)');
    267             data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
    268 
    269         const rows = dataArray.map(row => {
    270             const [timestamp, ra_offset, dec_offset, mjd, filter, exp_time, comment, ra, dec, ra_offset_sig, dec_offset_sig, boresite_ang, position_ang, exp_name, alt, az, exp_id, cam_label] = row;
    271 
    272             return [
    273                 parseFloat(mjd),               // MJD
    274                 parseFloat(ra_offset_sig),     // RA_offset_scatter (AST_RS)
    275                 "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>timestamp: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label,
    276                 parseFloat(dec_offset_sig),    // Dec_offset_scatter (AST_DS)
    277                 "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>timestamp: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label,
    278             ];
    279         });
    280 
    281         data.addRows(rows);
    282 
    283         const options = {
    284             title: 'Pointing Offset Scatter (MJD)',
    285             titleTextStyle: {color: 'black', fontSize: 15},
    286             width: '100%',
    287             height: 480,
    288             legend: { position: 'top', alignment: 'center' },
    289             series: {
    290                 0: { color: '#0000ff', pointSize: 3, lineWidth: 0 }, // RA_offset series
    291                 1: { color: '#33a532', pointSize: 3, lineWidth: 0 }, // Dec_offset series
    292                 2: { color: 'red', lineWidth: 2, lineDashStyle: [8, 4], pointSize: 0 }, // Line at y = 20
    293                 3: { color: 'red', lineWidth: 2, lineDashStyle: [8, 4], pointSize: 0 }  // Line at y = -20
    294             },
    295             intervals: { color: 'red', lineWidth: 2, style: 'sticks' },
    296             hAxis: {
    297                 title: 'MJD',
    298                 gridlines: {count: -1}
    299             },
    300             vAxis: {
    301                 title: 'Offset Scatter (TP units)',
    302             },
    303             tooltip: { isHtml: true }, // Render tooltips as HTML
    304             chartArea: {left: '7%', top: 60, right: '4%', bottom: 50}, // Adjust left and right
    305             fontSize: 15,
    306         };
    307 
    308         const chart = new google.visualization.ScatterChart(document.getElementById('dq_div_sig'));
    309         chart.draw(data, options);
    310     }
    311 
    312 
    313     // position angle
    314     function drawPOSChart() {
    315         var data = new google.visualization.DataTable();
    316             data.addColumn('number', 'MJD');
    317             data.addColumn('number', 'RA_offset (AST_R0)');
    318             data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
    319             data.addColumn('number', 'Dec_offset (AST_D0)');
    320             data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
    321 
    322         const rows = dataArray.map(row => {
    323             const [timestamp, ra_offset, dec_offset, mjd, filter, exp_time, comment, ra, dec, ra_offset_sig, dec_offset_sig, boresite_ang, position_ang, exp_name, alt, az, exp_id, cam_label] = row;
    324 
    325             return [
    326                 parseFloat(position_ang),      // Position Angle
    327                 parseFloat(ra_offset),         // RA_offset (AST_R0)
    328                 "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>timestamp: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label,
    329                 parseFloat(dec_offset),        // Dec_offset (AST_D0)
    330                 "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>timestamp: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label,
    331             ];
    332         });
    333 
    334         data.addRows(rows);
    335 
    336         const options = {
    337             title: 'Pointing Offset (Position Angle)',
    338             titleTextStyle: {color: 'black', fontSize: 15},
    339             width: '100%',
    340             height: 480,
    341             legend: { position: 'top', alignment: 'center' },
    342             // pointSize: 5, // Point size for scatter plot
    343             // lineWidth: 2,
    344             series: {
    345                 0: { color: '#0000ff', pointSize: 3, lineWidth: 0 }, // RA_offset series
    346                 1: { color: '#33a532', pointSize: 3, lineWidth: 0 }, // Dec_offset series
    347             },
    348             intervals: { color: 'red', lineWidth: 2, style: 'sticks' },
    349             hAxis: {
    350                 title: 'Position Angle',
    351                 gridlines: {count: -1}
    352             },
    353             vAxis: {
    354                 title: 'Offset (TP units)',
    355             },
    356             tooltip: { isHtml: true }, // Render tooltips as HTML
    357             chartArea: {left: '7%', top: 60, right: '4%', bottom: 50}, // Adjust left and right
    358             fontSize: 15,
    359         };
    360 
    361         const chart = new google.visualization.ScatterChart(document.getElementById('dq_div_pos'));
    362         chart.draw(data, options);
    363     }
    364 
    365     function drawALTChart() {
    366         var data = new google.visualization.DataTable();
    367             data.addColumn('number', 'Altitute');
    368             data.addColumn('number', 'RA_offset (AST_R0)');
    369             data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
    370             data.addColumn('number', 'Dec_offset (AST_D0)');
    371             data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
    372 
    373         const rows = dataArray.map(row => {
    374             const [timestamp, ra_offset, dec_offset, mjd, filter, exp_time, comment, ra, dec, ra_offset_sig, dec_offset_sig, boresite_ang, position_ang, exp_name, alt, az, exp_id, cam_label] = row;
    375 
    376             return [
    377                 parseFloat(alt),      // Position Angle
    378                 parseFloat(ra_offset),         // RA_offset (AST_R0)
    379                 "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>timestamp: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label,
    380                 parseFloat(dec_offset),        // Dec_offset (AST_D0)
    381                 "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>timestamp: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label,
    382             ];
    383         });
    384 
    385         data.addRows(rows);
    386 
    387         const options = {
    388             title: 'Pointing Offset (Altitude)',
    389             titleTextStyle: {color: 'black', fontSize: 15},
    390             width: '100%',
    391             height: 480,
    392             legend: { position: 'top', alignment: 'center' },
    393             series: {
    394                 0: { color: '#0000ff', pointSize: 3, lineWidth: 0 }, // RA_offset series
    395                 1: { color: '#33a532', pointSize: 3, lineWidth: 0 }, // Dec_offset series
    396             },
    397             intervals: { color: 'red', lineWidth: 2, style: 'sticks' },
    398             hAxis: {
    399                 title: 'Altitude',
    400                 gridlines: {count: -1}
    401             },
    402             vAxis: {
    403                 title: 'Offset (TP units)',
    404             },
    405             tooltip: { isHtml: true }, // Render tooltips as HTML
    406             //chartArea: {left: 80, top: 50, right: 20, bottom: 50},
    407             chartArea: {left: '7%', top: 60, right: '4%', bottom: 50}, // Adjust left and right
    408             fontSize: 15,
    409         };
    410 
    411         const chart = new google.visualization.ScatterChart(document.getElementById('dq_div_alt'));
    412         chart.draw(data, options);
    413     }
    414 
    415    function drawAZChart() {
    416         var data = new google.visualization.DataTable();
    417             data.addColumn('number', 'Azumith');
    418             data.addColumn('number', 'RA_offset (AST_R0)');
    419             data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
    420             data.addColumn('number', 'Dec_offset (AST_D0)');
    421             data.addColumn({'type': 'string', 'role': 'tooltip', 'p': {'html': true}});
    422 
    423         const rows = dataArray.map(row => {
    424             const [timestamp, ra_offset, dec_offset, mjd, filter, exp_time, comment, ra, dec, ra_offset_sig, dec_offset_sig, boresite_ang, position_ang, exp_name, alt, az, exp_id, cam_label] = row;
    425 
    426             return [
    427                 parseFloat(az),      // Position Angle
    428                 parseFloat(ra_offset),         // RA_offset (AST_R0)
    429                 "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>timestamp: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label,
    430                 parseFloat(dec_offset),        // Dec_offset (AST_D0)
    431                 "expname: "+exp_name+"<br>exp_id: "+exp_id+"<br>timestamp: "+timestamp+"<br>RA: "+ra+"<br>DEC: "+dec+"<br>filter: "+filter+"<br>comment: "+comment+"<br>cam_label: "+cam_label,
    432             ];
    433         });
    434 
    435         data.addRows(rows);
    436 
    437         const options = {
    438             title: 'Pointing Offset (Azumith)',
    439             titleTextStyle: {color: 'black', fontSize: 15},
    440             width: '100%',
    441             height: 480,
    442             legend: { position: 'top', alignment: 'center' },
    443             series: {
    444                 0: { color: '#0000ff', pointSize: 3, lineWidth: 0 }, // RA_offset series
    445                 1: { color: '#33a532', pointSize: 3, lineWidth: 0 }, // Dec_offset series
    446             },
    447             intervals: { color: 'red', lineWidth: 2, style: 'sticks' },
    448             hAxis: {
    449                 title: 'Azumith',
    450                 gridlines: {count: -1}
    451             },
    452             vAxis: {
    453                 title: 'Offset (TP units)',
    454             },
    455             tooltip: { isHtml: true }, // Render tooltips as HTML
    456             //chartArea: {left: 80, top: 50, right: 20, bottom: 50},
    457             chartArea: {left: '7%', top: 60, right: '4%', bottom: 50}, // Adjust left and right
    458             fontSize: 15,
    459         };
    460 
    461         const chart = new google.visualization.ScatterChart(document.getElementById('dq_div_az'));
    462         chart.draw(data, options);
    463     }
    464 
    465     google.charts.setOnLoadCallback(drawMJDChart);
    466     google.charts.setOnLoadCallback(drawSIGChart);
    467     google.charts.setOnLoadCallback(drawPOSChart);
    468     google.charts.setOnLoadCallback(drawALTChart);
    469     google.charts.setOnLoadCallback(drawAZChart);
    470 </script>
    471 
    472 <div class="chartWithOverlay" style="position: relative; width: 1240px; height:500px;">
    473     <div id="dq_div_mjd" style="width:1240px;"></div>
    474     <div class="overlay" style="position: absolute; width: 800px; bottom: 60px; left: 85px;"></div>
    475 </div>
    476 
    477 <div class="chartWithOverlay" style="position: relative; width: 1240px; height:500px;">
    478     <div id="dq_div_sig" style="width:1240px;"></div>
    479     <div class="overlay" style="position: absolute; width: 800px; bottom: 60px; left: 85px;"></div>
    480 </div>
    481 
    482 <div class="chartWithOverlay" style="position: relative; width: 1240px; height:500px;">
    483     <div id="dq_div_pos" style="width:1240px;"></div>
    484     <div class="overlay" style="position: absolute; width: 800px; bottom: 60px; left: 85px;"></div>
    485 </div>
    486 
    487 <div class="chartWithOverlay" style="position: relative; width: 1240px; height:500px;">
    488     <div id="dq_div_alt" style="width:1240px;"></div>
    489     <div class="overlay" style="position: absolute; width: 800px; bottom: 60px; left: 85px;"></div>
    490 </div>
    491 
    492 <div class="chartWithOverlay" style="position: relative; width: 1240px; height:500px;">
    493     <div id="dq_div_az" style="width:1240px;"></div>
    494     <div class="overlay" style="position: absolute; width: 800px; bottom: 60px; left: 85px;"></div>
    495 </div>
    496 HTML;
    497 
    498 // End timer
    499 $total_time = round(microtime(true) - $start, 3);
    500 echo "<center>Loading plot in $total_time seconds<br></center>";
    501 
    502 }
    50375
    50476?>
  • trunk/ippMonitor/raw/storage.php

    r42923 r42972  
    1212$projectdb = dbconnect($ID['proj']);
    1313
     14
    1415if ($ID['menu']) {$myMenu = $ID['menu'];}
    1516else {$myMenu = "ipp.czar.dat";}
    1617
    17 menu($myMenu, 'Czartool on '.$lastUpdateTime, 'ipp.css', $ID['link'], $ID['proj']);
     18menu($myMenu, 'Czartool on ', 'ipp.css', $ID['link'], $ID['proj']);
    1819
    1920$pass = $ID['pass'];
     
    2526echo "  <td style=width:1200px>";
    2627echo "  <h1 align=\"middle\">Storage status</h1>";
     28
     29require_once 'functions.php'; 
     30
    2731# storage timeseries
    2832echo getSpaces($czardb);
     
    3539menu_end();
    3640
    37 
    38 /////////////////////////////////////////////////////////////////////////////
    39 //                                                                         //
    40 //                               Functions                                 //
    41 //                                                                         //
    42 /////////////////////////////////////////////////////////////////////////////
    43 
    44 ###########################################################################
    45 #
    46 # Gets disk status
    47 #
    48 ###########################################################################
    49 function getHosts($db) {
    50     $start = microtime(true);
    51 
    52 
    53     // Set environment variables
    54     putenv("PATH=/usr/local/bin:/usr/bin:/bin:/data/ippc65.0/ipp/src/psconfig//ipp-20210708-gentoo.lin64/bin");
    55     putenv("PERL5LIB=/data/ippc65.0/ipp/src/psconfig//ipp-20210708-gentoo.lin64/lib");
    56 
    57     $hostnoteall = array();
    58     exec("neb-host |cut -b 17-30,86-", $hostnoteall);
    59 
    60     $sql = "SELECT host, format(total, 2), format(available, 2), format(used, 2), writable, readable, format(used/total*100,3) as ratio FROM hosts where xattr <> 3 ORDER BY CASE WHEN ROUND(used/total*100,3) = 100 THEN 0 ELSE 1 END, xattr, host;";
    61     $qry = $db->query($sql);
    62 
    63     if ($qry === false) {
    64         echo "<b>Error with SQL query</b><br>";
    65         return;
    66     }
    67 
    68     $sqlMax = "SELECT MAX(total) AS max_total FROM hosts WHERE xattr <> 3";
    69     $qryMax = $db->query($sqlMax);
    70     $maxUsed = 0;
    71    
    72     if ($qryMax && $qryMax->fetchInto($rowMax)) {
    73         $maxUsed = $rowMax[0];  // max_total value
    74     }
    75 
    76     $dataRows = array();
    77     while ($qry->fetchInto($row)) {
    78         // Access by index
    79         $host      = $row[0]; // ipp071.0
    80         $total     = $row[1]; // 80.0352 TB
    81         $available = $row[2]; // 10.8096 TB
    82         $used      = $row[3]; // 69.2256 TB
    83         $writable  = $row[4]; // 0
    84         $readable  = $row[5]; // 1
    85         $ratio     = $row[6]; // 86.4939 %
    86 
    87         // Match notes
    88         $hostnotes = preg_grep("/$host/", $hostnoteall);
    89         $hostnote = reset($hostnotes);
    90 
    91         // Defaults
    92         $styles    = "stroke-width: .5; stroke-color: black;";
    93         $status    = "down";
    94         $usedColor = "lightgrey";
    95         $freeColor = "lightgrey";
    96 
    97         // Status logic
    98         if ($writable == 1 && $readable == 1) {
    99             $status    = "up";
    100             $usedColor = ($ratio >= 97) ? "pink" : "yellow"; // red if ≥97%
    101             $freeColor = "#a6ec99"; // green free
    102         } elseif ($writable == 0 && $readable == 1) {
    103             $status    = "repair";
    104             $usedColor = ($ratio >= 97) ? "pink" : "yellow";
    105             $freeColor = "lightgrey"; // grey free
    106         } elseif ($available < 1) {
    107             $used      = $maxUsed;
    108             $available = 0;
    109             $status    = "offline";
    110             $usedColor = "white";
    111             $freeColor = "white";
    112         }
    113    
    114         if ($status == "down") {
    115             $tooltipUsed = $host . ": " . round($ratio, 1) . "% of " . $total . " TB used ".
    116                            "<span style='color:red; font-weight:bold;'> <br>" . $hostnote .  "</span>";
    117             $tooltipFree = "<span style='color:red; font-weight:bold;'>" .
    118                            $host . ": " . $available . " TB free" .
    119                            "</span>";
    120         } elseif ($status == "offline") {
    121             $tooltipUsed = $host . ": " . round($ratio, 1) . "% of " . $total . " TB used ".
    122                            "<span style='color:red; font-weight:bold;'> <br>" . $hostnote .  "</span>";
    123             $tooltipFree = "<span style='color:red; font-weight:bold;'>" .
    124                            $host . ": " . $total . " TB free" .
    125                            "</span>";
    126         } else {
    127             $tooltipUsed = $host . ": " . round($ratio, 1) . "% of " . $total . " TB used<br>" . $hostnote ;
    128             $tooltipFree = $host . ": " . $available . " TB free";
    129         }
    130 
    131 
    132         // Build data row (manual string concatenation for old PHP)
    133         $dataRows[] =
    134             "[ \"" . $host . ":" . $status . "\", " .
    135             $used . ", \"" . $styles . " color: " . $usedColor . ";\", \"" . $tooltipUsed . "\", " .
    136             $available . ", \"" . $styles . " color: " . $freeColor . ";\", \"" . $tooltipFree . "\" ]";
    137 
    138     }
    139     echo "<script type=\"text/javascript\">
    140             google.charts.load('current', {packages: ['corechart', 'bar']});
    141             google.charts.setOnLoadCallback(drawBarColors);
    142 
    143             function drawBarColors() {
    144                 var data = google.visualization.arrayToDataTable([
    145                     ['Host', 'Used', {type: 'string', role: 'style'}, {type: 'string', role: 'tooltip', 'p': {'html': true}},
    146                      'Free', {type: 'string', role: 'style'}, {type: 'string', role: 'tooltip', 'p': {'html': true}}],
    147                     " . implode(",\n", $dataRows) . "
    148                 ]);
    149                
    150                 var options = {
    151                     title: 'Nebulous Disk Use across IPP Clusters',
    152                     titleTextStyle: {color: 'black', fontSize: 15},
    153                     width: '100%',
    154                     height: '100%',
    155                     tooltip: {isHtml: true},
    156                     legend: { position: 'none', alignment: 'end', maxLines: 3 },
    157                     hAxis: { title: 'Space (TB)', gridlines: {count: 20} },
    158                     vAxis: { textStyle: {fontSize: 14}, format: 'string',  textPosition: 'in', direction: 1},
    159                     bar: { groupWidth: '95%' },
    160                     chartArea: {left:40, top:50, right:20, bottom:40},
    161                     fontSize: 15,
    162                     allowHtml: true,
    163                     isStacked: true
    164                 };
    165 
    166                 var chart = new google.visualization.BarChart(document.getElementById('disk_div'));
    167                 chart.draw(data, options);
    168             }
    169           </script>";
    170 
    171 echo "<script type='text/javascript' src='loader.js'></script>
    172 <br>
    173 <div class='chartWithOverlay' style='position: relative; width: 100%'>
    174 
    175     <div id='disk_div' style='width:100%; height:5000px'></div>
    176 
    177     <div class='overlay' style='position: absolute; width:160px; top:50px; right:30px; font-family:Arial, sans-serif;'>
    178 
    179       <div style='font-size:15px; border:1px solid #888; background-color:lightgreen; color:black; text-align:center; margin-bottom:1px;'>Free</div>
    180       <div style='font-size:15px; border:1px solid #888; background-color:yellow    ; color:black; text-align:center; margin-bottom:1px;'>Used</div>
    181       <div style='font-size:15px; border:1px solid #888; background-color:pink      ; color:black; text-align:center; margin-bottom:1px;'>97% Usage</div>
    182       <div style='font-size:15px; border:1px solid #888; background-color:lightgrey ; color:black; text-align:center; margin-bottom:1px;'>Down/Repair</div>
    183       <div style='font-size:15px; border:1px solid #888; background-color:white     ; color:black; text-align:center; margin-bottom:1px;'>Offline</div>
    184       <div style='font-size:15px; border:1px solid #888; background-color:#42a5f5   ; color:white; text-align:center; margin-bottom:1px;'>
    185         <a href='https://atrcganglia.ifa.hawaii.edu/' target='_blank' style='text-decoration:none; color:white; font-weight:bold;'>ATRC ganglia</a>
    186       </div>
    187 
    188     </div>
    189 
    190 </div>";
    191 
    192     $total_time = round(microtime(true) - $start, 3);
    193     echo "<center>loading in $total_time seconds<br></center>";
    194 }
    195 
    196 
    197 ###########################################################################
    198 #
    199 # Gets disk spaces status
    200 #
    201 ###########################################################################
    202 function getSpaces($db) {
    203     // timer start
    204     $time = microtime();
    205     $time = explode(' ', $time);
    206     $time = $time[1] + $time[0];
    207     $start = $time;
    208 
    209     $db->query("SELECT FORMAT(IFNULL(SUM(available), 0), 1) FROM hosts where host like 'ippb%' and xattr <> 3")->fetchInto($row);
    210     $ippbusable = $row[0];
    211     $db->query("SELECT FORMAT(IFNULL(SUM(available), 0), 1) FROM hosts where host like 'ippb%' and writable = 1 and xattr <> 3")->fetchInto($row);
    212     $ippbfree = $row[0];
    213 
    214     $db->query("SELECT FORMAT(IFNULL(SUM(available), 0), 1) FROM hosts where host not like 'ippb%' and xattr <> 3")->fetchInto($row);
    215     $ippusable = $row[0];
    216     $db->query("SELECT FORMAT(IFNULL(SUM(available), 0), 1) FROM hosts where host not like 'ippb%' and writable = 1 and xattr <> 3")->fetchInto($row);
    217     $ippfree = $row[0];
    218 
    219     $db->query("SELECT FORMAT(IFNULL(SUM(available), 0), 1) FROM hosts where host like 'ipp%_bck.0' and xattr <> 3")->fetchInto($row);
    220     $bckusable = $row[0];
    221     $db->query("SELECT FORMAT(IFNULL(SUM(available), 0), 1) FROM hosts where host like 'ipp%_bck.0' and writable = 1 and xattr <> 3")->fetchInto($row);
    222     $bckfree = $row[0];
    223 
    224 
    225     $sql = "select format(used/total*100,1), usable from cluster_space order by timestamp desc limit 1;";
    226 
    227     if ($debug) {echo "$sql<br>";}
    228     $qry = $db->query($sql);
    229     if (dberror($qry)) {echo "<b>error with $sql </b><br>\n";}
    230     $qry->fetchInto($row);
    231     $allocated = $row[0];
    232     $usable    = $row[1];
    233 
    234     #echo "<script type=\"text/javascript\" src=\"https://www.gstatic.com/charts/loader.js\"></script>";
    235     echo "<script type=\"text/javascript\" src=\"loader.js\"></script>";
    236     echo "<br><div class=\"chartWithOverlay\" style=\"position: relative; width: 100%; height:400\">";
    237     echo "  <div id=\"space_div\" style=\"width:100%; \"></div>";
    238     echo "  <div class=\"overlay\" style=\"position: absolute; width: 500px; bottom: 60px; left: 80px;\">";
    239     echo "    <div style=\"font-size: 16px; color:#0000ff; \"><b>ipp*_bck: $bckusable TB (usable)</b></div>";
    240     echo "    <div style=\"font-size: 16px; color:#33a532; \"><b>ipp*_bck: $bckfree   TB (free)</b></div>";
    241     echo "  </div>";
    242     echo "</div>";
    243 
    244     echo "<script type=\"text/javascript\">";
    245     echo "google.charts.load('current', {packages: ['corechart']});";
    246     echo "google.charts.setOnLoadCallback(drawChart);";
    247     echo "function drawChart() {";
    248     echo "  var data = new google.visualization.DataTable();";
    249     echo "        data.addColumn('datetime', 'Day');";
    250     echo "        data.addColumn('number', 'Usable (ipp:$ippusable, ippb:$ippbusable) TB');";
    251     echo "        data.addColumn('number', 'Free (ipp:$ippfree, ippb:$ippbfree) TB');";
    252     echo "   data.addRows([";
    253     $interval = 10;
    254     $sql = "select sub.dated, sub.usable, sub.available from (select date_format(timestamp, \"%Y.%m.%d.%H.%i\") as dated, usable, available from cluster_space where timestamp > curdate() - INTERVAL $interval DAY) as sub group by dated;";
    255     if ($debug) {echo "$sql<br>";}
    256     $qry = $db->query($sql);
    257     if (dberror($qry)) {echo "<b>error with $sql </b><br>\n";}
    258     while ($qry->fetchInto($row)) {
    259         $tsp = explode('.', $row[0]);
    260         $mon = $tsp[1]-1;
    261         $timestamp = $row[0];
    262         $usable    = $row[1];
    263         $available = $row[2];
    264 #       new Date(Year, Month, Day, Hours, Minutes, Seconds, Milliseconds)
    265        echo "[new Date($tsp[0], $mon, $tsp[2], $tsp[3], $tsp[4]), $usable, $available],";
    266     }
    267    echo "  ]);";
    268    echo "   var options = {";
    269    echo "      chart: {"; 
    270    echo "               position: 'center', " ;
    271    echo "              },";
    272    echo "     title: 'Total Available Cluster Space in the Past $interval Days ($allocated% of total allocated)',";
    273    echo "     titleTextStyle: {color: 'black', fontSize: 15},";
    274    echo "     width: '100%',";
    275    echo "     height: 400,";
    276    echo "     legend: { position: 'top',";
    277    echo "               alignment: 'center',";
    278    echo "              },";
    279    echo "     lineWidth: 2, ";
    280    echo "     series: {";
    281    echo "         0: { color: '#0000ff', lineWidth: 3 },      ";
    282    echo "         1: { color: '#33a532', lineWidth: 3 },      ";
    283    echo "              },";
    284    echo "     hAxis: {  ";
    285    echo "             title: 'Date/Time (HST)',";
    286    echo "             format: 'MMM dd',";
    287    echo "            gridlines: {count: -1},";
    288    echo "     },";
    289    echo "     vAxis: {  ";
    290    echo "                  title: 'Available (TB)',";
    291    echo "     viewWindow: {   min: 500    },  ";
    292    echo "     },";
    293    echo "     chartArea: {left:80, top:50, right:20, bottom:50},";
    294    echo "     fontSize: 12,";
    295    echo "   };";
    296    echo "   var chart = new google.visualization.LineChart(document.getElementById('space_div'));";
    297    echo "   chart.draw(data, options);";
    298    echo " }";
    299    echo "</script>";
    300     $time = microtime();
    301     $time = explode(' ', $time);
    302     $time = $time[1] + $time[0];
    303     $finish = $time;
    304     $total_time = round(($finish - $start), 3);
    305     $start= $finish;
    306     echo "<center>loading in $total_time seconds<br></center>";
    307 }
    308 
    309 
    31041?>
    31142
Note: See TracChangeset for help on using the changeset viewer.